@@ -115,7 +115,7 @@ import Prelude (Char, Bool(..), Maybe(..), (.), (&&), (<=), not, fst, maybe, (||
115115import Data.Bifunctor (first )
116116import Data.Semigroup ((<>) )
117117import qualified Prelude as P
118- import Data.Maybe (isJust )
118+ import Data.Maybe (fromMaybe , isJust )
119119import qualified Data.List as L
120120
121121#ifndef OS_PATH
@@ -604,11 +604,45 @@ splitFileName x = if null path
604604-- directory to make a valid FILEPATH, and having a "./" appear would
605605-- look strange and upset simple equality properties. See
606606-- e.g. replaceFileName.
607+ --
608+ -- A naive implementation is
609+ --
610+ -- splitFileName_ fp = (drv <> dir, file)
611+ -- where
612+ -- (drv, pth) = splitDrive fp
613+ -- (dir, file) = breakEnd isPathSeparator pth
614+ --
615+ -- but it is undesirable for two reasons:
616+ -- * splitDrive is very slow on Windows,
617+ -- * we unconditionally allocate 5 FilePath objects where only 2 would normally suffice.
618+ --
619+ -- In the implementation below we first speculatively split the input by the last path
620+ -- separator. In the vast majority of cases this is already the answer, except
621+ -- two exceptional cases explained below.
622+ --
607623splitFileName_ :: FILEPATH -> (STRING , STRING )
608- splitFileName_ fp = (drv <> dir, file)
624+ splitFileName_ fp
625+ -- If dirSlash is empty, @fp@ is either a genuine filename without any dir,
626+ -- or just a Windows drive name without slash like "c:".
627+ -- Run readDriveLetter to figure out.
628+ | isWindows
629+ , null dirSlash
630+ = fromMaybe (mempty , fp) (readDriveLetter fp)
631+ -- Another Windows quirk is that @fp@ could have been a shared drive "\\share"
632+ -- or UNC location "\\?\UNC\foo", where path separator is a part of the drive name.
633+ -- We can test this by trying dropDrive and falling back to splitDrive.
634+ | isWindows
635+ , Just (s1, _s2, bs') <- uncons2 dirSlash
636+ , isPathSeparator s1
637+ -- If bs' is empty, then s2 as the last character of dirSlash must be a path separator,
638+ -- so we are in the middle of shared drive.
639+ -- Otherwise, since s1 is a path separator, we might be in the middle of UNC path.
640+ , null bs' || maybe False (null . snd ) (readDriveUNC dirSlash)
641+ = (fp, mempty )
642+ | otherwise
643+ = (dirSlash, file)
609644 where
610- (drv, pth) = splitDrive fp
611- (dir, file) = breakEnd isPathSeparator pth
645+ (dirSlash, file) = breakEnd isPathSeparator fp
612646
613647-- | Set the filename.
614648--
0 commit comments