Skip to content

Commit 61d1d30

Browse files
committed
Refactor getCompletions function
- add some documentation comments - add type signatures fo easier overview - remove superfluous IO signature - remove outer let - move to where - remove multiway if, use guards directly
1 parent f15b6e3 commit 61d1d30

File tree

2 files changed

+101
-67
lines changed

2 files changed

+101
-67
lines changed

ghcide/src/Development/IDE/Plugin/Completions.hs

+1-1
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@ getCompletionsLSP ide plId
150150
(Just pfix', _) -> do
151151
let clientCaps = clientCapabilities $ shakeExtras ide
152152
config <- getCompletionsConfig plId
153-
allCompletions <- liftIO $ getCompletions plId ideOpts cci' parsedMod bindMap pfix' clientCaps config moduleExports
153+
let allCompletions = getCompletions plId ideOpts cci' parsedMod bindMap pfix' clientCaps config moduleExports
154154
pure $ InL (List $ orderedCompletions allCompletions)
155155
_ -> return (InL $ List [])
156156
_ -> return (InL $ List [])

ghcide/src/Development/IDE/Plugin/Completions/Logic.hs

+100-66
Original file line numberDiff line numberDiff line change
@@ -560,24 +560,76 @@ getCompletions
560560
-> ClientCapabilities
561561
-> CompletionsConfig
562562
-> HM.HashMap T.Text (HashSet.HashSet IdentInfo)
563-
-> IO [Scored CompletionItem]
564-
getCompletions plId ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qualCompls, importableModules}
565-
maybe_parsed (localBindings, bmapping) prefixInfo caps config moduleExportsMap = do
566-
let VFS.PosPrefixInfo { fullLine, prefixModule, prefixText } = prefixInfo
567-
enteredQual = if T.null prefixModule then "" else prefixModule <> "."
563+
-> [Scored CompletionItem]
564+
getCompletions
565+
plId
566+
ideOpts
567+
CC {allModNamesAsNS, anyQualCompls, unqualCompls, qualCompls, importableModules}
568+
maybe_parsed
569+
(localBindings, bmapping)
570+
VFS.PosPrefixInfo {fullLine, prefixModule, prefixText, cursorPos}
571+
caps
572+
config
573+
moduleExportsMap
574+
-- ------------------------------------------------------------------------
575+
-- IMPORT MODULENAME (NAM|)
576+
--
577+
-- TODO: handle multiline imports
578+
| Just (ImportListContext moduleName) <- maybeContext
579+
= moduleImportListCompletions moduleName
580+
581+
| Just (ImportHidingContext moduleName) <- maybeContext
582+
= moduleImportListCompletions moduleName
583+
584+
-- TODO: Is manual parsing ever needed or is context always present for module?
585+
-- If possible only keep the above.
586+
| "import " `T.isPrefixOf` fullLine
587+
, Just moduleName <- getModuleName fullLine
588+
, "(" `T.isInfixOf` fullLine
589+
= moduleImportListCompletions $ T.unpack moduleName
590+
591+
-- ------------------------------------------------------------------------
592+
-- IMPORT MODULENAM|
593+
| Just (ImportContext _moduleName) <- maybeContext
594+
= filtImportCompls
595+
596+
-- TODO: Can we avoid this manual parsing?
597+
-- If possible only keep the above.
598+
| "import " `T.isPrefixOf` fullLine
599+
= filtImportCompls
600+
601+
-- ------------------------------------------------------------------------
602+
-- {-# LA| #-}
603+
-- we leave this condition here to avoid duplications and return empty list
604+
-- since HLS implements these completions (#haskell-language-server/pull/662)
605+
| "{-# " `T.isPrefixOf` fullLine
606+
= []
607+
608+
-- ------------------------------------------------------------------------
609+
| otherwise =
610+
-- assumes that nubOrdBy is stable
611+
let uniqueFiltCompls = nubOrdBy (uniqueCompl `on` snd . Fuzzy.original) filtCompls
612+
compls = (fmap.fmap.fmap) (mkCompl plId ideOpts) uniqueFiltCompls
613+
in (fmap.fmap) snd $
614+
sortBy (compare `on` lexicographicOrdering) $
615+
mergeListsBy (flip compare `on` score)
616+
[ (fmap.fmap) (notQual,) filtModNameCompls
617+
, (fmap.fmap) (notQual,) filtKeywordCompls
618+
, (fmap.fmap.fmap) (toggleSnippets caps config) compls
619+
]
620+
where
621+
-- TODO: If possible avoid using raw text PosPrefixInfo and use ParsedModule.
622+
enteredQual :: T.Text
623+
enteredQual = if qual then prefixModule <> "." else ""
624+
fullPrefix :: T.Text
568625
fullPrefix = enteredQual <> prefixText
569626

570627
-- Boolean labels to tag suggestions as qualified (or not)
571-
qual = not(T.null prefixModule)
628+
qual, notQual :: Bool
629+
qual = not (T.null prefixModule)
572630
notQual = False
573631

574-
{- correct the position by moving 'foo :: Int -> String -> '
575-
^
576-
to 'foo :: Int -> String -> '
577-
^
578-
-}
579-
pos = VFS.cursorPos prefixInfo
580-
632+
maxC :: Int
581633
maxC = maxCompletions config
582634

583635
filtModNameCompls :: [Scored CompletionItem]
@@ -587,15 +639,29 @@ getCompletions plId ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qu
587639
$ (if T.null enteredQual then id else mapMaybe (T.stripPrefix enteredQual))
588640
allModNamesAsNS
589641

642+
-- ----------------------------------------
643+
-- Note: correct the cursorPos by moving
644+
--
645+
-- 'foo :: Int -> String -> '
646+
-- ^
647+
-- to
648+
--
649+
-- 'foo :: Int -> String -> '
650+
-- ^
651+
-- ----------------------------------------
652+
653+
-- If we have a parsed module, use it to determine which completion to show.
654+
maybeContext :: Maybe Context
590655
maybeContext = case maybe_parsed of
591656
Nothing -> Nothing
592657
Just (pm, pmapping) ->
593658
let PositionMapping pDelta = pmapping
594-
position' = fromDelta pDelta pos
659+
position' = fromDelta pDelta cursorPos
595660
lpos = lowerRange position'
596661
hpos = upperRange position'
597662
in getCContext lpos pm <|> getCContext hpos pm
598663

664+
filtCompls :: [Scored (Bool, CompItem)]
599665
filtCompls = Fuzzy.filter chunkSize maxC prefixText ctxCompls (label . snd)
600666
where
601667
-- completions specific to the current context
@@ -608,10 +674,10 @@ getCompletions plId ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qu
608674
ctxCompls = (fmap.fmap) (\comp -> toggleAutoExtend config $ comp { isInfix = infixCompls }) ctxCompls'
609675

610676
infixCompls :: Maybe Backtick
611-
infixCompls = isUsedAsInfix fullLine prefixModule prefixText pos
677+
infixCompls = isUsedAsInfix fullLine prefixModule prefixText cursorPos
612678

613679
PositionMapping bDelta = bmapping
614-
oldPos = fromDelta bDelta $ VFS.cursorPos prefixInfo
680+
oldPos = fromDelta bDelta cursorPos
615681
startLoc = lowerRange oldPos
616682
endLoc = upperRange oldPos
617683
localCompls = map (uncurry localBindsToCompItem) $ getFuzzyScope localBindings startLoc endLoc
@@ -629,6 +695,7 @@ getCompletions plId ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qu
629695
else ((qual,) <$> Map.findWithDefault [] prefixModule (getQualCompls qualCompls))
630696
++ ((notQual,) . ($ Just prefixModule) <$> anyQualCompls)
631697

698+
filtListWith :: (T.Text -> CompletionItem) -> [T.Text] -> [Scored CompletionItem]
632699
filtListWith f list =
633700
[ fmap f label
634701
| label <- Fuzzy.simpleFilter chunkSize maxC fullPrefix list
@@ -643,64 +710,31 @@ getCompletions plId ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qu
643710
in filterModuleExports moduleName $ map T.pack funs
644711

645712
-- manually parse in case we don't have completion context ("import [qualified ]ModuleName")
713+
getModuleName :: T.Text -> Maybe T.Text
646714
getModuleName line = filter (/= "qualified") (T.words line) !? 1
715+
716+
filtImportCompls :: [Scored CompletionItem]
647717
filtImportCompls = filtListWith (mkImportCompl enteredQual) importableModules
718+
719+
filterModuleExports :: T.Text -> [T.Text] -> [Scored CompletionItem]
648720
filterModuleExports moduleName = filtListWith $ mkModuleFunctionImport moduleName
721+
722+
filtKeywordCompls :: [Scored CompletionItem]
649723
filtKeywordCompls
650724
| T.null prefixModule = filtListWith mkExtCompl (optKeywords ideOpts)
651725
| otherwise = []
652726

653-
if
654-
-- TODO: handle multiline imports
655-
| Just (ImportListContext moduleName) <- maybeContext
656-
-> pure $ moduleImportListCompletions moduleName
657-
658-
| Just (ImportHidingContext moduleName) <- maybeContext
659-
-> pure $ moduleImportListCompletions moduleName
660-
661-
-- TODO: Is manual parsing ever needed or is context always present for module?
662-
-- If possible only keep the above.
663-
| "import " `T.isPrefixOf` fullLine
664-
, Just moduleName <- getModuleName fullLine
665-
, "(" `T.isInfixOf` fullLine
666-
-> pure $ moduleImportListCompletions $ T.unpack moduleName
667-
668-
| Just (ImportContext _moduleName) <- maybeContext
669-
-> return filtImportCompls
670-
671-
-- TODO: Can we avoid this manual parsing?
672-
-- If possible only keep the above.
673-
| "import " `T.isPrefixOf` fullLine
674-
-> return filtImportCompls
675-
676-
-- we leave this condition here to avoid duplications and return empty list
677-
-- since HLS implements these completions (#haskell-language-server/pull/662)
678-
| "{-# " `T.isPrefixOf` fullLine
679-
-> return []
680-
681-
| otherwise -> do
682-
-- assumes that nubOrdBy is stable
683-
let uniqueFiltCompls = nubOrdBy (uniqueCompl `on` snd . Fuzzy.original) filtCompls
684-
let compls = (fmap.fmap.fmap) (mkCompl plId ideOpts) uniqueFiltCompls
685-
return $
686-
(fmap.fmap) snd $
687-
sortBy (compare `on` lexicographicOrdering) $
688-
mergeListsBy (flip compare `on` score)
689-
[ (fmap.fmap) (notQual,) filtModNameCompls
690-
, (fmap.fmap) (notQual,) filtKeywordCompls
691-
, (fmap.fmap.fmap) (toggleSnippets caps config) compls
692-
]
693-
where
694-
-- We use this ordering to alphabetically sort suggestions while respecting
695-
-- all the previously applied ordering sources. These are:
696-
-- 1. Qualified suggestions go first
697-
-- 2. Fuzzy score ranks next
698-
-- 3. In-scope completions rank next
699-
-- 4. label alphabetical ordering next
700-
-- 4. detail alphabetical ordering (proxy for module)
701-
lexicographicOrdering Fuzzy.Scored{score, original} =
727+
-- We use this ordering to alphabetically sort suggestions while respecting
728+
-- all the previously applied ordering sources. These are:
729+
-- 1. Qualified suggestions go first
730+
-- 2. Fuzzy score ranks next
731+
-- 3. In-scope completions rank next
732+
-- 4. label alphabetical ordering next
733+
-- 4. detail alphabetical ordering (proxy for module)
734+
lexicographicOrdering :: Scored (a, CompletionItem) -> (Down a, Down Int, Down Bool, T.Text, Maybe T.Text)
735+
lexicographicOrdering Fuzzy.Scored{score, original} =
702736
case original of
703-
(isQual, CompletionItem{_label,_detail}) -> do
737+
(isQual, CompletionItem{_label,_detail}) -> do
704738
let isLocal = maybe False (":" `T.isPrefixOf`) _detail
705739
(Down isQual, Down score, Down isLocal, _label, _detail)
706740

0 commit comments

Comments
 (0)