@@ -6,7 +6,10 @@
6module Development.IDE.State.FileStore(
6module Development.IDE.State.FileStore(
7 getFileExists, getFileContents,
7 getFileExists, getFileContents,
8 setBufferModified,
8 setBufferModified,
9− fileStoreRules
9+ fileStoreRules,
@@ -14,7 +17,9 @@ module Development.IDE.State.FileStore(
14import StringBuffer
17import StringBuffer
15import Development.IDE.Orphans()
18import Development.IDE.Orphans()
20+import Control.Concurrent.Extra
17import qualified Data.Map.Strict as Map
21import qualified Data.Map.Strict as Map
18import qualified Data.Text as T
23import qualified Data.Text as T
19import Data.Time.Clock
24import Data.Time.Clock
20import Control.Monad.Extra
25import Control.Monad.Extra
@@ -22,28 +27,54 @@ import qualified System.Directory as Dir
22import Development.Shake
27import Development.Shake
23import Development.Shake.Classes
28import Development.Shake.Classes
24import Development.IDE.State.Shake
29import Development.IDE.State.Shake
25−import Control.Concurrent.Extra
26import Control.Exception
30import Control.Exception
27import GHC.Generics
31import GHC.Generics
28import System.IO.Error
32import System.IO.Error
29import qualified Data.ByteString.Char8 as BS
33import qualified Data.ByteString.Char8 as BS
30import qualified StringBuffer as SB
34import qualified StringBuffer as SB
31import Development.IDE.Types.Diagnostics
35import Development.IDE.Types.Diagnostics
36+import qualified Data.Rope.UTF16 as Rope
32import Data.Time
37import Data.Time
34−
39+import Language.Haskell.LSP.Core
35−-- This module stores the changed files in memory, and answers file system questions
40+import Language.Haskell.LSP.VFS
36−-- from either the memory changes OR the file system itself
41+
37−
42+-- | haskell-lsp manages the VFS internally and automatically so we cannot use
38−type DirtyFiles = Map.Map FilePath (UTCTime, StringBuffer) -- when it was modified, it's current value
43+-- the builtin VFS without spawning up an LSP server. To be able to test things
39−
44+-- like `setBufferModified` we abstract over the VFS implementation.
40−-- Store the DirtyFiles globally, so we can get at it through setBufferModified
45+data VFSHandle = VFSHandle
41−newtype GlobalDirtyFiles = GlobalDirtyFiles (Var DirtyFiles)
46+ { getVirtualFile :: Uri -> IO (Maybe VirtualFile)
47+ , setVirtualFileContents :: Uri -> T.Text -> IO ()
48+ , removeVirtualFile :: Uri -> IO ()
42−instance IsIdeGlobal GlobalDirtyFiles
51+instance IsIdeGlobal VFSHandle
53+makeVFSHandle :: IO VFSHandle
55+ vfsVar <- newVar (1, Map.empty)
57+ { getVirtualFile = \uri -> do
58+ (_nextVersion, vfs) <- readVar vfsVar
59+ pure $ Map.lookup uri vfs
60+ , setVirtualFileContents = \uri content ->
61+ modifyVar_ vfsVar $ \(nextVersion, vfs) ->
62+ pure (nextVersion + 1, Map.insert uri (VirtualFile nextVersion (Rope.fromText content) Nothing) vfs)
63+ , removeVirtualFile = \uri -> modifyVar_ vfsVar $ \(nextVersion, vfs) -> pure (nextVersion, Map.delete uri vfs)
66+makeLSPVFSHandle :: LspFuncs c -> VFSHandle
67+makeLSPVFSHandle lspFuncs = VFSHandle
68+ { getVirtualFile = getVirtualFileFunc lspFuncs
69+ , setVirtualFileContents = \_ _ -> pure ()
70+ -- ^ Handled internally by haskell-lsp.
71+ , removeVirtualFile = \_ -> pure ()
72+ -- ^ Handled internally by haskell-lsp.
45-- | Get the contents of a file, either dirty (if the buffer is modified) or from disk.
76-- | Get the contents of a file, either dirty (if the buffer is modified) or from disk.
46−type instance RuleResult GetFileContents = (UTCTime, StringBuffer)
77+type instance RuleResult GetFileContents = (FileVersion, StringBuffer)
48-- | Does the file exist.
79-- | Does the file exist.
49type instance RuleResult GetFileExists = Bool
80type instance RuleResult GetFileExists = Bool
@@ -60,12 +91,12 @@ instance Hashable GetFileContents
60instance NFData GetFileContents
91instance NFData GetFileContents
63−getFileExistsRule :: Var DirtyFiles -> Rules ()
94+getFileExistsRule :: VFSHandle -> Rules ()
64−getFileExistsRule dirty =
95+getFileExistsRule vfs =
65 defineEarlyCutoff $ \GetFileExists file -> do
96 defineEarlyCutoff $ \GetFileExists file -> do
66 alwaysRerun
97 alwaysRerun
67 res <- liftIO $ handle (\(_ :: IOException) -> return False) $
98 res <- liftIO $ handle (\(_ :: IOException) -> return False) $
68− (Map.member file <$> readVar dirty) ||^
99+ (isJust <$> getVirtualFile vfs (filePathToUri file)) ||^
69 Dir.doesFileExist file
100 Dir.doesFileExist file
70 return (Just $ if res then BS.singleton '1' else BS.empty, ([], Just res))
101 return (Just $ if res then BS.singleton '1' else BS.empty, ([], Just res))
@@ -73,36 +104,36 @@ getFileExistsRule dirty =
73showTimePrecise :: UTCTime -> String
104showTimePrecise :: UTCTime -> String
74showTimePrecise UTCTime{..} = show (toModifiedJulianDay utctDay, diffTimeToPicoseconds utctDayTime)
105showTimePrecise UTCTime{..} = show (toModifiedJulianDay utctDay, diffTimeToPicoseconds utctDayTime)
76−getModificationTimeRule :: Var DirtyFiles -> Rules ()
107+getModificationTimeRule :: VFSHandle -> Rules ()
77−getModificationTimeRule dirty =
108+getModificationTimeRule vfs =
78 defineEarlyCutoff $ \GetModificationTime file -> do
109 defineEarlyCutoff $ \GetModificationTime file -> do
79− let wrap time = (Just $ BS.pack $ showTimePrecise time, ([], Just time))
110+ let wrap time = (Just $ BS.pack $ showTimePrecise time, ([], Just $ ModificationTime time))
80 alwaysRerun
111 alwaysRerun
81− mp <- liftIO $ readVar dirty
112+ mbVirtual <- liftIO $ getVirtualFile vfs $ filePathToUri file
82− case Map.lookup file mp of
113+ case mbVirtual of
83− Just (time, _) -> return $ wrap time
114+ Just (VirtualFile ver _ _) -> pure (Just $ BS.pack $ show ver, ([], Just $ VFSVersion ver))
84 Nothing -> liftIO $ fmap wrap (Dir.getModificationTime file) `catch` \(e :: IOException) -> do
115 Nothing -> liftIO $ fmap wrap (Dir.getModificationTime file) `catch` \(e :: IOException) -> do
85 let err | isDoesNotExistError e = "File does not exist: " ++ file
116 let err | isDoesNotExistError e = "File does not exist: " ++ file
86 | otherwise = "IO error while reading " ++ file ++ ", " ++ displayException e
117 | otherwise = "IO error while reading " ++ file ++ ", " ++ displayException e
87 return (Nothing, ([ideErrorText file $ T.pack err], Nothing))
118 return (Nothing, ([ideErrorText file $ T.pack err], Nothing))
90−getFileContentsRule :: Var DirtyFiles -> Rules ()
121+getFileContentsRule :: VFSHandle -> Rules ()
91−getFileContentsRule dirty =
122+getFileContentsRule vfs =
92 define $ \GetFileContents file -> do
123 define $ \GetFileContents file -> do
93 -- need to depend on modification time to introduce a dependency with Cutoff
124 -- need to depend on modification time to introduce a dependency with Cutoff
94 time <- use_ GetModificationTime file
125 time <- use_ GetModificationTime file
95 res <- liftIO $ ideTryIOException file $ do
126 res <- liftIO $ ideTryIOException file $ do
96− mp <- readVar dirty
127+ mbVirtual <- getVirtualFile vfs $ filePathToUri file
97− case Map.lookup file mp of
128+ case mbVirtual of
98− Just (_, contents) -> return contents
129+ Just (VirtualFile _ rope _) -> return $ textToStringBuffer $ Rope.toText rope
99 Nothing -> hGetStringBuffer file
130 Nothing -> hGetStringBuffer file
100 case res of
131 case res of
101 Left err -> return ([err], Nothing)
132 Left err -> return ([err], Nothing)
102 Right contents -> return ([], Just (time, contents))
133 Right contents -> return ([], Just (time, contents))
105−getFileContents :: FilePath -> Action (UTCTime, StringBuffer)
136+getFileContents :: FilePath -> Action (FileVersion, StringBuffer)
106getFileContents = use_ GetFileContents
137getFileContents = use_ GetFileContents
108getFileExists :: FilePath -> Action Bool
139getFileExists :: FilePath -> Action Bool
@@ -113,29 +144,21 @@ getFileExists =
113 use_ GetFileExists
144 use_ GetFileExists
116−fileStoreRules :: Rules ()
147+fileStoreRules :: VFSHandle -> Rules ()
117−fileStoreRules = do
148+fileStoreRules vfs = do
118− dirty <- liftIO $ newVar Map.empty
119− addIdeGlobal $ GlobalDirtyFiles dirty
149+ addIdeGlobal vfs
120− getModificationTimeRule dirty
150+ getModificationTimeRule vfs
121− getFileContentsRule dirty
151+ getFileContentsRule vfs
122− getFileExistsRule dirty
152+ getFileExistsRule vfs
125−strictPair :: a -> b -> (a, b)
126−strictPair !a !b = (a,b)
129-- | Notify the compiler service of a modified buffer
155-- | Notify the compiler service of a modified buffer
130−setBufferModified :: IdeState -> FilePath -> (Maybe T.Text, UTCTime) -> IO ()
156+setBufferModified :: IdeState -> FilePath -> Maybe T.Text -> IO ()
131−setBufferModified state absFile (mcontents, !time) = do
157+setBufferModified state absFile mbContents = do
132− GlobalDirtyFiles envDirtyFiles <- getIdeGlobalState state
158+ VFSHandle{..} <- getIdeGlobalState state
133− -- update vars synchronously
159+ case mbContents of
134− modifyVar_ envDirtyFiles $ evaluate . case mcontents of
135− Nothing -> Map.delete absFile
160+ Nothing -> removeVirtualFile (filePathToUri absFile)
136− Just contents -> Map.insert absFile $ strictPair time $ textToStringBuffer contents
161+ Just contents -> setVirtualFileContents (filePathToUri absFile) contents
138− -- run shake to update results regarding the files of interest
139 void $ shakeRun state []
162 void $ shakeRun state []