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
@@ -22,21 +22,23 @@ import Data.HashMap.Strict (HashMap)
22
22
import qualified Data.HashMap.Strict as HashMap
23
23
import qualified Data.List.NonEmpty as NE
24
24
import qualified Data.Text.Encoding as Encoding
25
+ import qualified Data.Text.Utf16.Rope as Rope
25
26
import Data.Typeable
26
27
import Development.IDE as D
27
28
import Development.IDE.Core.Shake (restartShakeSession )
28
29
import qualified Development.IDE.Core.Shake as Shake
29
30
import Development.IDE.Graph (alwaysRerun )
31
+ import Distribution.Compat.Lens ((^.) )
30
32
import GHC.Generics
33
+ import qualified Ide.Plugin.Cabal.Completions as Completions
31
34
import qualified Ide.Plugin.Cabal.Diagnostics as Diagnostics
32
35
import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest
33
36
import qualified Ide.Plugin.Cabal.Parse as Parse
34
- import Ide.Plugin.Config (Config )
35
37
import Ide.Types
38
+ import qualified Language.LSP.Protocol.Lens as JL
36
39
import qualified Language.LSP.Protocol.Message as LSP
37
40
import Language.LSP.Protocol.Types
38
- import qualified Language.LSP.Protocol.Types as LSP
39
- import Language.LSP.Server (LspM )
41
+ import Language.LSP.Server (LspM , getVirtualFile )
40
42
import qualified Language.LSP.VFS as VFS
41
43
42
44
data Log
@@ -47,12 +49,14 @@ data Log
47
49
| LogDocSaved Uri
48
50
| LogDocClosed Uri
49
51
| LogFOI (HashMap NormalizedFilePath FileOfInterestStatus )
50
- deriving Show
52
+ | LogCompletionContext Completions. Context Position
53
+ | LogCompletions Completions. Log
54
+ deriving (Show )
51
55
52
56
instance Pretty Log where
53
57
pretty = \ case
54
58
LogShake log' -> pretty log'
55
- LogModificationTime nfp modTime ->
59
+ LogModificationTime nfp modTime ->
56
60
" Modified:" <+> pretty (fromNormalizedFilePath nfp) <+> pretty (show modTime)
57
61
LogDocOpened uri ->
58
62
" Opened text document:" <+> pretty (getUri uri)
@@ -64,12 +68,18 @@ instance Pretty Log where
64
68
" Closed text document:" <+> pretty (getUri uri)
65
69
LogFOI files ->
66
70
" Set files of interest to:" <+> viaShow files
67
-
71
+ LogCompletionContext context position->
72
+ " Determined completion context:" <+> viaShow context
73
+ <+> " for cursor position:" <+> viaShow position
74
+ LogCompletions logs -> pretty logs
68
75
69
76
descriptor :: Recorder (WithPriority Log ) -> PluginId -> PluginDescriptor IdeState
70
77
descriptor recorder plId = (defaultCabalPluginDescriptor plId)
71
78
{ pluginRules = cabalRules recorder
72
- , pluginHandlers = mkPluginHandler LSP. SMethod_TextDocumentCodeAction licenseSuggestCodeAction
79
+ , pluginHandlers = mconcat
80
+ [ mkPluginHandler LSP. SMethod_TextDocumentCodeAction licenseSuggestCodeAction
81
+ , mkPluginHandler LSP. SMethod_TextDocumentCompletion $ completion recorder
82
+ ]
73
83
, pluginNotificationHandlers = mconcat
74
84
[ mkPluginNotificationHandler LSP. SMethod_TextDocumentDidOpen $
75
85
\ ide vfs _ (DidOpenTextDocumentParams TextDocumentItem {_uri,_version}) -> liftIO $ do
@@ -104,7 +114,7 @@ descriptor recorder plId = (defaultCabalPluginDescriptor plId)
104
114
log' = logWith recorder
105
115
106
116
whenUriFile :: Uri -> (NormalizedFilePath -> IO () ) -> IO ()
107
- whenUriFile uri act = whenJust (LSP. uriToFilePath uri) $ act . toNormalizedFilePath'
117
+ whenUriFile uri act = whenJust (uriToFilePath uri) $ act . toNormalizedFilePath'
108
118
109
119
-- | Helper function to restart the shake session, specifically for modifying .cabal files.
110
120
-- No special logic, just group up a bunch of functions you need for the base
@@ -124,9 +134,9 @@ restartCabalShakeSession shakeExtras vfs file actionMsg = do
124
134
-- ----------------------------------------------------------------
125
135
126
136
data ParseCabal = ParseCabal
127
- deriving (Eq , Show , Typeable , Generic )
137
+ deriving (Eq , Show , Typeable , Generic )
128
138
instance Hashable ParseCabal
129
- instance NFData ParseCabal
139
+ instance NFData ParseCabal
130
140
131
141
type instance RuleResult ParseCabal = ()
132
142
@@ -141,7 +151,8 @@ cabalRules recorder = do
141
151
(t, mCabalSource) <- use_ GetFileContents file
142
152
log' Debug $ LogModificationTime file t
143
153
contents <- case mCabalSource of
144
- Just sources -> pure $ Encoding. encodeUtf8 sources
154
+ Just sources ->
155
+ pure $ Encoding. encodeUtf8 sources
145
156
Nothing -> do
146
157
liftIO $ BS. readFile $ fromNormalizedFilePath file
147
158
@@ -160,15 +171,16 @@ cabalRules recorder = do
160
171
-- Must be careful to not impede the performance too much. Crucial to
161
172
-- a snappy IDE experience.
162
173
kick
163
- where
164
- log' = logWith recorder
174
+ where
175
+ log' = logWith recorder
165
176
166
- -- | This is the kick function for the cabal plugin.
167
- -- We run this action, whenever we shake session us run/restarted, which triggers
168
- -- actions to produce diagnostics for cabal files.
169
- --
170
- -- It is paramount that this kick-function can be run quickly, since it is a blocking
171
- -- function invocation.
177
+ {- | This is the kick function for the cabal plugin.
178
+ We run this action, whenever we shake session us run/restarted, which triggers
179
+ actions to produce diagnostics for cabal files.
180
+
181
+ It is paramount that this kick-function can be run quickly, since it is a blocking
182
+ function invocation.
183
+ -}
172
184
kick :: Action ()
173
185
kick = do
174
186
files <- HashMap. keys <$> getCabalFilesOfInterestUntracked
@@ -190,69 +202,100 @@ licenseSuggestCodeAction _ _ (CodeActionParams _ _ (TextDocumentIdentifier uri)
190
202
-- Cabal file of Interest rules and global variable
191
203
-- ----------------------------------------------------------------
192
204
193
- -- | Cabal files that are currently open in the lsp-client.
194
- -- Specific actions happen when these files are saved, closed or modified,
195
- -- such as generating diagnostics, re-parsing, etc...
196
- --
197
- -- We need to store the open files to parse them again if we restart the shake session.
198
- -- Restarting of the shake session happens whenever these files are modified.
205
+ {- | Cabal files that are currently open in the lsp-client.
206
+ Specific actions happen when these files are saved, closed or modified,
207
+ such as generating diagnostics, re-parsing, etc...
208
+
209
+ We need to store the open files to parse them again if we restart the shake session.
210
+ Restarting of the shake session happens whenever these files are modified.
211
+ -}
199
212
newtype OfInterestCabalVar = OfInterestCabalVar (Var (HashMap NormalizedFilePath FileOfInterestStatus ))
200
213
201
214
instance Shake. IsIdeGlobal OfInterestCabalVar
202
215
203
216
data IsCabalFileOfInterest = IsCabalFileOfInterest
204
- deriving (Eq , Show , Typeable , Generic )
217
+ deriving (Eq , Show , Typeable , Generic )
205
218
instance Hashable IsCabalFileOfInterest
206
- instance NFData IsCabalFileOfInterest
219
+ instance NFData IsCabalFileOfInterest
207
220
208
221
type instance RuleResult IsCabalFileOfInterest = CabalFileOfInterestResult
209
222
210
223
data CabalFileOfInterestResult = NotCabalFOI | IsCabalFOI FileOfInterestStatus
211
224
deriving (Eq , Show , Typeable , Generic )
212
225
instance Hashable CabalFileOfInterestResult
213
- instance NFData CabalFileOfInterestResult
226
+ instance NFData CabalFileOfInterestResult
214
227
215
- -- | The rule that initialises the files of interest state.
216
- --
217
- -- Needs to be run on start-up.
228
+ {- | The rule that initialises the files of interest state.
229
+
230
+ Needs to be run on start-up.
231
+ -}
218
232
ofInterestRules :: Recorder (WithPriority Log ) -> Rules ()
219
233
ofInterestRules recorder = do
220
- Shake. addIdeGlobal . OfInterestCabalVar =<< liftIO (newVar HashMap. empty)
221
- Shake. defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics $ \ IsCabalFileOfInterest f -> do
222
- alwaysRerun
223
- filesOfInterest <- getCabalFilesOfInterestUntracked
224
- let foi = maybe NotCabalFOI IsCabalFOI $ f `HashMap.lookup` filesOfInterest
225
- fp = summarize foi
226
- res = (Just fp, Just foi)
227
- return res
228
- where
229
- summarize NotCabalFOI = BS. singleton 0
230
- summarize (IsCabalFOI OnDisk ) = BS. singleton 1
231
- summarize (IsCabalFOI (Modified False )) = BS. singleton 2
232
- summarize (IsCabalFOI (Modified True )) = BS. singleton 3
234
+ Shake. addIdeGlobal . OfInterestCabalVar =<< liftIO (newVar HashMap. empty)
235
+ Shake. defineEarlyCutoff (cmapWithPrio LogShake recorder) $ RuleNoDiagnostics $ \ IsCabalFileOfInterest f -> do
236
+ alwaysRerun
237
+ filesOfInterest <- getCabalFilesOfInterestUntracked
238
+ let foi = maybe NotCabalFOI IsCabalFOI $ f `HashMap.lookup` filesOfInterest
239
+ fp = summarize foi
240
+ res = (Just fp, Just foi)
241
+ return res
242
+ where
243
+ summarize NotCabalFOI = BS. singleton 0
244
+ summarize (IsCabalFOI OnDisk ) = BS. singleton 1
245
+ summarize (IsCabalFOI (Modified False )) = BS. singleton 2
246
+ summarize (IsCabalFOI (Modified True )) = BS. singleton 3
233
247
234
248
getCabalFilesOfInterestUntracked :: Action (HashMap NormalizedFilePath FileOfInterestStatus )
235
249
getCabalFilesOfInterestUntracked = do
236
- OfInterestCabalVar var <- Shake. getIdeGlobalAction
237
- liftIO $ readVar var
250
+ OfInterestCabalVar var <- Shake. getIdeGlobalAction
251
+ liftIO $ readVar var
238
252
239
253
addFileOfInterest :: Recorder (WithPriority Log ) -> IdeState -> NormalizedFilePath -> FileOfInterestStatus -> IO ()
240
254
addFileOfInterest recorder state f v = do
241
- OfInterestCabalVar var <- Shake. getIdeGlobalState state
242
- (prev, files) <- modifyVar var $ \ dict -> do
243
- let (prev, new) = HashMap. alterF (, Just v) f dict
244
- pure (new, (prev, new))
245
- when (prev /= Just v) $ do
246
- join $ atomically $ Shake. recordDirtyKeys (shakeExtras state) IsFileOfInterest [f]
247
- log' Debug $ LogFOI files
248
- where
249
- log' = logWith recorder
255
+ OfInterestCabalVar var <- Shake. getIdeGlobalState state
256
+ (prev, files) <- modifyVar var $ \ dict -> do
257
+ let (prev, new) = HashMap. alterF (,Just v) f dict
258
+ pure (new, (prev, new))
259
+ when (prev /= Just v) $ do
260
+ join $ atomically $ Shake. recordDirtyKeys (shakeExtras state) IsFileOfInterest [f]
261
+ log' Debug $ LogFOI files
262
+ where
263
+ log' = logWith recorder
250
264
251
265
deleteFileOfInterest :: Recorder (WithPriority Log ) -> IdeState -> NormalizedFilePath -> IO ()
252
266
deleteFileOfInterest recorder state f = do
253
- OfInterestCabalVar var <- Shake. getIdeGlobalState state
254
- files <- modifyVar' var $ HashMap. delete f
255
- join $ atomically $ Shake. recordDirtyKeys (shakeExtras state) IsFileOfInterest [f]
256
- log' Debug $ LogFOI files
257
- where
258
- log' = logWith recorder
267
+ OfInterestCabalVar var <- Shake. getIdeGlobalState state
268
+ files <- modifyVar' var $ HashMap. delete f
269
+ join $ atomically $ Shake. recordDirtyKeys (shakeExtras state) IsFileOfInterest [f]
270
+ log' Debug $ LogFOI files
271
+ where
272
+ log' = logWith recorder
273
+
274
+ -- ----------------------------------------------------------------
275
+ -- Completion
276
+ -- ----------------------------------------------------------------
277
+
278
+ completion :: Recorder (WithPriority Log ) -> PluginMethodHandler IdeState 'LSP.Method_TextDocumentCompletion
279
+ completion recorder _ide _ complParams = do
280
+ let (TextDocumentIdentifier uri) = complParams ^. JL. textDocument
281
+ position = complParams ^. JL. position
282
+ contents <- getVirtualFile $ toNormalizedUri uri
283
+ fmap (Right . InL ) $ case (contents, uriToFilePath' uri) of
284
+ (Just cnts, Just path) -> do
285
+ pref <- VFS. getCompletionPrefix position cnts
286
+ liftIO $ result pref path cnts
287
+ _ -> return []
288
+ where
289
+ result :: Maybe VFS. PosPrefixInfo -> FilePath -> VFS. VirtualFile -> IO [CompletionItem ]
290
+ result Nothing _ _ = pure []
291
+ result (Just prefix) fp cnts
292
+ | Just ctx <- context = do
293
+ logWith recorder Debug $ LogCompletionContext ctx pos
294
+ let completer = Completions. contextToCompleter ctx
295
+ completions <- completer (cmapWithPrio LogCompletions recorder) completionContext
296
+ pure $ Completions. mkCompletionItems completions
297
+ | otherwise = pure []
298
+ where
299
+ pos = VFS. cursorPos prefix
300
+ context = Completions. getContext completionContext (Rope. lines $ cnts ^. VFS. file_text)
301
+ completionContext = Completions. getCabalCompletionContext fp prefix
0 commit comments