Skip to content

Commit 611da62

Browse files
committed
Refactor completion code to its own module
1 parent 9b2f3fc commit 611da62

File tree

3 files changed

+313
-278
lines changed

3 files changed

+313
-278
lines changed

plugins/hls-cabal-plugin/hls-cabal-plugin.cabal

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ library
2727
exposed-modules:
2828
Ide.Plugin.Cabal
2929
Ide.Plugin.Cabal.Diagnostics
30+
Ide.Plugin.Cabal.Completions
3031
Ide.Plugin.Cabal.LicenseSuggest
3132
Ide.Plugin.Cabal.Parse
3233

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

Lines changed: 2 additions & 278 deletions
Original file line numberDiff line numberDiff line change
@@ -20,26 +20,20 @@ import qualified Data.ByteString as BS
2020
import Data.Hashable
2121
import Data.HashMap.Strict (HashMap)
2222
import qualified Data.HashMap.Strict as HashMap
23-
import qualified Data.List as List
24-
import qualified Data.List.Extra as Extra
2523
import qualified Data.List.NonEmpty as NE
26-
import Data.Map (Map)
27-
import qualified Data.Map as Map
28-
import qualified Data.Text as T
2924
import qualified Data.Text.Encoding as Encoding
3025
import qualified Data.Text.Utf16.Rope as Rope
3126
import Data.Typeable
32-
import Debug.Trace
3327
import Development.IDE as D
3428
import Development.IDE.Core.Shake (restartShakeSession)
3529
import qualified Development.IDE.Core.Shake as Shake
3630
import Development.IDE.Graph (alwaysRerun)
3731
import Distribution.Compat.Lens ((^.))
3832
import GHC.Generics
33+
import Ide.Plugin.Cabal.Completions
3934
import qualified Ide.Plugin.Cabal.Diagnostics as Diagnostics
4035
import qualified Ide.Plugin.Cabal.LicenseSuggest as LicenseSuggest
4136
import qualified Ide.Plugin.Cabal.Parse as Parse
42-
import Ide.Plugin.Config (Config)
4337
import Ide.Types
4438
import qualified Language.LSP.Server as LSP
4539
import Language.LSP.Types
@@ -48,7 +42,6 @@ import qualified Language.LSP.Types as LSP
4842
import qualified Language.LSP.Types.Lens as JL
4943
import Language.LSP.VFS (VirtualFile)
5044
import qualified Language.LSP.VFS as VFS
51-
import qualified Text.Fuzzy.Parallel as Fuzzy
5245

5346

5447
data Log
@@ -288,7 +281,7 @@ completion _ide _ complParams = do
288281
result :: Maybe VFS.PosPrefixInfo -> VirtualFile -> J.List CompletionItem
289282
result Nothing _ = J.List []
290283
result (Just pfix) cnts
291-
| pos ^. JL.line == 0 = case traceShowId $ context of
284+
| pos ^. JL.line == 0 = case context of
292285
Just (_, kw)
293286
| KeyWord _ <- kw -> J.List $ map buildCompletion $ snd cabalVersionKeyword
294287
_ -> J.List [buildCompletion (fst cabalVersionKeyword)]
@@ -297,272 +290,3 @@ completion _ide _ complParams = do
297290
where
298291
pos = VFS.cursorPos pfix
299292
context = getContext pos (Rope.lines $ cnts ^. VFS.file_text)
300-
301-
-- | Takes a context and returns all possible completions within that context
302-
getCompletionsForContext :: Context -> [T.Text]
303-
-- if we are in the top level of the cabal file and not in a keyword context,
304-
-- we can write any toplevel keywords or a stanza declaration
305-
getCompletionsForContext (TopLevel, None) =
306-
Map.keys cabalKeywords ++ Map.keys stanzaKeywordMap
307-
-- if we are in a keyword context in the toplevel,
308-
-- we look up that keyword in the toplevel context and can complete its possible values
309-
getCompletionsForContext (TopLevel, KeyWord kw) =
310-
case Map.lookup kw cabalKeywords of
311-
Nothing -> []
312-
Just l -> l
313-
-- if we are in a stanza and not in a keyword context,
314-
-- we can write any of the stanza's keywords or a stanza declaration
315-
getCompletionsForContext (Stanza s, None) =
316-
case Map.lookup s stanzaKeywordMap of
317-
Nothing -> []
318-
Just l -> Map.keys l ++ Map.keys stanzaKeywordMap
319-
-- if we are in a stanza's keyword's context we can complete possible values of that keyword
320-
getCompletionsForContext (Stanza s, KeyWord kw) =
321-
case Map.lookup s stanzaKeywordMap of
322-
Nothing -> []
323-
Just m -> case Map.lookup kw m of
324-
Nothing -> []
325-
Just l -> l
326-
327-
-- | Takes a position and a list of lines (representing a file)
328-
-- and returns the context of the current position
329-
-- can return Nothing if an error occurs
330-
getContext :: Position -> [T.Text] -> Maybe Context
331-
getContext pos ls =
332-
case lvlContext of
333-
TopLevel -> do
334-
kwContext <- getKeyWordContext pos ls (uncurry Map.insert cabalVersionKeyword cabalKeywords)
335-
pure (TopLevel, kwContext)
336-
Stanza s ->
337-
case Map.lookup (traceShowId s) stanzaKeywordMap of
338-
Nothing -> do
339-
pure (Stanza s, None)
340-
Just m -> do
341-
kwContext <- getKeyWordContext pos ls m
342-
pure (Stanza s, kwContext)
343-
where
344-
lvlContext = findCurrentLevel (getPreviousLines pos ls)
345-
346-
-- | Takes a position, a list of lines (representing a file) and a map of keywords as keys
347-
-- and returns a keyword context if there is a keyword from the map before the current position
348-
-- in the given line list
349-
getKeyWordContext :: Position -> [T.Text] -> Map T.Text a -> Maybe KeyWordContext
350-
getKeyWordContext pos ls keywords = do
351-
curLine <- fmap T.stripStart currentLine
352-
case List.find (`T.isPrefixOf` curLine) (Map.keys keywords) of
353-
Nothing -> Just None
354-
Just kw -> Just $ KeyWord kw
355-
where
356-
currentLine = ls Extra.!? (fromIntegral $ pos ^. JL.line)
357-
358-
-- | Takes info about the current cursor position and a set of possible keywords
359-
-- and creates completion suggestions that fit the current input from the given list
360-
makeCompletionItems :: VFS.PosPrefixInfo -> [T.Text] -> [CompletionItem]
361-
makeCompletionItems pfix l =
362-
map
363-
(buildCompletion . Fuzzy.original)
364-
(Fuzzy.simpleFilter 1000 10 (VFS.prefixText pfix) l)
365-
366-
-- | Parse the given set of lines (starting before current cursor position
367-
-- up to the start of the file) to find the nearest stanza declaration,
368-
-- if none is found we are in the top level
369-
findCurrentLevel :: [T.Text] -> LevelContext
370-
findCurrentLevel [] = TopLevel
371-
findCurrentLevel (cur : xs)
372-
| Just s <- stanza = Stanza s
373-
| otherwise = findCurrentLevel xs
374-
where
375-
stanza = List.find (`T.isPrefixOf` cur) (Map.keys stanzaKeywordMap)
376-
377-
-- | Get all lines before the given cursor position in the given file
378-
-- and reverse them since we want to traverse starting from our current position
379-
getPreviousLines :: Position -> [T.Text] -> [T.Text]
380-
getPreviousLines pos ls = reverse $ take (fromIntegral currentLine) ls
381-
where
382-
currentLine = pos ^. JL.line
383-
384-
-- | The context a cursor can be in within a cabal file,
385-
-- we can be in stanzas or the toplevel,
386-
-- and additionally we can be in a context where we have already written a keyword
387-
-- but no value for it yet
388-
type Context = (LevelContext, KeyWordContext)
389-
390-
data LevelContext
391-
= TopLevel
392-
-- ^ Top level context in a cabal file such as 'author'
393-
| Stanza T.Text
394-
-- ^ Nested context in a cabal file, such as 'library', which has nested keywords, specific to the stanza
395-
deriving (Eq, Show)
396-
397-
-- | Keyword context in cabal file
398-
data KeyWordContext
399-
= KeyWord T.Text
400-
-- ^ We are in a line with the given keyword before our cursor
401-
| None
402-
-- ^ We are in a line with no keyword context
403-
deriving (Eq, Show)
404-
405-
-- | Keyword for cabal version required to be the top line in a cabal file
406-
cabalVersionKeyword :: (T.Text,[T.Text])
407-
cabalVersionKeyword = ("cabal-version:", ["2.0", "2.2", "2.4", "3.0"])
408-
409-
410-
-- todo: we could add file path completion for file path fields
411-
-- we could add descriptions of field values and then show them when inside the field's context
412-
-- | Top level keywords of a cabal file
413-
cabalKeywords :: Map T.Text [T.Text]
414-
cabalKeywords =
415-
Map.fromList [
416-
("name:", []),
417-
("version:", []),
418-
("build-type:", ["Simple", "Custom"]),
419-
("license:", ["NONE"]),
420-
("license-file:", []),
421-
("license-files:",[]),
422-
("copyright:", []),
423-
("author:", []),
424-
("maintainer:",[]),
425-
("stability:",[]),
426-
("homepage:",[]),
427-
("bug-reports:",[]),
428-
("package-url:",[]),
429-
("synopsis:",[]),
430-
("description:",[]),
431-
("category:",[]),
432-
("tested-with:",["GHC"]),
433-
("data-files:", []),
434-
("data-dir:", []),
435-
("data-dir:", []),
436-
("extra-source-files:", []),
437-
("extra-doc-files:", []),
438-
("extra-tmp-files:", [])
439-
]
440-
441-
-- | Map, containing all stanzas in a cabal file as keys and lists of their possible nested keywords as values
442-
stanzaKeywordMap :: Map T.Text (Map T.Text [T.Text])
443-
stanzaKeywordMap =
444-
Map.fromList
445-
[ ( "library",
446-
Map.fromList $
447-
[ ("exposed-modules:", []),
448-
("virtual-modules:", []),
449-
("exposed:", ["True", "False"]),
450-
("visibility:", ["private", "public"]),
451-
("reexported-modules:", []),
452-
("signatures:", [])
453-
]
454-
++ libExecTestBenchCommons
455-
),
456-
( "executable",
457-
Map.fromList $
458-
[ ("main-is:", []),
459-
("scope:", ["public", "private"])
460-
]
461-
++ libExecTestBenchCommons
462-
),
463-
( "test-suite",
464-
Map.fromList $
465-
[ ("type:", ["exitcode-stdio-1.0"]),
466-
("main-is:", [])
467-
]
468-
++ libExecTestBenchCommons
469-
),
470-
( "benchmark",
471-
Map.fromList $
472-
[ ("type:", []),
473-
("main-is:", [])
474-
]
475-
++ libExecTestBenchCommons
476-
),
477-
( "foreign-library",
478-
Map.fromList
479-
[ ("type:", []),
480-
("options:", []),
481-
("mod-def-file:", []),
482-
("lib-def-file:", []),
483-
("lib-version-info:", []),
484-
("lib-version-linux:", [])
485-
]
486-
),
487-
( "flag",
488-
Map.fromList
489-
[ ("description:", []),
490-
("default:", ["True", "False"]),
491-
("manual:", ["False", "True"]),
492-
("lib-def-file:", []),
493-
("lib-version-info:", []),
494-
("lib-version-linux:", [])
495-
]
496-
)
497-
]
498-
where
499-
libExecTestBenchCommons =
500-
[ ("build-depends:", []),
501-
("other-modules:", []),
502-
("hs-source-dir:", ["."]),
503-
("hs-source-dirs:", ["."]),
504-
("default-extensions:", []),
505-
("other-extensions:", []),
506-
("default-language:", []),
507-
("build-tool-depends:", []),
508-
("buildable:", ["True", "False"]),
509-
-- todo maybe there is a list of possible ghc options somewhere
510-
("ghc-options:", []),
511-
("ghc-prof-options:", []),
512-
("ghc-shared-options:", []),
513-
("ghcjs-options:", []),
514-
("ghcjs-prof-options:", []),
515-
("ghcjs-shared-options:", []),
516-
("includes:", []),
517-
("install-includes:", []),
518-
("include-dirs:", []),
519-
("c-sources:", []),
520-
("cxx-sources:", []),
521-
("asm-sources:", []),
522-
("cmm-sources:", []),
523-
("js-sources:", []),
524-
("extra-libraries:", []),
525-
("extra-ghci-libraries:", []),
526-
("extra-bundled-libraries:", []),
527-
("extra-lib-dirs:", []),
528-
("cc-options:", []),
529-
("cpp-options:", []),
530-
("cxx-options:", []),
531-
("cmm-options:", []),
532-
("asm-options:", []),
533-
("ld-options:", []),
534-
("pkgconfig-depends:", []),
535-
("frameworks:", []),
536-
("extra-framework-dirs:", []),
537-
("mixins:", [])
538-
]
539-
540-
-- cabalFlagKeywords :: [(T.Text, T.Text)]
541-
-- cabalFlagKeywords =
542-
-- [
543-
-- ("flag", "name"),
544-
-- ("description:", "freeform"),
545-
-- ("default:", "boolean"),
546-
-- ("manual:", "boolean")
547-
-- ]
548-
549-
-- cabalStanzaKeywords :: [(T.Text, T.Text)]
550-
-- cabalStanzaKeywords =
551-
-- [
552-
-- ("common", "name"),
553-
-- ("import:", "token-list")
554-
-- ]
555-
556-
-- cabalSourceRepoKeywords :: [(T.Text, T.Text)]
557-
-- cabalSourceRepoKeywords =
558-
-- [
559-
-- ("source-repository", ""),
560-
-- ("type:", "token"),
561-
-- ("location:", "URL")
562-
-- ]
563-
564-
buildCompletion :: T.Text -> J.CompletionItem
565-
buildCompletion label =
566-
J.CompletionItem label (Just J.CiKeyword) Nothing Nothing
567-
Nothing Nothing Nothing Nothing Nothing Nothing Nothing
568-
Nothing Nothing Nothing Nothing Nothing Nothing

0 commit comments

Comments
 (0)