@@ -560,24 +560,74 @@ 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+ | Just (ImportListContext moduleName) <- maybeContext
577+ = moduleImportListCompletions moduleName
578+
579+ | Just (ImportHidingContext moduleName) <- maybeContext
580+ = moduleImportListCompletions moduleName
581+
582+ -- TODO: Is manual parsing ever needed or is context always present for module?
583+ -- If possible only keep the above.
584+ | "import " `T.isPrefixOf` fullLine
585+ , Just moduleName <- getModuleName fullLine
586+ , "(" `T.isInfixOf` fullLine
587+ = moduleImportListCompletions $ T.unpack moduleName
588+
589+ -- ------------------------------------------------------------------------
590+ -- IMPORT MODULENAM|
591+ | Just (ImportContext _moduleName) <- maybeContext
592+ = filtImportCompls
593+
594+ -- TODO: Can we avoid this manual parsing?
595+ -- If possible only keep the above.
596+ | "import " `T.isPrefixOf` fullLine
597+ = filtImportCompls
598+
599+ -- ------------------------------------------------------------------------
600+ -- {-# LA| #-}
601+ -- we leave this condition here to avoid duplications and return empty list
602+ -- since HLS implements these completions (#haskell-language-server/pull/662)
603+ | "{-# " `T.isPrefixOf` fullLine
604+ = []
605+
606+ -- ------------------------------------------------------------------------
607+ | otherwise =
608+ -- assumes that nubOrdBy is stable
609+ let uniqueFiltCompls = nubOrdBy (uniqueCompl `on` snd . Fuzzy.original) filtCompls
610+ compls = (fmap.fmap.fmap) (mkCompl plId ideOpts) uniqueFiltCompls
611+ in (fmap.fmap) snd $
612+ sortBy (compare `on` lexicographicOrdering) $
613+ mergeListsBy (flip compare `on` score)
614+ [ (fmap.fmap) (notQual,) filtModNameCompls
615+ , (fmap.fmap) (notQual,) filtKeywordCompls
616+ , (fmap.fmap.fmap) (toggleSnippets caps config) compls
617+ ]
618+ where
619+ -- construct the qualified completion (do not confuse with qualified import)
620+ enteredQual :: T.Text
621+ enteredQual = if qual then prefixModule <> "." else ""
622+ fullPrefix :: T.Text
568623 fullPrefix = enteredQual <> prefixText
569624
570625 -- Boolean labels to tag suggestions as qualified (or not)
571- qual = not(T.null prefixModule)
626+ qual, notQual :: Bool
627+ qual = not (T.null prefixModule)
572628 notQual = False
573629
574- {- correct the position by moving 'foo :: Int -> String -> '
575- ^
576- to 'foo :: Int -> String -> '
577- ^
578- -}
579- pos = VFS.cursorPos prefixInfo
580-
630+ maxC :: Int
581631 maxC = maxCompletions config
582632
583633 filtModNameCompls :: [Scored CompletionItem]
@@ -587,15 +637,29 @@ getCompletions plId ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qu
587637 $ (if T.null enteredQual then id else mapMaybe (T.stripPrefix enteredQual))
588638 allModNamesAsNS
589639
640+ -- ----------------------------------------
641+ -- Note: correct the cursorPos by moving
642+ --
643+ -- 'foo :: Int -> String -> '
644+ -- ^
645+ -- to
646+ --
647+ -- 'foo :: Int -> String -> '
648+ -- ^
649+ -- ----------------------------------------
650+
651+ -- If we have a parsed module, use it to determine which completion to show.
652+ maybeContext :: Maybe Context
590653 maybeContext = case maybe_parsed of
591654 Nothing -> Nothing
592655 Just (pm, pmapping) ->
593656 let PositionMapping pDelta = pmapping
594- position' = fromDelta pDelta pos
657+ position' = fromDelta pDelta cursorPos
595658 lpos = lowerRange position'
596659 hpos = upperRange position'
597660 in getCContext lpos pm <|> getCContext hpos pm
598661
662+ filtCompls :: [Scored (Bool, CompItem)]
599663 filtCompls = Fuzzy.filter chunkSize maxC prefixText ctxCompls (label . snd)
600664 where
601665 -- completions specific to the current context
@@ -608,10 +672,10 @@ getCompletions plId ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qu
608672 ctxCompls = (fmap.fmap) (\comp -> toggleAutoExtend config $ comp { isInfix = infixCompls }) ctxCompls'
609673
610674 infixCompls :: Maybe Backtick
611- infixCompls = isUsedAsInfix fullLine prefixModule prefixText pos
675+ infixCompls = isUsedAsInfix fullLine prefixModule prefixText cursorPos
612676
613677 PositionMapping bDelta = bmapping
614- oldPos = fromDelta bDelta $ VFS. cursorPos prefixInfo
678+ oldPos = fromDelta bDelta cursorPos
615679 startLoc = lowerRange oldPos
616680 endLoc = upperRange oldPos
617681 localCompls = map (uncurry localBindsToCompItem) $ getFuzzyScope localBindings startLoc endLoc
@@ -629,6 +693,7 @@ getCompletions plId ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qu
629693 else ((qual,) <$> Map.findWithDefault [] prefixModule (getQualCompls qualCompls))
630694 ++ ((notQual,) . ($ Just prefixModule) <$> anyQualCompls)
631695
696+ filtListWith :: (T.Text -> CompletionItem) -> [T.Text] -> [Scored CompletionItem]
632697 filtListWith f list =
633698 [ fmap f label
634699 | label <- Fuzzy.simpleFilter chunkSize maxC fullPrefix list
@@ -643,64 +708,31 @@ getCompletions plId ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qu
643708 in filterModuleExports moduleName $ map T.pack funs
644709
645710 -- manually parse in case we don't have completion context ("import [qualified ]ModuleName")
711+ getModuleName :: T.Text -> Maybe T.Text
646712 getModuleName line = filter (/= "qualified") (T.words line) !? 1
713+
714+ filtImportCompls :: [Scored CompletionItem]
647715 filtImportCompls = filtListWith (mkImportCompl enteredQual) importableModules
716+
717+ filterModuleExports :: T.Text -> [T.Text] -> [Scored CompletionItem]
648718 filterModuleExports moduleName = filtListWith $ mkModuleFunctionImport moduleName
719+
720+ filtKeywordCompls :: [Scored CompletionItem]
649721 filtKeywordCompls
650722 | T.null prefixModule = filtListWith mkExtCompl (optKeywords ideOpts)
651723 | otherwise = []
652724
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} =
725+ -- We use this ordering to alphabetically sort suggestions while respecting
726+ -- all the previously applied ordering sources. These are:
727+ -- 1. Qualified suggestions go first
728+ -- 2. Fuzzy score ranks next
729+ -- 3. In-scope completions rank next
730+ -- 4. label alphabetical ordering next
731+ -- 4. detail alphabetical ordering (proxy for module)
732+ lexicographicOrdering :: Scored (a, CompletionItem) -> (Down a, Down Int, Down Bool, T.Text, Maybe T.Text)
733+ lexicographicOrdering Fuzzy.Scored{score, original} =
702734 case original of
703- (isQual, CompletionItem{_label,_detail}) -> do
735+ (isQual, CompletionItem{_label,_detail}) -> do
704736 let isLocal = maybe False (":" `T.isPrefixOf`) _detail
705737 (Down isQual, Down score, Down isLocal, _label, _detail)
706738
0 commit comments