9
9
{-# LANGUAGE TupleSections #-}
10
10
{-# LANGUAGE TypeFamilies #-}
11
11
12
- module Ide.Plugin.Cabal (descriptor , Log (.. )) where
12
+ module Ide.Plugin.Cabal (descriptor , Log (.. )) where
13
13
14
14
import Control.Concurrent.STM
15
15
import Control.Concurrent.Strict
16
16
import Control.DeepSeq
17
17
import Control.Monad.Extra
18
18
import Control.Monad.IO.Class
19
- import qualified Data.ByteString as BS
19
+ import qualified Data.ByteString as BS
20
20
import Data.Hashable
21
- import Data.HashMap.Strict (HashMap )
22
- import qualified Data.HashMap.Strict as HashMap
23
- import qualified Data.List.NonEmpty as NE
24
- import qualified Data.Text as T
25
- import qualified Data.Text.Encoding as Encoding
26
- import qualified Data.Text.Utf16.Rope as Rope
21
+ import Data.HashMap.Strict (HashMap )
22
+ import qualified Data.HashMap.Strict as HashMap
23
+ import qualified Data.List.NonEmpty as NE
24
+ import qualified Data.Text.Encoding as Encoding
25
+ import qualified Data.Text.Utf16.Rope as Rope
27
26
import Data.Typeable
28
- import Development.IDE as D
29
- import Development.IDE.Core.Shake (restartShakeSession )
30
- import qualified Development.IDE.Core.Shake as Shake
31
- import Development.IDE.Graph (alwaysRerun )
32
- import Distribution.Compat.Lens ((^.) )
33
- import Distribution.Simple.PackageDescription (readGenericPackageDescription )
34
- import Distribution.Verbosity (silent )
27
+ import Development.IDE as D
28
+ import Development.IDE.Core.Shake (restartShakeSession )
29
+ import qualified Development.IDE.Core.Shake as Shake
30
+ import Development.IDE.Graph (alwaysRerun )
31
+ import Distribution.Compat.Lens ((^.) )
35
32
import GHC.Generics
36
33
import Ide.Plugin.Cabal.Completions
37
- import qualified Ide.Plugin.Cabal.Diagnostics as Diagnostics
38
- import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest
39
- import qualified Ide.Plugin.Cabal.Parse as Parse
34
+ import qualified Ide.Plugin.Cabal.Diagnostics as Diagnostics
35
+ import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest
36
+ import qualified Ide.Plugin.Cabal.Parse as Parse
40
37
import Ide.Types
41
- import qualified Language.LSP.Server as LSP
38
+ import qualified Language.LSP.Server as LSP
42
39
import Language.LSP.Types
43
- import qualified Language.LSP.Types as J
44
- import qualified Language.LSP.Types as LSP
45
- import qualified Language.LSP.Types.Lens as JL
46
- import Language.LSP.VFS (VirtualFile )
47
- import qualified Language.LSP.VFS as VFS
40
+ import qualified Language.LSP.Types as J
41
+ import qualified Language.LSP.Types as LSP
42
+ import qualified Language.LSP.Types.Lens as JL
43
+ import Language.LSP.VFS (VirtualFile )
44
+ import qualified Language.LSP.VFS as VFS
45
+
48
46
data Log
49
47
= LogModificationTime NormalizedFilePath FileVersion
50
48
| LogShake Shake. Log
@@ -53,12 +51,12 @@ data Log
53
51
| LogDocSaved Uri
54
52
| LogDocClosed Uri
55
53
| LogFOI (HashMap NormalizedFilePath FileOfInterestStatus )
56
- deriving Show
54
+ deriving ( Show )
57
55
58
56
instance Pretty Log where
59
57
pretty = \ case
60
58
LogShake log' -> pretty log'
61
- LogModificationTime nfp modTime ->
59
+ LogModificationTime nfp modTime ->
62
60
" Modified:" <+> pretty (fromNormalizedFilePath nfp) <+> pretty (show modTime)
63
61
LogDocOpened uri ->
64
62
" Opened text document:" <+> pretty (getUri uri)
@@ -71,56 +69,56 @@ instance Pretty Log where
71
69
LogFOI files ->
72
70
" Set files of interest to:" <+> viaShow files
73
71
74
-
75
72
descriptor :: Recorder (WithPriority Log ) -> PluginId -> PluginDescriptor IdeState
76
- descriptor recorder plId = (defaultCabalPluginDescriptor plId)
77
- { pluginRules = cabalRules recorder
78
- , pluginHandlers = mkPluginHandler STextDocumentCodeAction licenseSuggestCodeAction
79
- <> mkPluginHandler J. STextDocumentCompletion completion
80
- , pluginNotificationHandlers = mconcat
81
- [ mkPluginNotificationHandler LSP. STextDocumentDidOpen $
82
- \ ide vfs _ (DidOpenTextDocumentParams TextDocumentItem {_uri,_version}) -> liftIO $ do
83
- whenUriFile _uri $ \ file -> do
84
- log' Debug $ LogDocOpened _uri
85
- addFileOfInterest recorder ide file Modified {firstOpen= True }
86
- restartCabalShakeSession (shakeExtras ide) vfs file " (opened)"
87
-
88
- , mkPluginNotificationHandler LSP. STextDocumentDidChange $
89
- \ ide vfs _ (DidChangeTextDocumentParams VersionedTextDocumentIdentifier {_uri} _) -> liftIO $ do
90
- whenUriFile _uri $ \ file -> do
91
- log' Debug $ LogDocModified _uri
92
- addFileOfInterest recorder ide file Modified {firstOpen= False }
93
- restartCabalShakeSession (shakeExtras ide) vfs file " (changed)"
94
-
95
- , mkPluginNotificationHandler LSP. STextDocumentDidSave $
96
- \ ide vfs _ (DidSaveTextDocumentParams TextDocumentIdentifier {_uri} _) -> liftIO $ do
97
- whenUriFile _uri $ \ file -> do
98
- log' Debug $ LogDocSaved _uri
99
- addFileOfInterest recorder ide file OnDisk
100
- restartCabalShakeSession (shakeExtras ide) vfs file " (saved)"
101
-
102
- , mkPluginNotificationHandler LSP. STextDocumentDidClose $
103
- \ ide vfs _ (DidCloseTextDocumentParams TextDocumentIdentifier {_uri}) -> liftIO $ do
104
- whenUriFile _uri $ \ file -> do
105
- log' Debug $ LogDocClosed _uri
106
- deleteFileOfInterest recorder ide file
107
- restartCabalShakeSession (shakeExtras ide) vfs file " (closed)"
108
- ]
109
- }
110
- where
111
- log' = logWith recorder
112
-
113
- whenUriFile :: Uri -> (NormalizedFilePath -> IO () ) -> IO ()
114
- whenUriFile uri act = whenJust (LSP. uriToFilePath uri) $ act . toNormalizedFilePath'
115
-
116
- -- | Helper function to restart the shake session, specifically for modifying .cabal files.
117
- -- No special logic, just group up a bunch of functions you need for the base
118
- -- Notification Handlers.
119
- --
120
- -- To make sure diagnostics are up to date, we need to tell shake that the file was touched and
121
- -- needs to be re-parsed. That's what we do when we record the dirty key that our parsing
122
- -- rule depends on.
123
- -- Then we restart the shake session, so that changes to our virtual files are actually picked up.
73
+ descriptor recorder plId =
74
+ (defaultCabalPluginDescriptor plId)
75
+ { pluginRules = cabalRules recorder
76
+ , pluginHandlers =
77
+ mkPluginHandler STextDocumentCodeAction licenseSuggestCodeAction
78
+ <> mkPluginHandler J. STextDocumentCompletion completion
79
+ , pluginNotificationHandlers =
80
+ mconcat
81
+ [ mkPluginNotificationHandler LSP. STextDocumentDidOpen $
82
+ \ ide vfs _ (DidOpenTextDocumentParams TextDocumentItem {_uri, _version}) -> liftIO $ do
83
+ whenUriFile _uri $ \ file -> do
84
+ log' Debug $ LogDocOpened _uri
85
+ addFileOfInterest recorder ide file Modified {firstOpen = True }
86
+ restartCabalShakeSession (shakeExtras ide) vfs file " (opened)"
87
+ , mkPluginNotificationHandler LSP. STextDocumentDidChange $
88
+ \ ide vfs _ (DidChangeTextDocumentParams VersionedTextDocumentIdentifier {_uri} _) -> liftIO $ do
89
+ whenUriFile _uri $ \ file -> do
90
+ log' Debug $ LogDocModified _uri
91
+ addFileOfInterest recorder ide file Modified {firstOpen = False }
92
+ restartCabalShakeSession (shakeExtras ide) vfs file " (changed)"
93
+ , mkPluginNotificationHandler LSP. STextDocumentDidSave $
94
+ \ ide vfs _ (DidSaveTextDocumentParams TextDocumentIdentifier {_uri} _) -> liftIO $ do
95
+ whenUriFile _uri $ \ file -> do
96
+ log' Debug $ LogDocSaved _uri
97
+ addFileOfInterest recorder ide file OnDisk
98
+ restartCabalShakeSession (shakeExtras ide) vfs file " (saved)"
99
+ , mkPluginNotificationHandler LSP. STextDocumentDidClose $
100
+ \ ide vfs _ (DidCloseTextDocumentParams TextDocumentIdentifier {_uri}) -> liftIO $ do
101
+ whenUriFile _uri $ \ file -> do
102
+ log' Debug $ LogDocClosed _uri
103
+ deleteFileOfInterest recorder ide file
104
+ restartCabalShakeSession (shakeExtras ide) vfs file " (closed)"
105
+ ]
106
+ }
107
+ where
108
+ log' = logWith recorder
109
+
110
+ whenUriFile :: Uri -> (NormalizedFilePath -> IO () ) -> IO ()
111
+ whenUriFile uri act = whenJust (LSP. uriToFilePath uri) $ act . toNormalizedFilePath'
112
+
113
+ {- | Helper function to restart the shake session, specifically for modifying .cabal files.
114
+ No special logic, just group up a bunch of functions you need for the base
115
+ Notification Handlers.
116
+
117
+ To make sure diagnostics are up to date, we need to tell shake that the file was touched and
118
+ needs to be re-parsed. That's what we do when we record the dirty key that our parsing
119
+ rule depends on.
120
+ Then we restart the shake session, so that changes to our virtual files are actually picked up.
121
+ -}
124
122
restartCabalShakeSession :: ShakeExtras -> VFS. VFS -> NormalizedFilePath -> String -> IO ()
125
123
restartCabalShakeSession shakeExtras vfs file actionMsg = do
126
124
join $ atomically $ Shake. recordDirtyKeys shakeExtras GetModificationTime [file]
@@ -131,9 +129,9 @@ restartCabalShakeSession shakeExtras vfs file actionMsg = do
131
129
-- ----------------------------------------------------------------
132
130
133
131
data ParseCabal = ParseCabal
134
- deriving (Eq , Show , Typeable , Generic )
132
+ deriving (Eq , Show , Typeable , Generic )
135
133
instance Hashable ParseCabal
136
- instance NFData ParseCabal
134
+ instance NFData ParseCabal
137
135
138
136
type instance RuleResult ParseCabal = ()
139
137
@@ -168,15 +166,16 @@ cabalRules recorder = do
168
166
-- Must be careful to not impede the performance too much. Crucial to
169
167
-- a snappy IDE experience.
170
168
kick
171
- where
172
- log' = logWith recorder
173
-
174
- -- | This is the kick function for the cabal plugin.
175
- -- We run this action, whenever we shake session us run/restarted, which triggers
176
- -- actions to produce diagnostics for cabal files.
177
- --
178
- -- It is paramount that this kick-function can be run quickly, since it is a blocking
179
- -- function invocation.
169
+ where
170
+ log' = logWith recorder
171
+
172
+ {- | This is the kick function for the cabal plugin.
173
+ We run this action, whenever we shake session us run/restarted, which triggers
174
+ actions to produce diagnostics for cabal files.
175
+
176
+ It is paramount that this kick-function can be run quickly, since it is a blocking
177
+ function invocation.
178
+ -}
180
179
kick :: Action ()
181
180
kick = do
182
181
files <- HashMap. keys <$> getCabalFilesOfInterestUntracked
@@ -186,84 +185,86 @@ kick = do
186
185
-- Code Actions
187
186
-- ----------------------------------------------------------------
188
187
189
- licenseSuggestCodeAction
190
- :: IdeState
191
- -> PluginId
192
- -> CodeActionParams
193
- -> LSP. LspM Config (Either ResponseError (ResponseResult 'TextDocumentCodeAction))
194
- licenseSuggestCodeAction _ _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _range CodeActionContext {_diagnostics= List diags}) =
188
+ licenseSuggestCodeAction ::
189
+ IdeState ->
190
+ PluginId ->
191
+ CodeActionParams ->
192
+ LSP. LspM Config (Either ResponseError (ResponseResult 'TextDocumentCodeAction))
193
+ licenseSuggestCodeAction _ _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _range CodeActionContext {_diagnostics = List diags}) =
195
194
pure $ Right $ List $ diags >>= (fmap InR . (LicenseSuggest. licenseErrorAction uri))
196
195
197
196
-- ----------------------------------------------------------------
198
197
-- Cabal file of Interest rules and global variable
199
198
-- ----------------------------------------------------------------
200
199
201
- -- | Cabal files that are currently open in the lsp-client.
202
- -- Specific actions happen when these files are saved, closed or modified,
203
- -- such as generating diagnostics, re-parsing, etc...
204
- --
205
- -- We need to store the open files to parse them again if we restart the shake session.
206
- -- Restarting of the shake session happens whenever these files are modified.
200
+ {- | Cabal files that are currently open in the lsp-client.
201
+ Specific actions happen when these files are saved, closed or modified,
202
+ such as generating diagnostics, re-parsing, etc...
203
+
204
+ We need to store the open files to parse them again if we restart the shake session.
205
+ Restarting of the shake session happens whenever these files are modified.
206
+ -}
207
207
newtype OfInterestCabalVar = OfInterestCabalVar (Var (HashMap NormalizedFilePath FileOfInterestStatus ))
208
208
209
209
instance Shake. IsIdeGlobal OfInterestCabalVar
210
210
211
211
data IsCabalFileOfInterest = IsCabalFileOfInterest
212
- deriving (Eq , Show , Typeable , Generic )
212
+ deriving (Eq , Show , Typeable , Generic )
213
213
instance Hashable IsCabalFileOfInterest
214
- instance NFData IsCabalFileOfInterest
214
+ instance NFData IsCabalFileOfInterest
215
215
216
216
type instance RuleResult IsCabalFileOfInterest = CabalFileOfInterestResult
217
217
218
218
data CabalFileOfInterestResult = NotCabalFOI | IsCabalFOI FileOfInterestStatus
219
219
deriving (Eq , Show , Typeable , Generic )
220
220
instance Hashable CabalFileOfInterestResult
221
- instance NFData CabalFileOfInterestResult
221
+ instance NFData CabalFileOfInterestResult
222
222
223
- -- | The rule that initialises the files of interest state.
224
- --
225
- -- Needs to be run on start-up.
223
+ {- | The rule that initialises the files of interest state.
224
+
225
+ Needs to be run on start-up.
226
+ -}
226
227
ofInterestRules :: Recorder (WithPriority Log ) -> Rules ()
227
228
ofInterestRules recorder = do
228
- Shake. addIdeGlobal . OfInterestCabalVar =<< liftIO (newVar HashMap. empty)
229
- Shake. defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics $ \ IsCabalFileOfInterest f -> do
230
- alwaysRerun
231
- filesOfInterest <- getCabalFilesOfInterestUntracked
232
- let foi = maybe NotCabalFOI IsCabalFOI $ f `HashMap.lookup` filesOfInterest
233
- fp = summarize foi
234
- res = (Just fp, Just foi)
235
- return res
236
- where
237
- summarize NotCabalFOI = BS. singleton 0
238
- summarize (IsCabalFOI OnDisk ) = BS. singleton 1
239
- summarize (IsCabalFOI (Modified False )) = BS. singleton 2
240
- summarize (IsCabalFOI (Modified True )) = BS. singleton 3
229
+ Shake. addIdeGlobal . OfInterestCabalVar =<< liftIO (newVar HashMap. empty)
230
+ Shake. defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics $ \ IsCabalFileOfInterest f -> do
231
+ alwaysRerun
232
+ filesOfInterest <- getCabalFilesOfInterestUntracked
233
+ let foi = maybe NotCabalFOI IsCabalFOI $ f `HashMap.lookup` filesOfInterest
234
+ fp = summarize foi
235
+ res = (Just fp, Just foi)
236
+ return res
237
+ where
238
+ summarize NotCabalFOI = BS. singleton 0
239
+ summarize (IsCabalFOI OnDisk ) = BS. singleton 1
240
+ summarize (IsCabalFOI (Modified False )) = BS. singleton 2
241
+ summarize (IsCabalFOI (Modified True )) = BS. singleton 3
241
242
242
243
getCabalFilesOfInterestUntracked :: Action (HashMap NormalizedFilePath FileOfInterestStatus )
243
244
getCabalFilesOfInterestUntracked = do
244
- OfInterestCabalVar var <- Shake. getIdeGlobalAction
245
- liftIO $ readVar var
245
+ OfInterestCabalVar var <- Shake. getIdeGlobalAction
246
+ liftIO $ readVar var
246
247
247
248
addFileOfInterest :: Recorder (WithPriority Log ) -> IdeState -> NormalizedFilePath -> FileOfInterestStatus -> IO ()
248
249
addFileOfInterest recorder state f v = do
249
- OfInterestCabalVar var <- Shake. getIdeGlobalState state
250
- (prev, files) <- modifyVar var $ \ dict -> do
251
- let (prev, new) = HashMap. alterF (, Just v) f dict
252
- pure (new, (prev, new))
253
- when (prev /= Just v) $ do
254
- join $ atomically $ Shake. recordDirtyKeys (shakeExtras state) IsFileOfInterest [f]
255
- log' Debug $ LogFOI files
256
- where
257
- log' = logWith recorder
250
+ OfInterestCabalVar var <- Shake. getIdeGlobalState state
251
+ (prev, files) <- modifyVar var $ \ dict -> do
252
+ let (prev, new) = HashMap. alterF (,Just v) f dict
253
+ pure (new, (prev, new))
254
+ when (prev /= Just v) $ do
255
+ join $ atomically $ Shake. recordDirtyKeys (shakeExtras state) IsFileOfInterest [f]
256
+ log' Debug $ LogFOI files
257
+ where
258
+ log' = logWith recorder
258
259
259
260
deleteFileOfInterest :: Recorder (WithPriority Log ) -> IdeState -> NormalizedFilePath -> IO ()
260
261
deleteFileOfInterest recorder state f = do
261
- OfInterestCabalVar var <- Shake. getIdeGlobalState state
262
- files <- modifyVar' var $ HashMap. delete f
263
- join $ atomically $ Shake. recordDirtyKeys (shakeExtras state) IsFileOfInterest [f]
264
- log' Debug $ LogFOI files
265
- where
266
- log' = logWith recorder
262
+ OfInterestCabalVar var <- Shake. getIdeGlobalState state
263
+ files <- modifyVar' var $ HashMap. delete f
264
+ join $ atomically $ Shake. recordDirtyKeys (shakeExtras state) IsFileOfInterest [f]
265
+ log' Debug $ LogFOI files
266
+ where
267
+ log' = logWith recorder
267
268
268
269
-- ----------------------------------------------------------------
269
270
-- Completion
@@ -279,22 +280,17 @@ completion _ide _ complParams = do
279
280
pref <- VFS. getCompletionPrefix position cnts
280
281
liftIO $ result pref path cnts
281
282
_ -> return $ J. List []
282
- where
283
- result :: Maybe VFS. PosPrefixInfo -> FilePath -> VirtualFile -> IO (J. List CompletionItem )
284
- result Nothing _ _ = pure $ J. List []
285
- result (Just pfix) fp cnts
286
- | Just ctx <- context = do
283
+ where
284
+ result :: Maybe VFS. PosPrefixInfo -> FilePath -> VirtualFile -> IO (J. List CompletionItem )
285
+ result Nothing _ _ = pure $ J. List []
286
+ result (Just prefix) _fp cnts
287
+ | Just ctx <- context = do
287
288
let completer = contextToCompleter " " ctx
288
- completions <- completer filePathPfix
289
+ completions <- completer completionContext
289
290
-- genPkgDesc <- readGenericPackageDescription silent fp
290
- pure $ J. List $ makeCompletionItems editRange completions
291
- | otherwise = pure $ J. List []
292
- where
293
- (Position linePos charPos) = VFS. cursorPos pfix
294
- context = getContext (Position linePos charPos) (Rope. lines $ cnts ^. VFS. file_text)
295
- filePathPfix = getFilePathCursorPrefix pfix
296
- editRange =
297
- Range
298
- (Position linePos (fromIntegral charPos - fromIntegral (T. length filePathPfix)))
299
- (Position linePos charPos)
300
-
291
+ pure $ J. List $ makeCompletionItems completions
292
+ | otherwise = pure $ J. List []
293
+ where
294
+ (Position linePos charPos) = VFS. cursorPos prefix
295
+ context = getContext (Position linePos charPos) (Rope. lines $ cnts ^. VFS. file_text)
296
+ completionContext = getFilePathCompletionContext prefix
0 commit comments