Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions ghcide-test/exe/WatchedFileTests.hs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import Config (mkIdeTestFs,
import Control.Applicative.Combinators
import Control.Monad.IO.Class (liftIO)
import qualified Data.Aeson as A
import Data.List (nub)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import Development.IDE.Plugin.Test (WaitForIdeRuleResult (..))
Expand Down Expand Up @@ -51,6 +52,15 @@ tests = testGroup "watched files"
-- Expect 2 subscriptions: one for all .hs files and one for the hie.yaml cradle
liftIO $ length watchedFileRegs @?= 2

, testWithDummyPluginEmpty' "distinct registration ids" $ \sessionDir -> do
liftIO $ atomicFileWriteString (sessionDir </> "hie.yaml") "cradle: {direct: {arguments: [\"-isrc\", \"A\", \"WatchedFilesMissingModule\"]}}"
_doc <- createDoc "A.hs" "haskell" "{-#LANGUAGE NoImplicitPrelude #-}\nmodule A where\nimport WatchedFilesMissingModule"
setIgnoringRegistrationRequests False
ids <- getWatchedFilesRegistrationIdsUntil SMethod_TextDocumentPublishDiagnostics

liftIO $ length ids @?= 2
liftIO $ assertEqual "registration ids must be distinct" (nub ids) ids

-- TODO add a test for didChangeWorkspaceFolder
]
, testGroup "Changes"
Expand Down Expand Up @@ -190,6 +200,15 @@ tests = testGroup "watched files"
]
]

getWatchedFilesRegistrationIdsUntil :: forall m. SServerMethod m -> Session [T.Text]
getWatchedFilesRegistrationIdsUntil m = do
msgs <- manyTill (Just <$> message SMethod_ClientRegisterCapability <|> Nothing <$ anyMessage) (message m)
return
[ _id
| Just TRequestMessage{_params = RegistrationParams regs} <- msgs
, Registration _id "workspace/didChangeWatchedFiles" _ <- regs
]

getWatchedFilesSubscriptionsUntil :: forall m. SServerMethod m -> Session [DidChangeWatchedFilesRegistrationOptions]
getWatchedFilesSubscriptionsUntil m = do
msgs <- manyTill (Just <$> message SMethod_ClientRegisterCapability <|> Nothing <$ anyMessage) (message m)
Expand Down
19 changes: 15 additions & 4 deletions ghcide/src/Development/IDE/Core/FileStore.hs
Original file line number Diff line number Diff line change
Expand Up @@ -307,19 +307,30 @@ setSomethingModified vfs state reason actionBetweenSession = do
atomically $ writeTaskQueue (indexQueue $ hiedbWriter $ shakeExtras state) (\withHieDb -> withHieDb deleteMissingRealFiles)
void $ restartShakeSession (shakeExtras state) vfs reason [] actionBetweenSession

{- Note [Unique file watcher registration ids]
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
We send more than one 'client/registerCapability' for watched files: one for
the global source globs, plus one foreach cradle dependency that the globs
do not already cover, see 'addWatchedFileRule'. These are logically distinct
registrations, so they must not share an id.

LSP leaves the meaning of a repeated id undefined. emacs' eglot unregisters the
old watches before re-registering, while neovim and vscode accumulate. We derive
the id from the globs to keep them distinct and idempotent, while supporting
potentially de-registering in the future.
-}
registerFileWatches :: [String] -> LSP.LspT Config IO Bool
registerFileWatches globs = do
watchSupported <- isWatchSupported
if watchSupported
then do
let
regParams = LSP.RegistrationParams [toUntypedRegistration registration]
-- The registration ID is arbitrary and is only used in case we want to deregister (which we won't).
-- We could also use something like a random UUID, as some other servers do, but this works for
-- our purposes.
registration = LSP.TRegistration { _id ="globalFileWatches"
-- See Note [Unique file watcher registration ids]
registration = LSP.TRegistration { _id = registrationId
, _method = LSP.SMethod_WorkspaceDidChangeWatchedFiles
, _registerOptions = Just regOptions}
registrationId = "hls-file-watches:" <> Text.intercalate "," (map Text.pack globs)
regOptions =
DidChangeWatchedFilesRegistrationOptions { _watchers = watchers }
-- See Note [File existence cache and LSP file watchers] for why this exists, and the choice of watch kind
Expand Down
Loading