Skip to content

Commit cf6db6c

Browse files
committed
Fix completion for qualified import
- fix how we get the module name considering it can be preceded by `qualified` - use parsed context for import completions - add regression test for fixed multiline import - refactor `getCompletions` function
1 parent 86446c7 commit cf6db6c

File tree

2 files changed

+80
-54
lines changed

2 files changed

+80
-54
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,7 @@ getCompletionsLSP ide plId
221221
plugins = idePlugins $ shakeExtras ide
222222
config <- liftIO $ runAction "" ide $ getCompletionsConfig plId
223223

224-
allCompletions <- liftIO $ getCompletions plugins ideOpts cci' parsedMod astres bindMap pfix clientCaps config moduleExports uri
224+
let allCompletions = getCompletions plugins ideOpts cci' parsedMod astres bindMap pfix clientCaps config moduleExports uri
225225
pure $ InL (orderedCompletions allCompletions)
226226
_ -> return (InL [])
227227
_ -> return (InL [])

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

+79-53
Original file line numberDiff line numberDiff line change
@@ -576,10 +576,54 @@ getCompletions
576576
-> CompletionsConfig
577577
-> ModuleNameEnv (HashSet.HashSet IdentInfo)
578578
-> Uri
579-
-> IO [Scored CompletionItem]
580-
getCompletions plugins ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qualCompls, importableModules}
581-
maybe_parsed maybe_ast_res (localBindings, bmapping) prefixInfo caps config moduleExportsMap uri = do
582-
let PosPrefixInfo { fullLine, prefixScope, prefixText } = prefixInfo
579+
-> [Scored CompletionItem]
580+
getCompletions
581+
plugins
582+
ideOpts
583+
CC {allModNamesAsNS, anyQualCompls, unqualCompls, qualCompls, importableModules}
584+
maybe_parsed
585+
maybe_ast_res
586+
(localBindings, bmapping)
587+
prefixInfo@(PosPrefixInfo { fullLine, prefixScope, prefixText })
588+
caps
589+
config
590+
moduleExportsMap
591+
uri
592+
-- ------------------------------------------------------------------------
593+
-- IMPORT MODULENAME (NAM|)
594+
| Just (ImportListContext moduleName) <- maybeContext
595+
= moduleImportListCompletions moduleName
596+
597+
| Just (ImportHidingContext moduleName) <- maybeContext
598+
= moduleImportListCompletions moduleName
599+
600+
-- ------------------------------------------------------------------------
601+
-- IMPORT MODULENAM|
602+
| Just (ImportContext _moduleName) <- maybeContext
603+
= filtImportCompls
604+
605+
-- ------------------------------------------------------------------------
606+
-- {-# LA| #-}
607+
-- we leave this condition here to avoid duplications and return empty list
608+
-- since HLS implements these completions (#haskell-language-server/pull/662)
609+
| "{-# " `T.isPrefixOf` fullLine
610+
= []
611+
612+
-- ------------------------------------------------------------------------
613+
| otherwise =
614+
-- assumes that nubOrdBy is stable
615+
let uniqueFiltCompls = nubOrdBy (uniqueCompl `on` snd . Fuzzy.original) filtCompls
616+
compls = (fmap.fmap.fmap) (mkCompl pId ideOpts uri) uniqueFiltCompls
617+
pId = lookupCommandProvider plugins (CommandId extendImportCommandId)
618+
in
619+
(fmap.fmap) snd $
620+
sortBy (compare `on` lexicographicOrdering) $
621+
mergeListsBy (flip compare `on` score)
622+
[ (fmap.fmap) (notQual,) filtModNameCompls
623+
, (fmap.fmap) (notQual,) filtKeywordCompls
624+
, (fmap.fmap.fmap) (toggleSnippets caps config) compls
625+
]
626+
where
583627
enteredQual = if T.null prefixScope then "" else prefixScope <> "."
584628
fullPrefix = enteredQual <> prefixText
585629

@@ -602,11 +646,9 @@ getCompletions plugins ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls,
602646
$ Fuzzy.simpleFilter chunkSize maxC fullPrefix
603647
$ (if T.null enteredQual then id else mapMaybe (T.stripPrefix enteredQual))
604648
allModNamesAsNS
605-
606-
filtCompls = Fuzzy.filter chunkSize maxC prefixText ctxCompls (label . snd)
607-
where
608-
609-
mcc = case maybe_parsed of
649+
-- If we have a parsed module, use it to determine which completion to show.
650+
maybeContext :: Maybe Context
651+
maybeContext = case maybe_parsed of
610652
Nothing -> Nothing
611653
Just (pm, pmapping) ->
612654
let PositionMapping pDelta = pmapping
@@ -615,7 +657,9 @@ getCompletions plugins ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls,
615657
hpos = upperRange position'
616658
in getCContext lpos pm <|> getCContext hpos pm
617659

618-
660+
filtCompls :: [Scored (Bool, CompItem)]
661+
filtCompls = Fuzzy.filter chunkSize maxC prefixText ctxCompls (label . snd)
662+
where
619663
-- We need the hieast to be "fresh". We can't get types from "stale" hie files, so hasfield won't work,
620664
-- since it gets the record fields from the types.
621665
-- Perhaps this could be fixed with a refactor to GHC's IfaceTyCon, to have it also contain record fields.
@@ -653,7 +697,7 @@ getCompletions plugins ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls,
653697
})
654698

655699
-- completions specific to the current context
656-
ctxCompls' = case mcc of
700+
ctxCompls' = case maybeContext of
657701
Nothing -> compls
658702
Just TypeContext -> filter ( isTypeCompl . snd) compls
659703
Just ValueContext -> filter (not . isTypeCompl . snd) compls
@@ -694,54 +738,36 @@ getCompletions plugins ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls,
694738
, enteredQual `T.isPrefixOf` original label
695739
]
696740

741+
moduleImportListCompletions :: String -> [Scored CompletionItem]
742+
moduleImportListCompletions moduleNameS =
743+
let moduleName = T.pack moduleNameS
744+
funcs = lookupWithDefaultUFM moduleExportsMap HashSet.empty $ mkModuleName moduleNameS
745+
funs = map (show . name) $ HashSet.toList funcs
746+
in filterModuleExports moduleName $ map T.pack funs
747+
748+
filtImportCompls :: [Scored CompletionItem]
697749
filtImportCompls = filtListWith (mkImportCompl enteredQual) importableModules
750+
751+
filterModuleExports :: T.Text -> [T.Text] -> [Scored CompletionItem]
698752
filterModuleExports moduleName = filtListWith $ mkModuleFunctionImport moduleName
753+
754+
filtKeywordCompls :: [Scored CompletionItem]
699755
filtKeywordCompls
700756
| T.null prefixScope = filtListWith mkExtCompl (optKeywords ideOpts)
701757
| otherwise = []
702758

703-
if
704-
-- TODO: handle multiline imports
705-
| "import " `T.isPrefixOf` fullLine
706-
&& (List.length (words (T.unpack fullLine)) >= 2)
707-
&& "(" `isInfixOf` T.unpack fullLine
708-
-> do
709-
let moduleName = words (T.unpack fullLine) !! 1
710-
funcs = lookupWithDefaultUFM moduleExportsMap HashSet.empty $ mkModuleName moduleName
711-
funs = map (renderOcc . name) $ HashSet.toList funcs
712-
return $ filterModuleExports (T.pack moduleName) funs
713-
| "import " `T.isPrefixOf` fullLine
714-
-> return filtImportCompls
715-
-- we leave this condition here to avoid duplications and return empty list
716-
-- since HLS implements these completions (#haskell-language-server/pull/662)
717-
| "{-# " `T.isPrefixOf` fullLine
718-
-> return []
719-
| otherwise -> do
720-
-- assumes that nubOrdBy is stable
721-
let uniqueFiltCompls = nubOrdBy (uniqueCompl `on` snd . Fuzzy.original) filtCompls
722-
let compls = (fmap.fmap.fmap) (mkCompl pId ideOpts uri) uniqueFiltCompls
723-
pId = lookupCommandProvider plugins (CommandId extendImportCommandId)
724-
return $
725-
(fmap.fmap) snd $
726-
sortBy (compare `on` lexicographicOrdering) $
727-
mergeListsBy (flip compare `on` score)
728-
[ (fmap.fmap) (notQual,) filtModNameCompls
729-
, (fmap.fmap) (notQual,) filtKeywordCompls
730-
, (fmap.fmap.fmap) (toggleSnippets caps config) compls
731-
]
732-
where
733-
-- We use this ordering to alphabetically sort suggestions while respecting
734-
-- all the previously applied ordering sources. These are:
735-
-- 1. Qualified suggestions go first
736-
-- 2. Fuzzy score ranks next
737-
-- 3. In-scope completions rank next
738-
-- 4. label alphabetical ordering next
739-
-- 4. detail alphabetical ordering (proxy for module)
740-
lexicographicOrdering Fuzzy.Scored{score, original} =
741-
case original of
742-
(isQual, CompletionItem{_label,_detail}) -> do
743-
let isLocal = maybe False (":" `T.isPrefixOf`) _detail
744-
(Down isQual, Down score, Down isLocal, _label, _detail)
759+
-- We use this ordering to alphabetically sort suggestions while respecting
760+
-- all the previously applied ordering sources. These are:
761+
-- 1. Qualified suggestions go first
762+
-- 2. Fuzzy score ranks next
763+
-- 3. In-scope completions rank next
764+
-- 4. label alphabetical ordering next
765+
-- 4. detail alphabetical ordering (proxy for module)
766+
lexicographicOrdering Fuzzy.Scored{score, original} =
767+
case original of
768+
(isQual, CompletionItem{_label,_detail}) -> do
769+
let isLocal = maybe False (":" `T.isPrefixOf`) _detail
770+
(Down isQual, Down score, Down isLocal, _label, _detail)
745771

746772

747773

0 commit comments

Comments
 (0)