@@ -115,7 +115,7 @@ import Prelude (Char, Bool(..), Maybe(..), (.), (&&), (<=), not, fst, maybe, (||
115
115
import Data.Bifunctor (first )
116
116
import Data.Semigroup ((<>) )
117
117
import qualified Prelude as P
118
- import Data.Maybe (isJust )
118
+ import Data.Maybe (fromMaybe , isJust )
119
119
import qualified Data.List as L
120
120
121
121
#ifndef OS_PATH
@@ -604,11 +604,45 @@ splitFileName x = if null path
604
604
-- directory to make a valid FILEPATH, and having a "./" appear would
605
605
-- look strange and upset simple equality properties. See
606
606
-- 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
+ --
607
623
splitFileName_ :: 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)
609
644
where
610
- (drv, pth) = splitDrive fp
611
- (dir, file) = breakEnd isPathSeparator pth
645
+ (dirSlash, file) = breakEnd isPathSeparator fp
612
646
613
647
-- | Set the filename.
614
648
--
0 commit comments