Skip to content

Commit 3818404

Browse files
author
Jana Chadt
committed
Implement completion of possible keywords for field values in cabal files
1 parent 6dfd216 commit 3818404

File tree

1 file changed

+126
-74
lines changed
  • plugins/hls-cabal-plugin/src/Ide/Plugin

1 file changed

+126
-74
lines changed

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

Lines changed: 126 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ import qualified Data.Map as Map
4343
import Language.LSP.VFS (VirtualFile)
4444
import qualified Data.Text.Utf16.Rope as Rope
4545
import qualified Data.List as List
46+
import qualified Language.LSP.Types.Lens as Map
47+
import Development.IDE.GHC.Compat (getContext)
48+
import Debug.Trace
49+
import qualified Data.List.Extra as Extra
4650
data Log
4751
= LogModificationTime NormalizedFilePath (Maybe FileVersion)
4852
| LogDiagnostics NormalizedFilePath [FileDiagnostic]
@@ -72,7 +76,7 @@ instance Pretty Log where
7276
descriptor :: Recorder (WithPriority Log) -> PluginId -> PluginDescriptor IdeState
7377
descriptor recorder plId = (defaultCabalPluginDescriptor plId)
7478
{ pluginRules = cabalRules recorder
75-
, pluginHandlers = mkPluginHandler STextDocumentCodeAction licenseSuggestCodeAction
79+
, pluginHandlers = mkPluginHandler STextDocumentCodeAction licenseSuggestCodeAction
7680
<> mkPluginHandler J.STextDocumentCompletion completion
7781
, pluginNotificationHandlers = mconcat
7882
[ mkPluginNotificationHandler LSP.STextDocumentDidOpen $
@@ -166,6 +170,7 @@ licenseSuggestCodeAction _ _ (CodeActionParams _ _ (TextDocumentIdentifier uri)
166170
-- ----------------------------------------------------------------
167171
-- Completion
168172
-- ----------------------------------------------------------------
173+
169174
completion :: PluginMethodHandler IdeState 'J.TextDocumentCompletion
170175
completion _ide _ complParams = do
171176
let (J.TextDocumentIdentifier uri) = complParams ^. JL.textDocument
@@ -180,103 +185,150 @@ completion _ide _ complParams = do
180185
result :: Maybe VFS.PosPrefixInfo -> VirtualFile -> J.List CompletionItem
181186
result Nothing _ = J.List []
182187
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
188+
| pos ^. JL.line == 0 = J.List [buildCompletion (fst cabalVersionKeyword)]
189+
| Just ctx@(Stanza s kwContext) <- context =
190+
case (Map.lookup s stanzaKeywordMap) of
191+
Nothing ->
192+
case kwContext of
193+
None ->
194+
J.List $
195+
makeCompletionItems pfix topLevelKeywords
196+
KeyWord kw -> J.List $ makeCompletionItems pfix (getPossibleValuesForKeyWord kw ctx)
197+
Just m ->
198+
case kwContext of
199+
None -> J.List $ (makeCompletionItems pfix (Map.keys m)) ++ (makeCompletionItems pfix $ Map.keys stanzaKeywordMap)
200+
KeyWord kw -> J.List $ makeCompletionItems pfix (getPossibleValuesForKeyWord kw ctx)
201+
| Just ctx@(TopLevel kwContext) <- context =
202+
case kwContext of
203+
None -> J.List $ makeCompletionItems pfix topLevelKeywords
204+
KeyWord kw -> J.List $ makeCompletionItems pfix (getPossibleValuesForKeyWord kw ctx)
205+
| otherwise = J.List []
206+
where
207+
pos = VFS.cursorPos pfix
208+
topLevelKeywords = Map.keys cabalKeywords ++ Map.keys stanzaKeywordMap
209+
context = findCurrentContext pos (cnts ^. VFS.file_text)
210+
211+
getPossibleValuesForKeyWord :: T.Text -> Context -> [T.Text]
212+
getPossibleValuesForKeyWord kw (TopLevel _) =
213+
case Map.lookup kw cabalKeywords of
214+
Nothing -> []
215+
Just l -> l
216+
getPossibleValuesForKeyWord kw (Stanza s _) =
217+
case Map.lookup s stanzaKeywordMap of
218+
Nothing -> []
219+
Just m -> case Map.lookup kw m of
220+
Nothing -> []
221+
Just l -> l
222+
223+
findCurrentContext :: Position -> Rope.Rope -> Maybe Context
224+
findCurrentContext pos rope =
225+
case outerContext of
226+
TopLevel _ -> TopLevel <$> getKeyWordContext cabalKeywords
227+
Stanza s _ ->
228+
case traceShowId (Map.lookup (traceShowId s) stanzaKeywordMap) of
229+
Nothing -> pure $ Stanza s None
230+
Just m -> traceShowId $ Stanza s <$> getKeyWordContext m
231+
where
232+
outerContext = findCurrentLevel (getPreviousLines pos rope) None
233+
currentLine = traceShowId $ (Rope.lines rope) Extra.!? (fromIntegral $ pos ^. JL.line)
234+
getKeyWordContext keywords = do
235+
curLine <- fmap T.stripStart currentLine
236+
case List.find (\kw -> traceShowId kw `T.isPrefixOf` curLine) (traceShowId $ Map.keys keywords) of
237+
Nothing -> Just None
238+
Just kw -> Just $ KeyWord kw
239+
240+
-- | Takes info about the current cursor position and a set of possible keywords
197241
-- and creates completion suggestions that fit the current input from the given list
198242
makeCompletionItems :: VFS.PosPrefixInfo -> [T.Text] -> [CompletionItem]
199243
makeCompletionItems pfix l =
200244
map
201245
(buildCompletion . Fuzzy.original)
202246
(Fuzzy.simpleFilter 1000 10 (VFS.prefixText pfix) l)
203247

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,
248+
-- | Parse the given set of lines (starting before current cursor position
249+
-- up to the start of the file) to find the nearest stanza declaration,
206250
-- 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
251+
findCurrentLevel :: [T.Text] -> KeyWordContext -> Context
252+
findCurrentLevel [] kwContext = TopLevel kwContext
253+
findCurrentLevel (cur : xs) kwContext
254+
| Just s <- stanza = Stanza s kwContext
255+
| otherwise = findCurrentLevel xs kwContext
212256
where
213257
stanza = List.find (`T.isPrefixOf` cur) (Map.keys stanzaKeywordMap)
214258

215-
-- | Get all lines before the given cursor position in the given file
259+
-- | Get all lines before the given cursor position in the given file
216260
-- 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
261+
getPreviousLines :: Position -> Rope.Rope -> [T.Text]
262+
getPreviousLines pos rope = reverse $ take (fromIntegral currentLine) allLines
219263
where
220-
allLines = Rope.lines $ cont ^. VFS.file_text
221-
currentLine = (VFS.cursorPos pos) ^. JL.line
264+
allLines = Rope.lines rope
265+
currentLine = pos ^. JL.line
222266

223-
224-
data Context
225-
= TopLevel
267+
data Context
268+
= TopLevel KeyWordContext
226269
-- ^ top level context in a cabal file such as 'author'
227-
| Stanza T.Text
270+
| Stanza T.Text KeyWordContext
228271
-- ^ nested context in a cabal file, such as 'library', which has nested keywords, specific to the stanza
229-
deriving (Eq)
272+
deriving (Eq, Show)
273+
274+
-- | Keyword context in cabal file
275+
data KeyWordContext
276+
= KeyWord T.Text
277+
-- ^ we are in a line with the given keyword before our cursor
278+
| None
279+
-- ^ we are in a line with no keyword context
280+
deriving (Eq, Show)
230281

231282
-- | Keyword for cabal version required to be the top line in a cabal file
232-
cabalVersionKeyword :: T.Text
233-
cabalVersionKeyword = "cabal-version:"
283+
cabalVersionKeyword :: (T.Text,[T.Text])
284+
cabalVersionKeyword = ("cabal-version:", [])
234285

235286
-- | 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:"
287+
cabalKeywords :: Map T.Text [T.Text]
288+
cabalKeywords =
289+
Map.fromList [
290+
("name:", []),
291+
("version:", []),
292+
("build-type:", ["Simple", "Custom"]),
293+
("license:", ["NONE"]),
294+
("license-file:", []),
295+
("license-files:",[]),
296+
("copyright:", []),
297+
("author:", [])
298+
-- "maintainer:",
299+
-- "stability:",
300+
-- "homepage:",
301+
-- "bug-reports:",
302+
-- "package-url:",
303+
-- "synopsis:",
304+
-- "description:",
305+
-- "category:",
306+
-- "tested-with:",
307+
-- "data-files:",
308+
-- "data-dir:",
309+
-- "data-dir:",
310+
-- "extra-doc-files:",
311+
-- "extra-tmp-files:"
261312
]
262313

263314
-- | 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-
315+
stanzaKeywordMap :: Map T.Text (Map T.Text [T.Text])
316+
stanzaKeywordMap = Map.fromList [("library", Map.fromList[
317+
("exposed-modules:", []),
318+
("virtual-modules:", []),
319+
("exposed:", ["True", "False"]),
320+
("visibility:", ["private", "public"]),
321+
("reexported-modules:", []),
322+
("signatures:", [])
323+
]),
324+
("test-suite", Map.fromList[])
325+
]
326+
274327

275328
-- TODO move out toplevel commands i.e. test-suite
276329
-- cabalTestKeywords :: [T.Text]
277-
-- cabalTestKeywords =
330+
-- cabalTestKeywords =
278331
-- [
279-
-- "test-suite",
280332
-- "type:",
281333
-- "main-is:",
282334
-- "test-module:",
@@ -336,7 +388,7 @@ stanzaKeywordMap = Map.fromList [("library", [
336388
-- ]
337389

338390
-- cabalFlagKeywords :: [(T.Text, T.Text)]
339-
-- cabalFlagKeywords =
391+
-- cabalFlagKeywords =
340392
-- [
341393
-- ("flag", "name"),
342394
-- ("description:", "freeform"),
@@ -345,14 +397,14 @@ stanzaKeywordMap = Map.fromList [("library", [
345397
-- ]
346398

347399
-- cabalStanzaKeywords :: [(T.Text, T.Text)]
348-
-- cabalStanzaKeywords =
400+
-- cabalStanzaKeywords =
349401
-- [
350402
-- ("common", "name"),
351403
-- ("import:", "token-list")
352404
-- ]
353405

354406
-- cabalSourceRepoKeywords :: [(T.Text, T.Text)]
355-
-- cabalSourceRepoKeywords =
407+
-- cabalSourceRepoKeywords =
356408
-- [
357409
-- ("source-repository", ""),
358410
-- ("type:", "token"),

0 commit comments

Comments
 (0)