@@ -29,11 +29,20 @@ import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest
29
29
import qualified Ide.Plugin.Cabal.Parse as Parse
30
30
import Ide.Plugin.Config (Config )
31
31
import Ide.Types
32
- import Language.LSP.Server ( LspM )
32
+ import qualified Language.LSP.Server as LSP
33
33
import Language.LSP.Types
34
34
import qualified Language.LSP.Types as LSP
35
35
import qualified Language.LSP.VFS as VFS
36
-
36
+ import qualified Data.Text as T
37
+ import qualified Language.LSP.Types.Lens as JL
38
+ import qualified Language.LSP.Types as J
39
+ import Distribution.Compat.Lens ((^.) )
40
+ import qualified Text.Fuzzy.Parallel as Fuzzy
41
+ import Data.Map (Map )
42
+ import qualified Data.Map as Map
43
+ import Language.LSP.VFS (VirtualFile )
44
+ import qualified Data.Text.Utf16.Rope as Rope
45
+ import qualified Data.List as List
37
46
data Log
38
47
= LogModificationTime NormalizedFilePath (Maybe FileVersion )
39
48
| LogDiagnostics NormalizedFilePath [FileDiagnostic ]
@@ -63,7 +72,8 @@ instance Pretty Log where
63
72
descriptor :: Recorder (WithPriority Log ) -> PluginId -> PluginDescriptor IdeState
64
73
descriptor recorder plId = (defaultCabalPluginDescriptor plId)
65
74
{ pluginRules = cabalRules recorder
66
- , pluginHandlers = mkPluginHandler STextDocumentCodeAction licenseSuggestCodeAction
75
+ , pluginHandlers = mkPluginHandler STextDocumentCodeAction licenseSuggestCodeAction
76
+ <> mkPluginHandler J. STextDocumentCompletion completion
67
77
, pluginNotificationHandlers = mconcat
68
78
[ mkPluginNotificationHandler LSP. STextDocumentDidOpen $
69
79
\ ide vfs _ (DidOpenTextDocumentParams TextDocumentItem {_uri,_version}) -> liftIO $ do
@@ -149,6 +159,208 @@ licenseSuggestCodeAction
149
159
:: IdeState
150
160
-> PluginId
151
161
-> CodeActionParams
152
- -> LspM Config (Either ResponseError (ResponseResult 'TextDocumentCodeAction))
162
+ -> LSP. LspM Config (Either ResponseError (ResponseResult 'TextDocumentCodeAction))
153
163
licenseSuggestCodeAction _ _ (CodeActionParams _ _ (TextDocumentIdentifier uri) _range CodeActionContext {_diagnostics= List diags}) =
154
164
pure $ Right $ List $ mapMaybe (fmap InR . LicenseSuggest. licenseErrorAction uri) diags
165
+
166
+ -- ----------------------------------------------------------------
167
+ -- Completion
168
+ -- ----------------------------------------------------------------
169
+ completion :: PluginMethodHandler IdeState 'J.TextDocumentCompletion
170
+ completion _ide _ complParams = do
171
+ let (J. TextDocumentIdentifier uri) = complParams ^. JL. textDocument
172
+ position = complParams ^. JL. position
173
+ contents <- LSP. getVirtualFile $ toNormalizedUri uri
174
+ fmap (Right . J. InL ) $ case (contents, uriToFilePath' uri) of
175
+ (Just cnts, Just _path) -> do
176
+ pref <- VFS. getCompletionPrefix position cnts
177
+ return $ result pref cnts
178
+ _ -> return $ J. List []
179
+ where
180
+ result :: Maybe VFS. PosPrefixInfo -> VirtualFile -> J. List CompletionItem
181
+ result Nothing _ = J. List []
182
+ result (Just pfix) cnts
183
+ | (VFS. cursorPos pfix) ^. JL. line == 0 = J. List [buildCompletion cabalVersionKeyword]
184
+ | Stanza s <- findCurrentLevel (getPreviousLines pfix cnts) =
185
+ case (Map. lookup s stanzaKeywordMap) of
186
+ Nothing ->
187
+ J. List $
188
+ makeCompletionItems pfix topLevelKeywords
189
+ Just l -> J. List $ (makeCompletionItems pfix l) ++ (makeCompletionItems pfix $ Map. keys stanzaKeywordMap)
190
+ | otherwise =
191
+ J. List $
192
+ makeCompletionItems pfix topLevelKeywords
193
+ where
194
+ topLevelKeywords = cabalKeywords ++ Map. keys stanzaKeywordMap
195
+
196
+ -- | Takes info about the current cursor position and a set of possible keywords
197
+ -- and creates completion suggestions that fit the current input from the given list
198
+ makeCompletionItems :: VFS. PosPrefixInfo -> [T. Text ] -> [CompletionItem ]
199
+ makeCompletionItems pfix l =
200
+ map
201
+ (buildCompletion . Fuzzy. original)
202
+ (Fuzzy. simpleFilter 1000 10 (VFS. prefixText pfix) l)
203
+
204
+ -- | Parse the given set of lines (starting before current cursor position
205
+ -- up to the start of the file) to find the nearest stanza declaration,
206
+ -- if none is found we are in the top level
207
+ findCurrentLevel :: [T. Text ] -> Context
208
+ findCurrentLevel [] = TopLevel
209
+ findCurrentLevel (cur : xs)
210
+ | Just s <- stanza = Stanza s
211
+ | otherwise = findCurrentLevel xs
212
+ where
213
+ stanza = List. find (`T.isPrefixOf` cur) (Map. keys stanzaKeywordMap)
214
+
215
+ -- | Get all lines before the given cursor position in the given file
216
+ -- and reverse them since we want to traverse starting from our current position
217
+ getPreviousLines :: VFS. PosPrefixInfo -> VirtualFile -> [T. Text ]
218
+ getPreviousLines pos cont = reverse $ take (fromIntegral currentLine) allLines
219
+ where
220
+ allLines = Rope. lines $ cont ^. VFS. file_text
221
+ currentLine = (VFS. cursorPos pos) ^. JL. line
222
+
223
+
224
+ data Context
225
+ = TopLevel
226
+ -- ^ top level context in a cabal file such as 'author'
227
+ | Stanza T. Text
228
+ -- ^ nested context in a cabal file, such as 'library', which has nested keywords, specific to the stanza
229
+ deriving (Eq )
230
+
231
+ -- | Keyword for cabal version required to be the top line in a cabal file
232
+ cabalVersionKeyword :: T. Text
233
+ cabalVersionKeyword = " cabal-version:"
234
+
235
+ -- | Top level keywords of a cabal file
236
+ cabalKeywords :: [T. Text ]
237
+ cabalKeywords =
238
+ [
239
+ " name:" ,
240
+ " version:" ,
241
+ " build-type:" ,
242
+ " license:" ,
243
+ " license-file:" ,
244
+ " license-files:" ,
245
+ " copyright:" ,
246
+ " author:" ,
247
+ " maintainer:" ,
248
+ " stability:" ,
249
+ " homepage:" ,
250
+ " bug-reports:" ,
251
+ " package-url:" ,
252
+ " synopsis:" ,
253
+ " description:" ,
254
+ " category:" ,
255
+ " tested-with:" ,
256
+ " data-files:" ,
257
+ " data-dir:" ,
258
+ " data-dir:" ,
259
+ " extra-doc-files:" ,
260
+ " extra-tmp-files:"
261
+ ]
262
+
263
+ -- | Map, containing all stanzas in a cabal file as keys and lists of their possible nested keywords as values
264
+ stanzaKeywordMap :: Map T. Text [T. Text ]
265
+ stanzaKeywordMap = Map. fromList [(" library" , [
266
+ " exposed-modules:" ,
267
+ " virtual-modules:" ,
268
+ " exposed:" ,
269
+ " visibility:" ,
270
+ " reexported-modules:" ,
271
+ " signatures:"
272
+ ])]
273
+
274
+
275
+ -- TODO move out toplevel commands i.e. test-suite
276
+ -- cabalTestKeywords :: [T.Text]
277
+ -- cabalTestKeywords =
278
+ -- [
279
+ -- "test-suite",
280
+ -- "type:",
281
+ -- "main-is:",
282
+ -- "test-module:",
283
+ -- "benchmark",
284
+ -- "main-is:",
285
+ -- "foreign-library",
286
+ -- "type:",
287
+ -- "options:",
288
+ -- "mod-def-file:",
289
+ -- "lib-version-info:",
290
+ -- "lib-version-linux:",
291
+ -- "build-depends:",
292
+ -- "other-modules:",
293
+ -- "hs-source-dir:",
294
+ -- "hs-source-dirs:",
295
+ -- "default-extensions:",
296
+ -- "other-extensions:",
297
+ -- "default-language:",
298
+ -- "other-languages:",
299
+ -- "extensions:",
300
+ -- "build-tool-depends:",
301
+ -- "build-tools:",
302
+ -- "buildable:",
303
+ -- "ghc-options:",
304
+ -- "ghc-prof-options:",
305
+ -- "ghc-shared-options:",
306
+ -- "ghcjs-options:",
307
+ -- "ghcjs-prof-options:",
308
+ -- "ghcjs-shared-options:",
309
+ -- "includes:",
310
+ -- "install-includes:",
311
+ -- ("include-dirs:", "directory list"),
312
+ -- ("c-sources:", "filename list"),
313
+ -- ("cxx-sources:", "filename list"),
314
+ -- ("asm-sources:", "filename list"),
315
+ -- ("cmm-sources:", "filename list"),
316
+ -- ("js-sources:", "filename list"),
317
+ -- ("extra-libraries:", "token list"),
318
+ -- ("extra-libraries-static:", "token list"),
319
+ -- ("extra-ghci-libraries:", "token list"),
320
+ -- ("extra-bundled-libraries:", "token list"),
321
+ -- ("extra-lib-dirs:", "directory list")
322
+ -- ("extra-lib-dirs-static:", "directory list"),
323
+ -- ("extra-library-flavours:", "notsure"),
324
+ -- ("extra-dynamic-library-flavours:", "notsure"),
325
+ -- ("cc-options:", "token list"),
326
+ -- ("cpp-options:", "token list"),
327
+ -- ("cxx-options:", "token list"),
328
+ -- ("cmm-options:", "token list"),
329
+ -- ("asm-options:", "token list"),
330
+ -- ("ld-options:", "token list"),
331
+ -- ("hsc2hs-options:", "token list"),
332
+ -- ("pkgconfig-depends:", "package list"),
333
+ -- ("frameworks:", "token list"),
334
+ -- ("extra-framework-dirs:", "directory list"),
335
+ -- ("mixins:", "mixin list")
336
+ -- ]
337
+
338
+ -- cabalFlagKeywords :: [(T.Text, T.Text)]
339
+ -- cabalFlagKeywords =
340
+ -- [
341
+ -- ("flag", "name"),
342
+ -- ("description:", "freeform"),
343
+ -- ("default:", "boolean"),
344
+ -- ("manual:", "boolean")
345
+ -- ]
346
+
347
+ -- cabalStanzaKeywords :: [(T.Text, T.Text)]
348
+ -- cabalStanzaKeywords =
349
+ -- [
350
+ -- ("common", "name"),
351
+ -- ("import:", "token-list")
352
+ -- ]
353
+
354
+ -- cabalSourceRepoKeywords :: [(T.Text, T.Text)]
355
+ -- cabalSourceRepoKeywords =
356
+ -- [
357
+ -- ("source-repository", ""),
358
+ -- ("type:", "token"),
359
+ -- ("location:", "URL")
360
+ -- ]
361
+
362
+ buildCompletion :: T. Text -> J. CompletionItem
363
+ buildCompletion label =
364
+ J. CompletionItem label (Just J. CiKeyword ) Nothing Nothing
365
+ Nothing Nothing Nothing Nothing Nothing Nothing Nothing
366
+ Nothing Nothing Nothing Nothing Nothing Nothing
0 commit comments