@@ -560,24 +560,76 @@ getCompletions
560
560
-> ClientCapabilities
561
561
-> CompletionsConfig
562
562
-> 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
568
625
fullPrefix = enteredQual <> prefixText
569
626
570
627
-- 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)
572
630
notQual = False
573
631
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
581
633
maxC = maxCompletions config
582
634
583
635
filtModNameCompls :: [Scored CompletionItem ]
@@ -587,15 +639,29 @@ getCompletions plId ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qu
587
639
$ (if T. null enteredQual then id else mapMaybe (T. stripPrefix enteredQual))
588
640
allModNamesAsNS
589
641
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
590
655
maybeContext = case maybe_parsed of
591
656
Nothing -> Nothing
592
657
Just (pm, pmapping) ->
593
658
let PositionMapping pDelta = pmapping
594
- position' = fromDelta pDelta pos
659
+ position' = fromDelta pDelta cursorPos
595
660
lpos = lowerRange position'
596
661
hpos = upperRange position'
597
662
in getCContext lpos pm <|> getCContext hpos pm
598
663
664
+ filtCompls :: [Scored (Bool , CompItem )]
599
665
filtCompls = Fuzzy. filter chunkSize maxC prefixText ctxCompls (label . snd )
600
666
where
601
667
-- completions specific to the current context
@@ -608,10 +674,10 @@ getCompletions plId ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qu
608
674
ctxCompls = (fmap . fmap ) (\ comp -> toggleAutoExtend config $ comp { isInfix = infixCompls }) ctxCompls'
609
675
610
676
infixCompls :: Maybe Backtick
611
- infixCompls = isUsedAsInfix fullLine prefixModule prefixText pos
677
+ infixCompls = isUsedAsInfix fullLine prefixModule prefixText cursorPos
612
678
613
679
PositionMapping bDelta = bmapping
614
- oldPos = fromDelta bDelta $ VFS. cursorPos prefixInfo
680
+ oldPos = fromDelta bDelta cursorPos
615
681
startLoc = lowerRange oldPos
616
682
endLoc = upperRange oldPos
617
683
localCompls = map (uncurry localBindsToCompItem) $ getFuzzyScope localBindings startLoc endLoc
@@ -629,6 +695,7 @@ getCompletions plId ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qu
629
695
else ((qual,) <$> Map. findWithDefault [] prefixModule (getQualCompls qualCompls))
630
696
++ ((notQual,) . ($ Just prefixModule) <$> anyQualCompls)
631
697
698
+ filtListWith :: (T. Text -> CompletionItem ) -> [T. Text ] -> [Scored CompletionItem ]
632
699
filtListWith f list =
633
700
[ fmap f label
634
701
| label <- Fuzzy. simpleFilter chunkSize maxC fullPrefix list
@@ -643,64 +710,31 @@ getCompletions plId ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qu
643
710
in filterModuleExports moduleName $ map T. pack funs
644
711
645
712
-- manually parse in case we don't have completion context ("import [qualified ]ModuleName")
713
+ getModuleName :: T. Text -> Maybe T. Text
646
714
getModuleName line = filter (/= " qualified" ) (T. words line) !? 1
715
+
716
+ filtImportCompls :: [Scored CompletionItem ]
647
717
filtImportCompls = filtListWith (mkImportCompl enteredQual) importableModules
718
+
719
+ filterModuleExports :: T. Text -> [T. Text ] -> [Scored CompletionItem ]
648
720
filterModuleExports moduleName = filtListWith $ mkModuleFunctionImport moduleName
721
+
722
+ filtKeywordCompls :: [Scored CompletionItem ]
649
723
filtKeywordCompls
650
724
| T. null prefixModule = filtListWith mkExtCompl (optKeywords ideOpts)
651
725
| otherwise = []
652
726
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} =
702
736
case original of
703
- (isQual, CompletionItem {_label,_detail}) -> do
737
+ (isQual, CompletionItem {_label,_detail}) -> do
704
738
let isLocal = maybe False (" :" `T.isPrefixOf` ) _detail
705
739
(Down isQual, Down score, Down isLocal, _label, _detail)
706
740
0 commit comments