@@ -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