-
-
Notifications
You must be signed in to change notification settings - Fork 437
Expand file tree
/
Copy pathNotifications.hs
More file actions
170 lines (152 loc) · 8.69 KB
/
Notifications.hs
File metadata and controls
170 lines (152 loc) · 8.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
-- Copyright (c) 2019 The DAML Authors. All rights reserved.
-- SPDX-License-Identifier: Apache-2.0
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GADTs #-}
module Development.IDE.LSP.Notifications
( whenUriFile
, descriptor
, Log(..)
, ghcideNotificationsPluginPriority
) where
import qualified Language.LSP.Protocol.Message as LSP
import Language.LSP.Protocol.Types
import qualified Language.LSP.Protocol.Types as LSP
import Control.Concurrent.STM.Stats (atomically)
import Control.Monad.Extra
import Control.Monad.IO.Class
import qualified Data.HashMap.Strict as HM
import qualified Data.HashSet as S
import qualified Data.Text as Text
import Development.IDE.Core.FileExists (modifyFileExists,
watchedGlobs)
import Development.IDE.Core.FileStore (registerFileWatches,
resetFileStore,
setFileModified,
setSomethingModified)
import qualified Development.IDE.Core.FileStore as FileStore
import Development.IDE.Core.IdeConfiguration
import Development.IDE.Core.OfInterest hiding (Log, LogShake)
import Development.IDE.Core.Service hiding (Log, LogShake)
import Development.IDE.Core.Shake hiding (Log)
import qualified Development.IDE.Core.Shake as Shake
import qualified Development.IDE.Types.Shake as Shake
import Development.IDE.Types.Location
import Ide.Logger
import Ide.Types
import Numeric.Natural
import Development.IDE.Core.RuleTypes (GhcSessionIO(..))
data Log
= LogShake Shake.Log
| LogFileStore FileStore.Log
| LogOpenedTextDocument !Uri
| LogModifiedTextDocument !Uri
| LogSavedTextDocument !Uri
| LogClosedTextDocument !Uri
| LogWatchedFileEvents !Text.Text
| LogSessionRestart
| LogWarnNoWatchedFilesSupport
deriving Show
instance Pretty Log where
pretty = \case
LogShake msg -> pretty msg
LogFileStore msg -> pretty msg
LogOpenedTextDocument uri -> "Opened text document:" <+> pretty (getUri uri)
LogModifiedTextDocument uri -> "Modified text document:" <+> pretty (getUri uri)
LogSavedTextDocument uri -> "Saved text document:" <+> pretty (getUri uri)
LogClosedTextDocument uri -> "Closed text document:" <+> pretty (getUri uri)
LogWatchedFileEvents msg -> "Watched file events:" <+> pretty msg
LogWarnNoWatchedFilesSupport -> "Client does not support watched files. Falling back to OS polling"
LogSessionRestart -> "Restarting shake session globally"
whenUriFile :: Uri -> (NormalizedFilePath -> IO ()) -> IO ()
whenUriFile uri act = whenJust (LSP.uriToFilePath uri) $ act . toNormalizedFilePath'
descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState
descriptor recorder plId = (defaultPluginDescriptor plId desc) { pluginNotificationHandlers = mconcat
[ mkPluginNotificationHandler LSP.SMethod_TextDocumentDidOpen $
\ide vfs _ (DidOpenTextDocumentParams TextDocumentItem{_uri,_version}) -> liftIO $ do
atomically $ updatePositionMapping ide (VersionedTextDocumentIdentifier _uri _version) []
whenUriFile _uri $ \file -> do
-- We don't know if the file actually exists, or if the contents match those on disk
-- For example, vscode restores previously unsaved contents on open
setFileModified (cmapWithPrio LogFileStore recorder) (VFSModified vfs) ide False file $
addFileOfInterest ide file Modified{firstOpen=True}
logWith recorder Debug $ LogOpenedTextDocument _uri
, mkPluginNotificationHandler LSP.SMethod_TextDocumentDidChange $
\ide vfs _ (DidChangeTextDocumentParams identifier@VersionedTextDocumentIdentifier{_uri} changes) -> liftIO $ do
atomically $ updatePositionMapping ide identifier changes
whenUriFile _uri $ \file -> do
setFileModified (cmapWithPrio LogFileStore recorder) (VFSModified vfs) ide False file $
addFileOfInterest ide file Modified{firstOpen=False}
logWith recorder Debug $ LogModifiedTextDocument _uri
, mkPluginNotificationHandler LSP.SMethod_TextDocumentDidSave $
\ide vfs _ (DidSaveTextDocumentParams TextDocumentIdentifier{_uri} _) -> liftIO $ do
whenUriFile _uri $ \file -> do
setFileModified (cmapWithPrio LogFileStore recorder) (VFSModified vfs) ide True file $
addFileOfInterest ide file OnDisk
logWith recorder Debug $ LogSavedTextDocument _uri
, mkPluginNotificationHandler LSP.SMethod_TextDocumentDidClose $
\ide vfs _ (DidCloseTextDocumentParams TextDocumentIdentifier{_uri}) -> liftIO $ do
whenUriFile _uri $ \file -> do
let msg = "Closed text document: " <> getUri _uri
setSomethingModified (VFSModified vfs) ide (Text.unpack msg) $ do
scheduleGarbageCollection ide
deleteFileOfInterest ide file
logWith recorder Debug $ LogClosedTextDocument _uri
, mkPluginNotificationHandler LSP.SMethod_WorkspaceDidChangeWatchedFiles $
\ide vfs _ (DidChangeWatchedFilesParams fileEvents) -> liftIO $ do
-- See Note [File existence cache and LSP file watchers] which explains why we get these notifications and
-- what we do with them
-- filter out files of interest, since we already know all about those
-- filter also uris that do not map to filenames, since we cannot handle them
filesOfInterest <- getFilesOfInterest ide
let fileEvents' =
[ (nfp, event) | (FileEvent uri event) <- fileEvents
, Just fp <- [uriToFilePath uri]
, let nfp = toNormalizedFilePath fp
, not $ HM.member nfp filesOfInterest
]
unless (null fileEvents') $ do
let msg = show fileEvents'
logWith recorder Debug $ LogWatchedFileEvents (Text.pack msg)
setSomethingModified (VFSModified vfs) ide msg $ do
ks1 <- resetFileStore ide fileEvents'
ks2 <- modifyFileExists ide fileEvents'
return (ks1 <> ks2)
, mkPluginNotificationHandler LSP.SMethod_WorkspaceDidChangeWorkspaceFolders $
\ide _ _ (DidChangeWorkspaceFoldersParams events) -> liftIO $ do
let add = S.union
substract = flip S.difference
modifyWorkspaceFolders ide
$ add (foldMap (S.singleton . parseWorkspaceFolder) (_added events))
. substract (foldMap (S.singleton . parseWorkspaceFolder) (_removed events))
-- Nothing additional to do here beyond what `lsp` does for us, but this prevents
-- complaints about there being no handler defined
, mkPluginNotificationHandler LSP.SMethod_WorkspaceDidChangeConfiguration mempty
, mkPluginNotificationHandler LSP.SMethod_Initialized $ \ide _ _ _ -> do
--------- Initialize Shake session --------------------------------------------------------------------
liftIO $ shakeSessionInit (cmapWithPrio LogShake recorder) ide
--------- Set up file watchers ------------------------------------------------------------------------
opts <- liftIO $ getIdeOptionsIO $ shakeExtras ide
-- See Note [Which files should we watch?] for an explanation of why the pattern is the way that it is
-- The patterns will be something like "**/.hs", i.e. "any number of directory segments,
-- followed by a file with an extension 'hs'.
-- We use multiple watchers instead of one using '{}' because lsp-test doesn't
-- support that: https://github.com/bubba/lsp-test/issues/77
let globs = watchedGlobs opts
success <- registerFileWatches globs
unless success $
liftIO $ logWith recorder Warning LogWarnNoWatchedFilesSupport
, mkPluginNotificationHandler LSP.SMethod_WorkspaceDidRenameFiles $
\ide vfs _ _ -> liftIO $ do
logWith recorder Debug LogSessionRestart
Shake.shakeRestart (cmapWithPrio LogShake recorder) ide (VFSModified vfs) "" [] $ do
return [Shake.toNoFileKey GhcSessionIO]
pure ()
],
-- The ghcide descriptors should come last'ish so that the notification handlers
-- (which restart the Shake build) run after everything else
pluginPriority = ghcideNotificationsPluginPriority
}
where
desc = "Handles basic notifications for ghcide"
ghcideNotificationsPluginPriority :: Natural
ghcideNotificationsPluginPriority = defaultPluginPriority - 900