Skip to content

Commit 78a3168

Browse files
Jana ChadtVeryMilkyJoe
Jana Chadt
authored andcommitted
Implement completion of possible keywords for field values in cabal files
1 parent b4da571 commit 78a3168

File tree

1 file changed

+111
-61
lines changed
  • plugins/hls-cabal-plugin/src/Ide/Plugin

1 file changed

+111
-61
lines changed

plugins/hls-cabal-plugin/src/Ide/Plugin/Cabal.hs

Lines changed: 111 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ import Data.Hashable
2121
import Data.HashMap.Strict (HashMap)
2222
import qualified Data.HashMap.Strict as HashMap
2323
import qualified Data.List as List
24+
import qualified Data.List.Extra as Extra
2425
import qualified Data.List.NonEmpty as NE
2526
import Data.Map (Map)
2627
import qualified Data.Map as Map
2728
import qualified Data.Text as T
2829
import qualified Data.Text.Encoding as Encoding
2930
import qualified Data.Text.Utf16.Rope as Rope
3031
import Data.Typeable
32+
import Debug.Trace
3133
import Development.IDE as D
3234
import Development.IDE.Core.Shake (restartShakeSession)
3335
import qualified Development.IDE.Core.Shake as Shake
@@ -47,6 +49,7 @@ import qualified Language.LSP.Types.Lens as JL
4749
import Language.LSP.VFS (VirtualFile)
4850
import qualified Language.LSP.VFS as VFS
4951
import qualified Text.Fuzzy.Parallel as Fuzzy
52+
5053
data Log
5154
= LogModificationTime NormalizedFilePath FileVersion
5255
| LogShake Shake.Log
@@ -284,18 +287,57 @@ completion _ide _ complParams = do
284287
result :: Maybe VFS.PosPrefixInfo -> VirtualFile -> J.List CompletionItem
285288
result Nothing _ = J.List []
286289
result (Just pfix) cnts
287-
| (VFS.cursorPos pfix) ^. JL.line == 0 = J.List [buildCompletion cabalVersionKeyword]
288-
| Stanza s <- findCurrentLevel (getPreviousLines pfix cnts) =
289-
case (Map.lookup s stanzaKeywordMap) of
290-
Nothing ->
291-
J.List $
292-
makeCompletionItems pfix topLevelKeywords
293-
Just l -> J.List $ (makeCompletionItems pfix l) ++ (makeCompletionItems pfix $ Map.keys stanzaKeywordMap)
294-
| otherwise =
295-
J.List $
296-
makeCompletionItems pfix topLevelKeywords
297-
where
298-
topLevelKeywords = cabalKeywords ++ Map.keys stanzaKeywordMap
290+
| pos ^. JL.line == 0 = J.List [buildCompletion (fst cabalVersionKeyword)]
291+
| Just ctx@(Stanza s kwContext) <- context =
292+
case (Map.lookup s stanzaKeywordMap) of
293+
Nothing ->
294+
case kwContext of
295+
None ->
296+
J.List $
297+
makeCompletionItems pfix topLevelKeywords
298+
KeyWord kw -> J.List $ makeCompletionItems pfix (getPossibleValuesForKeyWord kw ctx)
299+
Just m ->
300+
case kwContext of
301+
None -> J.List $ (makeCompletionItems pfix (Map.keys m)) ++ (makeCompletionItems pfix $ Map.keys stanzaKeywordMap)
302+
KeyWord kw -> J.List $ makeCompletionItems pfix (getPossibleValuesForKeyWord kw ctx)
303+
| Just ctx@(TopLevel kwContext) <- context =
304+
case kwContext of
305+
None -> J.List $ makeCompletionItems pfix topLevelKeywords
306+
KeyWord kw -> J.List $ makeCompletionItems pfix (getPossibleValuesForKeyWord kw ctx)
307+
| otherwise = J.List []
308+
where
309+
pos = VFS.cursorPos pfix
310+
topLevelKeywords = Map.keys cabalKeywords ++ Map.keys stanzaKeywordMap
311+
context = findCurrentContext pos (cnts ^. VFS.file_text)
312+
313+
getPossibleValuesForKeyWord :: T.Text -> Context -> [T.Text]
314+
getPossibleValuesForKeyWord kw (TopLevel _) =
315+
case Map.lookup kw cabalKeywords of
316+
Nothing -> []
317+
Just l -> l
318+
getPossibleValuesForKeyWord kw (Stanza s _) =
319+
case Map.lookup s stanzaKeywordMap of
320+
Nothing -> []
321+
Just m -> case Map.lookup kw m of
322+
Nothing -> []
323+
Just l -> l
324+
325+
findCurrentContext :: Position -> Rope.Rope -> Maybe Context
326+
findCurrentContext pos rope =
327+
case outerContext of
328+
TopLevel _ -> TopLevel <$> getKeyWordContext cabalKeywords
329+
Stanza s _ ->
330+
case traceShowId (Map.lookup (traceShowId s) stanzaKeywordMap) of
331+
Nothing -> pure $ Stanza s None
332+
Just m -> traceShowId $ Stanza s <$> getKeyWordContext m
333+
where
334+
outerContext = findCurrentLevel (getPreviousLines pos rope) None
335+
currentLine = traceShowId $ (Rope.lines rope) Extra.!? (fromIntegral $ pos ^. JL.line)
336+
getKeyWordContext keywords = do
337+
curLine <- fmap T.stripStart currentLine
338+
case List.find (\kw -> traceShowId kw `T.isPrefixOf` curLine) (traceShowId $ Map.keys keywords) of
339+
Nothing -> Just None
340+
Just kw -> Just $ KeyWord kw
299341

300342
-- | Takes info about the current cursor position and a set of possible keywords
301343
-- and creates completion suggestions that fit the current input from the given list
@@ -308,79 +350,87 @@ makeCompletionItems pfix l =
308350
-- | Parse the given set of lines (starting before current cursor position
309351
-- up to the start of the file) to find the nearest stanza declaration,
310352
-- if none is found we are in the top level
311-
findCurrentLevel :: [T.Text] -> Context
312-
findCurrentLevel [] = TopLevel
313-
findCurrentLevel (cur : xs)
314-
| Just s <- stanza = Stanza s
315-
| otherwise = findCurrentLevel xs
353+
findCurrentLevel :: [T.Text] -> KeyWordContext -> Context
354+
findCurrentLevel [] kwContext = TopLevel kwContext
355+
findCurrentLevel (cur : xs) kwContext
356+
| Just s <- stanza = Stanza s kwContext
357+
| otherwise = findCurrentLevel xs kwContext
316358
where
317359
stanza = List.find (`T.isPrefixOf` cur) (Map.keys stanzaKeywordMap)
318360

319361
-- | Get all lines before the given cursor position in the given file
320362
-- and reverse them since we want to traverse starting from our current position
321-
getPreviousLines :: VFS.PosPrefixInfo -> VirtualFile -> [T.Text]
322-
getPreviousLines pos cont = reverse $ take (fromIntegral currentLine) allLines
363+
getPreviousLines :: Position -> Rope.Rope -> [T.Text]
364+
getPreviousLines pos rope = reverse $ take (fromIntegral currentLine) allLines
323365
where
324-
allLines = Rope.lines $ cont ^. VFS.file_text
325-
currentLine = (VFS.cursorPos pos) ^. JL.line
326-
366+
allLines = Rope.lines rope
367+
currentLine = pos ^. JL.line
327368

328369
data Context
329-
= TopLevel
370+
= TopLevel KeyWordContext
330371
-- ^ top level context in a cabal file such as 'author'
331-
| Stanza T.Text
372+
| Stanza T.Text KeyWordContext
332373
-- ^ nested context in a cabal file, such as 'library', which has nested keywords, specific to the stanza
333-
deriving (Eq)
374+
deriving (Eq, Show)
375+
376+
-- | Keyword context in cabal file
377+
data KeyWordContext
378+
= KeyWord T.Text
379+
-- ^ we are in a line with the given keyword before our cursor
380+
| None
381+
-- ^ we are in a line with no keyword context
382+
deriving (Eq, Show)
334383

335384
-- | Keyword for cabal version required to be the top line in a cabal file
336-
cabalVersionKeyword :: T.Text
337-
cabalVersionKeyword = "cabal-version:"
385+
cabalVersionKeyword :: (T.Text,[T.Text])
386+
cabalVersionKeyword = ("cabal-version:", [])
338387

339388
-- | Top level keywords of a cabal file
340-
cabalKeywords :: [T.Text]
389+
cabalKeywords :: Map T.Text [T.Text]
341390
cabalKeywords =
342-
[
343-
"name:",
344-
"version:",
345-
"build-type:",
346-
"license:",
347-
"license-file:",
348-
"license-files:",
349-
"copyright:",
350-
"author:",
351-
"maintainer:",
352-
"stability:",
353-
"homepage:",
354-
"bug-reports:",
355-
"package-url:",
356-
"synopsis:",
357-
"description:",
358-
"category:",
359-
"tested-with:",
360-
"data-files:",
361-
"data-dir:",
362-
"data-dir:",
363-
"extra-doc-files:",
364-
"extra-tmp-files:"
391+
Map.fromList [
392+
("name:", []),
393+
("version:", []),
394+
("build-type:", ["Simple", "Custom"]),
395+
("license:", ["NONE"]),
396+
("license-file:", []),
397+
("license-files:",[]),
398+
("copyright:", []),
399+
("author:", [])
400+
-- "maintainer:",
401+
-- "stability:",
402+
-- "homepage:",
403+
-- "bug-reports:",
404+
-- "package-url:",
405+
-- "synopsis:",
406+
-- "description:",
407+
-- "category:",
408+
-- "tested-with:",
409+
-- "data-files:",
410+
-- "data-dir:",
411+
-- "data-dir:",
412+
-- "extra-doc-files:",
413+
-- "extra-tmp-files:"
365414
]
366415

367416
-- | Map, containing all stanzas in a cabal file as keys and lists of their possible nested keywords as values
368-
stanzaKeywordMap :: Map T.Text [T.Text]
369-
stanzaKeywordMap = Map.fromList [("library", [
370-
"exposed-modules:",
371-
"virtual-modules:",
372-
"exposed:",
373-
"visibility:",
374-
"reexported-modules:",
375-
"signatures:"
376-
])]
417+
stanzaKeywordMap :: Map T.Text (Map T.Text [T.Text])
418+
stanzaKeywordMap = Map.fromList [("library", Map.fromList[
419+
("exposed-modules:", []),
420+
("virtual-modules:", []),
421+
("exposed:", ["True", "False"]),
422+
("visibility:", ["private", "public"]),
423+
("reexported-modules:", []),
424+
("signatures:", [])
425+
]),
426+
("test-suite", Map.fromList[])
427+
]
377428

378429

379430
-- TODO move out toplevel commands i.e. test-suite
380431
-- cabalTestKeywords :: [T.Text]
381432
-- cabalTestKeywords =
382433
-- [
383-
-- "test-suite",
384434
-- "type:",
385435
-- "main-is:",
386436
-- "test-module:",

0 commit comments

Comments
 (0)