@@ -560,24 +560,72 @@ 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+ -- manual parsing (doesn't require parsed module and work with multiline)
583+ | " import " `T.isPrefixOf` fullLine
584+ , Just moduleName <- getModuleName fullLine
585+ , " (" `T.isInfixOf` fullLine
586+ = moduleImportListCompletions $ T. unpack moduleName
587+
588+ -- ------------------------------------------------------------------------
589+ -- IMPORT MODULENAM|
590+ | Just (ImportContext _moduleName) <- maybeContext
591+ = filtImportCompls
592+
593+ -- manual parsing (doesn't require parsed module and work with multiline)
594+ | " import " `T.isPrefixOf` fullLine
595+ = filtImportCompls
596+
597+ -- ------------------------------------------------------------------------
598+ -- {-# LA| #-}
599+ -- we leave this condition here to avoid duplications and return empty list
600+ -- since HLS implements these completions (#haskell-language-server/pull/662)
601+ | " {-# " `T.isPrefixOf` fullLine
602+ = []
603+
604+ -- ------------------------------------------------------------------------
605+ | otherwise =
606+ -- assumes that nubOrdBy is stable
607+ let uniqueFiltCompls = nubOrdBy (uniqueCompl `on` snd . Fuzzy. original) filtCompls
608+ compls = (fmap . fmap . fmap ) (mkCompl plId ideOpts) uniqueFiltCompls
609+ in (fmap . fmap ) snd $
610+ sortBy (compare `on` lexicographicOrdering) $
611+ mergeListsBy (flip compare `on` score)
612+ [ (fmap . fmap ) (notQual,) filtModNameCompls
613+ , (fmap . fmap ) (notQual,) filtKeywordCompls
614+ , (fmap . fmap . fmap ) (toggleSnippets caps config) compls
615+ ]
616+ where
617+ -- construct the qualified completion (do not confuse with qualified import)
618+ enteredQual :: T. Text
619+ enteredQual = if qual then prefixModule <> " ." else " "
620+ fullPrefix :: T. Text
568621 fullPrefix = enteredQual <> prefixText
569622
570623 -- Boolean labels to tag suggestions as qualified (or not)
571- qual = not (T. null prefixModule)
624+ qual , notQual :: Bool
625+ qual = not (T. null prefixModule)
572626 notQual = False
573627
574- {- correct the position by moving 'foo :: Int -> String -> '
575- ^
576- to 'foo :: Int -> String -> '
577- ^
578- -}
579- pos = VFS. cursorPos prefixInfo
580-
628+ maxC :: Int
581629 maxC = maxCompletions config
582630
583631 filtModNameCompls :: [Scored CompletionItem ]
@@ -587,15 +635,29 @@ getCompletions plId ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qu
587635 $ (if T. null enteredQual then id else mapMaybe (T. stripPrefix enteredQual))
588636 allModNamesAsNS
589637
638+ -- ----------------------------------------
639+ -- Note: correct the cursorPos by moving
640+ --
641+ -- 'foo :: Int -> String -> '
642+ -- ^
643+ -- to
644+ --
645+ -- 'foo :: Int -> String -> '
646+ -- ^
647+ -- ----------------------------------------
648+
649+ -- If we have a parsed module, use it to determine which completion to show.
650+ maybeContext :: Maybe Context
590651 maybeContext = case maybe_parsed of
591652 Nothing -> Nothing
592653 Just (pm, pmapping) ->
593654 let PositionMapping pDelta = pmapping
594- position' = fromDelta pDelta pos
655+ position' = fromDelta pDelta cursorPos
595656 lpos = lowerRange position'
596657 hpos = upperRange position'
597658 in getCContext lpos pm <|> getCContext hpos pm
598659
660+ filtCompls :: [Scored (Bool , CompItem )]
599661 filtCompls = Fuzzy. filter chunkSize maxC prefixText ctxCompls (label . snd )
600662 where
601663 -- completions specific to the current context
@@ -608,10 +670,10 @@ getCompletions plId ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qu
608670 ctxCompls = (fmap . fmap ) (\ comp -> toggleAutoExtend config $ comp { isInfix = infixCompls }) ctxCompls'
609671
610672 infixCompls :: Maybe Backtick
611- infixCompls = isUsedAsInfix fullLine prefixModule prefixText pos
673+ infixCompls = isUsedAsInfix fullLine prefixModule prefixText cursorPos
612674
613675 PositionMapping bDelta = bmapping
614- oldPos = fromDelta bDelta $ VFS. cursorPos prefixInfo
676+ oldPos = fromDelta bDelta cursorPos
615677 startLoc = lowerRange oldPos
616678 endLoc = upperRange oldPos
617679 localCompls = map (uncurry localBindsToCompItem) $ getFuzzyScope localBindings startLoc endLoc
@@ -629,6 +691,7 @@ getCompletions plId ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qu
629691 else ((qual,) <$> Map. findWithDefault [] prefixModule (getQualCompls qualCompls))
630692 ++ ((notQual,) . ($ Just prefixModule) <$> anyQualCompls)
631693
694+ filtListWith :: (T. Text -> CompletionItem ) -> [T. Text ] -> [Scored CompletionItem ]
632695 filtListWith f list =
633696 [ fmap f label
634697 | label <- Fuzzy. simpleFilter chunkSize maxC fullPrefix list
@@ -643,64 +706,31 @@ getCompletions plId ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qu
643706 in filterModuleExports moduleName $ map T. pack funs
644707
645708 -- manually parse in case we don't have completion context ("import [qualified ]ModuleName")
709+ getModuleName :: T. Text -> Maybe T. Text
646710 getModuleName line = filter (/= " qualified" ) (T. words line) !? 1
711+
712+ filtImportCompls :: [Scored CompletionItem ]
647713 filtImportCompls = filtListWith (mkImportCompl enteredQual) importableModules
714+
715+ filterModuleExports :: T. Text -> [T. Text ] -> [Scored CompletionItem ]
648716 filterModuleExports moduleName = filtListWith $ mkModuleFunctionImport moduleName
717+
718+ filtKeywordCompls :: [Scored CompletionItem ]
649719 filtKeywordCompls
650720 | T. null prefixModule = filtListWith mkExtCompl (optKeywords ideOpts)
651721 | otherwise = []
652722
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} =
723+ -- We use this ordering to alphabetically sort suggestions while respecting
724+ -- all the previously applied ordering sources. These are:
725+ -- 1. Qualified suggestions go first
726+ -- 2. Fuzzy score ranks next
727+ -- 3. In-scope completions rank next
728+ -- 4. label alphabetical ordering next
729+ -- 4. detail alphabetical ordering (proxy for module)
730+ lexicographicOrdering :: Scored (a , CompletionItem ) -> (Down a , Down Int , Down Bool , T. Text , Maybe T. Text )
731+ lexicographicOrdering Fuzzy. Scored {score, original} =
702732 case original of
703- (isQual, CompletionItem {_label,_detail}) -> do
733+ (isQual, CompletionItem {_label,_detail}) -> do
704734 let isLocal = maybe False (" :" `T.isPrefixOf` ) _detail
705735 (Down isQual, Down score, Down isLocal, _label, _detail)
706736
0 commit comments