@@ -19,7 +19,9 @@ module Development.IDE.Core.FileStore(
1919 getModificationTimeImpl ,
2020 addIdeGlobal ,
2121 getFileContentsImpl ,
22- getModTime
22+ getModTime ,
23+ isWatchSupported ,
24+ registerFileWatches
2325 ) where
2426
2527import Control.Concurrent.STM (atomically )
@@ -48,7 +50,8 @@ import Development.IDE.Types.Location
4850import Development.IDE.Types.Options
4951import Development.IDE.Types.Shake (SomeShakeValue )
5052import HieDb.Create (deleteMissingRealFiles )
51- import Ide.Plugin.Config (CheckParents (.. ))
53+ import Ide.Plugin.Config (CheckParents (.. ),
54+ Config )
5255import System.IO.Error
5356
5457#ifdef mingw32_HOST_OS
@@ -65,13 +68,20 @@ import qualified Data.ByteString.Lazy as LBS
6568import qualified Data.HashSet as HSet
6669import Data.IORef.Extra (atomicModifyIORef_ )
6770import Data.List (foldl' )
71+ import qualified Data.Text as Text
6872import Language.LSP.Server hiding
6973 (getVirtualFile )
7074import qualified Language.LSP.Server as LSP
71- import Language.LSP.Types (FileChangeType (FcChanged ),
75+ import Language.LSP.Types (DidChangeWatchedFilesRegistrationOptions (DidChangeWatchedFilesRegistrationOptions ),
76+ FileChangeType (FcChanged ),
7277 FileEvent (FileEvent ),
78+ FileSystemWatcher (.. ),
79+ WatchKind (.. ),
80+ _watchers ,
7381 toNormalizedFilePath ,
7482 uriToFilePath )
83+ import qualified Language.LSP.Types as LSP
84+ import qualified Language.LSP.Types.Capabilities as LSP
7585import Language.LSP.VFS
7686import System.FilePath
7787
@@ -96,46 +106,54 @@ makeLSPVFSHandle lspEnv = VFSHandle
96106 , setVirtualFileContents = Nothing
97107 }
98108
109+ addWatchedFileRule :: (NormalizedFilePath -> Action Bool ) -> Rules ()
110+ addWatchedFileRule isWatched = defineNoDiagnostics $ \ AddWatchedFile f -> do
111+ isAlreadyWatched <- isWatched f
112+ if isAlreadyWatched then pure (Just True ) else do
113+ ShakeExtras {lspEnv} <- getShakeExtras
114+ case lspEnv of
115+ Just env -> fmap Just $ liftIO $ LSP. runLspT env $
116+ registerFileWatches [fromNormalizedFilePath f]
117+ Nothing -> pure Nothing
99118
100- getModificationTimeRule :: VFSHandle -> ( NormalizedFilePath -> Action Bool ) -> Rules ()
101- getModificationTimeRule vfs isWatched = defineEarlyCutoff $ Rule $ \ (GetModificationTime_ missingFileDiags) file ->
102- getModificationTimeImpl vfs isWatched missingFileDiags file
119+ getModificationTimeRule :: VFSHandle -> Rules ()
120+ getModificationTimeRule vfs = defineEarlyCutoff $ Rule $ \ (GetModificationTime_ missingFileDiags) file ->
121+ getModificationTimeImpl vfs missingFileDiags file
103122
104123getModificationTimeImpl :: VFSHandle
105- -> (NormalizedFilePath -> Action Bool )
106124 -> Bool
107125 -> NormalizedFilePath
108126 -> Action
109127 (Maybe BS. ByteString , ([FileDiagnostic ], Maybe FileVersion ))
110- getModificationTimeImpl vfs isWatched missingFileDiags file = do
111- let file' = fromNormalizedFilePath file
112- let wrap time = (Just $ LBS. toStrict $ B. encode $ toRational time, ([] , Just $ ModificationTime time))
113- mbVirtual <- liftIO $ getVirtualFile vfs $ filePathToUri' file
114- case mbVirtual of
115- Just (virtualFileVersion -> ver) -> do
116- alwaysRerun
117- pure (Just $ LBS. toStrict $ B. encode ver, ([] , Just $ VFSVersion ver))
118- Nothing -> do
119- isWF <- isWatched file
120- if isWF
121- then -- the file is watched so we can rely on FileWatched notifications,
122- -- but also need a dependency on IsFileOfInterest to reinstall
123- -- alwaysRerun when the file becomes VFS
124- void (use_ IsFileOfInterest file)
125- else if isInterface file
126- then -- interface files are tracked specially using the closed world assumption
127- pure ()
128- else -- in all other cases we will need to freshly check the file system
129- alwaysRerun
128+ getModificationTimeImpl vfs missingFileDiags file = do
129+ let file' = fromNormalizedFilePath file
130+ let wrap time = (Just $ LBS. toStrict $ B. encode $ toRational time, ([] , Just $ ModificationTime time))
131+ mbVirtual <- liftIO $ getVirtualFile vfs $ filePathToUri' file
132+ case mbVirtual of
133+ Just (virtualFileVersion -> ver) -> do
134+ alwaysRerun
135+ pure (Just $ LBS. toStrict $ B. encode ver, ([] , Just $ VFSVersion ver))
136+ Nothing -> do
137+ isWF <- use_ AddWatchedFile file
138+ if isWF
139+ then -- the file is watched so we can rely on FileWatched notifications,
140+ -- but also need a dependency on IsFileOfInterest to reinstall
141+ -- alwaysRerun when the file becomes VFS
142+ void (use_ IsFileOfInterest file)
143+ else if isInterface file
144+ then -- interface files are tracked specially using the closed world assumption
145+ pure ()
146+ else -- in all other cases we will need to freshly check the file system
147+ alwaysRerun
130148
131- liftIO $ fmap wrap (getModTime file')
132- `catch` \ (e :: IOException ) -> do
133- let err | isDoesNotExistError e = " File does not exist: " ++ file'
134- | otherwise = " IO error while reading " ++ file' ++ " , " ++ displayException e
135- diag = ideErrorText file (T. pack err)
136- if isDoesNotExistError e && not missingFileDiags
137- then return (Nothing , ([] , Nothing ))
138- else return (Nothing , ([diag], Nothing ))
149+ liftIO $ fmap wrap (getModTime file')
150+ `catch` \ (e :: IOException ) -> do
151+ let err | isDoesNotExistError e = " File does not exist: " ++ file'
152+ | otherwise = " IO error while reading " ++ file' ++ " , " ++ displayException e
153+ diag = ideErrorText file (T. pack err)
154+ if isDoesNotExistError e && not missingFileDiags
155+ then return (Nothing , ([] , Nothing ))
156+ else return (Nothing , ([diag], Nothing ))
139157
140158-- | Interface files cannot be watched, since they live outside the workspace.
141159-- But interface files are private, in that only HLS writes them.
@@ -229,8 +247,9 @@ getFileContents f = do
229247fileStoreRules :: VFSHandle -> (NormalizedFilePath -> Action Bool ) -> Rules ()
230248fileStoreRules vfs isWatched = do
231249 addIdeGlobal vfs
232- getModificationTimeRule vfs isWatched
250+ getModificationTimeRule vfs
233251 getFileContentsRule vfs
252+ addWatchedFileRule isWatched
234253
235254-- | Note that some buffer for a specific file has been modified but not
236255-- with what changes.
@@ -283,3 +302,43 @@ setSomethingModified state keys = do
283302 atomicModifyIORef_ (dirtyKeys $ shakeExtras state) $ \ x ->
284303 foldl' (flip HSet. insert) x keys
285304 void $ restartShakeSession (shakeExtras state) []
305+
306+ registerFileWatches :: [String ] -> LSP. LspT Config IO Bool
307+ registerFileWatches globs = do
308+ watchSupported <- isWatchSupported
309+ if watchSupported
310+ then do
311+ let
312+ regParams = LSP. RegistrationParams (List [LSP. SomeRegistration registration])
313+ -- The registration ID is arbitrary and is only used in case we want to deregister (which we won't).
314+ -- We could also use something like a random UUID, as some other servers do, but this works for
315+ -- our purposes.
316+ registration = LSP. Registration " globalFileWatches"
317+ LSP. SWorkspaceDidChangeWatchedFiles
318+ regOptions
319+ regOptions =
320+ DidChangeWatchedFilesRegistrationOptions { _watchers = List watchers }
321+ -- See Note [File existence cache and LSP file watchers] for why this exists, and the choice of watch kind
322+ watchKind = WatchKind { _watchCreate = True , _watchChange = True , _watchDelete = True }
323+ -- See Note [Which files should we watch?] for an explanation of why the pattern is the way that it is
324+ -- The patterns will be something like "**/.hs", i.e. "any number of directory segments,
325+ -- followed by a file with an extension 'hs'.
326+ watcher glob = FileSystemWatcher { _globPattern = glob, _kind = Just watchKind }
327+ -- We use multiple watchers instead of one using '{}' because lsp-test doesn't
328+ -- support that: https://github.com/bubba/lsp-test/issues/77
329+ watchers = [ watcher (Text. pack glob) | glob <- globs ]
330+
331+ void $ LSP. sendRequest LSP. SClientRegisterCapability regParams (const $ pure () ) -- TODO handle response
332+ return True
333+ else return False
334+
335+ isWatchSupported :: LSP. LspT Config IO Bool
336+ isWatchSupported = do
337+ clientCapabilities <- LSP. getClientCapabilities
338+ pure $ case () of
339+ _ | LSP. ClientCapabilities {_workspace} <- clientCapabilities
340+ , Just LSP. WorkspaceClientCapabilities {_didChangeWatchedFiles} <- _workspace
341+ , Just LSP. DidChangeWatchedFilesClientCapabilities {_dynamicRegistration} <- _didChangeWatchedFiles
342+ , Just True <- _dynamicRegistration
343+ -> True
344+ | otherwise -> False
0 commit comments