@@ -240,6 +240,10 @@ FileStat: type $type
240240 * files and directories.
241241 */
242242abstract class FileSystemEntity {
243+ static const _backslashChar = 0x5c ;
244+ static const _slashChar = 0x2f ;
245+ static const _colonChar = 0x3a ;
246+
243247 String get _path;
244248 Uint8List get _rawPath;
245249
@@ -531,16 +535,31 @@ abstract class FileSystemEntity {
531535 }
532536
533537 static final RegExp _absoluteWindowsPathPattern =
534- new RegExp (r'^(\\\\|[a-zA-Z]:[/\\])' );
538+ new RegExp (r'^(?: \\\\|[a-zA-Z]:[/\\])' );
535539
536540 /**
537- * Returns a [bool] indicating whether this object's path is absolute.
541+ * Whether this object's path is absolute.
542+ *
543+ * An absolute path is independent of the current working
544+ * directory ([Directory.current] ).
545+ * A non-absolute path must be interpreted relative to
546+ * the current working directory.
547+ *
548+ * On Windows, a path is absolute if it starts with `\\`
549+ * (two backslashesor representing a UNC path) or with a drive letter
550+ * between `a` and `z` (upper or lower case) followed by `:\` or `:/` .
551+ * The makes, for example, `\file.ext` a non-absolute path
552+ * because it depends on the current drive letter.
553+ *
554+ * On non-Windows, a path is absolute if it starts with `/` .
538555 *
539- * On Windows, a path is absolute if it starts with \\\\ or a drive letter
540- * between a and z (upper or lower case) followed by :\\ or :/.
541- * On non-Windows, a path is absolute if it starts with / .
556+ * If the path is not absolute, use [absolute] to get an entity
557+ * with an absolute path referencing the same object in the file system,
558+ * if possible .
542559 */
543- bool get isAbsolute {
560+ bool get isAbsolute => _isAbsolute (path);
561+
562+ static bool _isAbsolute (String path) {
544563 if (Platform .isWindows) {
545564 return path.startsWith (_absoluteWindowsPathPattern);
546565 } else {
@@ -553,37 +572,86 @@ abstract class FileSystemEntity {
553572 *
554573 * The type of the returned instance is the type of [this] .
555574 *
556- * The absolute path is computed by prefixing
557- * a relative path with the current working directory, and returning
558- * an absolute path unchanged.
575+ * A file system entity with an already absolute path
576+ * (as reported by [isAbsolute] ) is returned directly.
577+ * For a non-absolute path, the returned entity is absolute ([isAbsolute] )
578+ * *if possible*, but still refers to the same file system object.
559579 */
560580 FileSystemEntity get absolute;
561581
562582 String get _absolutePath {
563583 if (isAbsolute) return path;
584+ if (Platform .isWindows) return _absoluteWindowsPath (path);
564585 String current = Directory .current.path;
565- if (current.endsWith ('/' ) ||
566- (Platform .isWindows && current.endsWith ('\\ ' ))) {
586+ if (current.endsWith ('/' )) {
567587 return '$current $path ' ;
568588 } else {
569589 return '$current ${Platform .pathSeparator }$path ' ;
570590 }
571591 }
572592
573- Uint8List get _rawAbsolutePath {
574- if (isAbsolute) return _rawPath;
575- var current = Directory .current._rawPath.toList ();
576- assert (current.last == 0 );
577- current.removeLast (); // Remove null terminator.
578- if ((current.last == '/' .codeUnitAt (0 )) ||
579- (Platform .isWindows && (current.last == '\\ ' .codeUnitAt (0 )))) {
580- current.addAll (_rawPath);
581- return new Uint8List .fromList (current);
582- } else {
583- current.addAll (utf8.encode (Platform .pathSeparator));
584- current.addAll (_rawPath);
585- return new Uint8List .fromList (current);
593+ /// The ASCII code of the Windows drive letter if [entity] , if any.
594+ ///
595+ /// Returns the ASCII code of the upper-cased drive letter of
596+ /// the path of [entity] , if it has a drive letter (starts with `[a-zA-z]:` ),
597+ /// or `-1` if it has no drive letter.
598+ static int _windowsDriveLetter (String path) {
599+ if (! path.startsWith (':' , 1 )) return - 1 ;
600+ var first = path.codeUnitAt (0 ) & ~ 0x20 ;
601+ if (first >= 0x41 && first <= 0x5b ) return first;
602+ return - 1 ;
603+ }
604+
605+ /// The relative [path] converted to an absolute path.
606+ static String _absoluteWindowsPath (String path) {
607+ assert (Platform .isWindows);
608+ assert (! _isAbsolute (path));
609+ // Could perhaps use something like
610+ // https://docs.microsoft.com/en-us/windows/win32/api/pathcch/nf-pathcch-pathalloccombine
611+ var current = Directory .current.path;
612+ if (path.startsWith (r'\' )) {
613+ assert (! path.startsWith (r'\' , 1 ));
614+ // Absolute path, no drive letter.
615+ var currentDrive = _windowsDriveLetter (current);
616+ if (currentDrive >= 0 ) {
617+ return '${current [0 ]}:$path ' ;
618+ }
619+ // If `current` is a UNC path \\server\share[...],
620+ // we make the absolute path relative to the share.
621+ // Also works with `\\?\c:\` paths.
622+ if (current.startsWith (r'\\' )) {
623+ var serverEnd = current.indexOf (r'\' , 2 );
624+ if (serverEnd >= 0 ) {
625+ // We may want to recognize UNC paths of the form:
626+ // \\?\UNC\Server\share\...
627+ // specially, and be relative to the *share* not to UNC\.
628+ var shareEnd = current.indexOf (r'\' , serverEnd + 1 );
629+ if (shareEnd < 0 ) shareEnd = current.length;
630+ return '${current .substring (0 , shareEnd )}$path ' ;
631+ }
632+ }
633+ // If `current` is not in the drive-letter:path format,
634+ // or not \\server\share[\path],
635+ // we ignore it and return a relative path.
636+ return path;
637+ }
638+ var entityDrive = _windowsDriveLetter (path);
639+ if (entityDrive >= 0 ) {
640+ if (entityDrive != _windowsDriveLetter (current)) {
641+ // Need to resolve relative to current directory of the drive.
642+ // Windows remembers the last CWD of each drive.
643+ // We currently don't have that information, so we assume the root of that drive.
644+ return '${path [0 ]}:\\ $path ' ;
645+ }
646+
647+ /// A `c:relative\path` path on the same drive as `current` .
648+ path = path.substring (2 );
649+ assert (! path.startsWith (r'\\' ));
650+ }
651+ if (current.endsWith (r'\' ) || current.endsWith ('/' )) {
652+ return '$current $path ' ;
586653 }
654+ return '$current \\ $path ' ;
587655 }
588656
589657 static bool _identicalSync (String path1, String path2) {
0 commit comments