@@ -10,6 +10,7 @@ module Development.IDE.Plugin.Completions.Logic (
10
10
, localCompletionsForParsedModule
11
11
, getCompletions
12
12
, fromIdentInfo
13
+ , getCompletionPrefix
13
14
) where
14
15
15
16
import Control.Applicative
@@ -20,7 +21,7 @@ import Data.List.Extra as List hiding
20
21
import qualified Data.Map as Map
21
22
22
23
import Data.Maybe (fromMaybe , isJust ,
23
- mapMaybe )
24
+ mapMaybe , catMaybes )
24
25
import qualified Data.Text as T
25
26
import qualified Text.Fuzzy.Parallel as Fuzzy
26
27
@@ -30,6 +31,8 @@ import Data.Either (fromRight)
30
31
import Data.Function (on )
31
32
import Data.Functor
32
33
import qualified Data.HashMap.Strict as HM
34
+ import qualified Data.Map.Strict as M
35
+
33
36
import qualified Data.HashSet as HashSet
34
37
import Data.Monoid (First (.. ))
35
38
import Data.Ord (Down (Down ))
@@ -66,6 +69,12 @@ import qualified Language.LSP.VFS as VFS
66
69
import Text.Fuzzy.Parallel (Scored (score ),
67
70
original )
68
71
72
+ import Development.IDE
73
+ import Data.Coerce (coerce )
74
+
75
+ import Data.Char (isAlphaNum )
76
+ import qualified Data.Rope.UTF16 as Rope
77
+
69
78
-- Chunk size used for parallelizing fuzzy matching
70
79
chunkSize :: Int
71
80
chunkSize = 1000
@@ -562,28 +571,29 @@ getCompletions
562
571
-> IdeOptions
563
572
-> CachedCompletions
564
573
-> Maybe (ParsedModule , PositionMapping )
574
+ -> Maybe (HieAstResult , PositionMapping )
565
575
-> (Bindings , PositionMapping )
566
- -> VFS. PosPrefixInfo
576
+ -> PosPrefixInfo
567
577
-> ClientCapabilities
568
578
-> CompletionsConfig
569
579
-> HM. HashMap T. Text (HashSet. HashSet IdentInfo )
570
580
-> IO [Scored CompletionItem ]
571
581
getCompletions plugins ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls, qualCompls, importableModules}
572
- maybe_parsed (localBindings, bmapping) prefixInfo caps config moduleExportsMap = do
573
- let VFS. PosPrefixInfo { fullLine, prefixModule , prefixText } = prefixInfo
574
- enteredQual = if T. null prefixModule then " " else prefixModule <> " ."
582
+ maybe_parsed maybe_ast_res (localBindings, bmapping) prefixInfo caps config moduleExportsMap = do
583
+ let PosPrefixInfo { fullLine, prefixScope , prefixText } = prefixInfo
584
+ enteredQual = if T. null prefixScope then " " else prefixScope <> " ."
575
585
fullPrefix = enteredQual <> prefixText
576
586
577
587
-- Boolean labels to tag suggestions as qualified (or not)
578
- qual = not (T. null prefixModule )
588
+ qual = not (T. null prefixScope )
579
589
notQual = False
580
590
581
591
{- correct the position by moving 'foo :: Int -> String -> '
582
592
^
583
593
to 'foo :: Int -> String -> '
584
594
^
585
595
-}
586
- pos = VFS. cursorPos prefixInfo
596
+ pos = cursorPos prefixInfo
587
597
588
598
maxC = maxCompletions config
589
599
@@ -605,6 +615,53 @@ getCompletions plugins ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls,
605
615
lpos = lowerRange position'
606
616
hpos = upperRange position'
607
617
in getCContext lpos pm <|> getCContext hpos pm
618
+
619
+ dotFieldSelectorToCompl :: T. Text -> (Bool , CompItem )
620
+ dotFieldSelectorToCompl label = (True , CI CiVariable label (ImportedFrom T. empty) Nothing label Nothing emptySpanDoc False Nothing )
621
+
622
+ -- we need the hieast to be fresh
623
+ -- not fresh, hasfield won't have a chance. it would to another larger change to ghc IfaceTyCon to contain record fields
624
+ tst :: [(Bool , CompItem )]
625
+ tst = case maybe_ast_res of
626
+ Just (HAR {hieAst = hieast, hieKind = HieFresh },_) -> concat $ pointCommand hieast (completionPrefixPos prefixInfo) (theFunc HieFresh )
627
+ _ -> []
628
+
629
+ getSels :: GHC. TyCon -> [T. Text ]
630
+ getSels tycon = let f fieldLabel = printOutputable fieldLabel
631
+ in map f $ tyConFieldLabels tycon
632
+
633
+ theFunc :: HieKind Type -> HieAST Type -> [(Bool , CompItem )]
634
+ theFunc kind node = concatMap g (nodeType $ nodeInfoH kind node)
635
+ where
636
+ g :: Type -> [(Bool , CompItem )]
637
+ g (TyConApp theTyCon _) = map dotFieldSelectorToCompl $ getSels theTyCon
638
+ g _ = []
639
+
640
+ nodeInfoH :: HieKind a -> HieAST a -> NodeInfo a
641
+ nodeInfoH (HieFromDisk _) = nodeInfo'
642
+ nodeInfoH HieFresh = nodeInfo
643
+
644
+ pointCommand :: HieASTs t -> Position -> (HieAST t -> a ) -> [a ]
645
+ pointCommand hf pos k =
646
+ catMaybes $ M. elems $ flip M. mapWithKey (getAsts hf) $ \ fs ast ->
647
+ -- Since GHC 9.2:
648
+ -- getAsts :: Map HiePath (HieAst a)
649
+ -- type HiePath = LexialFastString
650
+ --
651
+ -- but before:
652
+ -- getAsts :: Map HiePath (HieAst a)
653
+ -- type HiePath = FastString
654
+ --
655
+ -- 'coerce' here to avoid an additional function for maintaining
656
+ -- backwards compatibility.
657
+ case selectSmallestContaining (sp $ coerce fs) ast of
658
+ Nothing -> Nothing
659
+ Just ast' -> Just $ k ast'
660
+ where
661
+ sloc fs = mkRealSrcLoc fs (fromIntegral $ line+ 1 ) (fromIntegral $ cha+ 1 )
662
+ sp fs = mkRealSrcSpan (sloc fs) (sloc fs)
663
+ line = _line pos
664
+ cha = _character pos
608
665
609
666
-- completions specific to the current context
610
667
ctxCompls' = case mcc of
@@ -616,10 +673,10 @@ getCompletions plugins ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls,
616
673
ctxCompls = (fmap . fmap ) (\ comp -> toggleAutoExtend config $ comp { isInfix = infixCompls }) ctxCompls'
617
674
618
675
infixCompls :: Maybe Backtick
619
- infixCompls = isUsedAsInfix fullLine prefixModule prefixText pos
676
+ infixCompls = isUsedAsInfix fullLine prefixScope prefixText pos
620
677
621
678
PositionMapping bDelta = bmapping
622
- oldPos = fromDelta bDelta $ VFS. cursorPos prefixInfo
679
+ oldPos = fromDelta bDelta $ cursorPos prefixInfo
623
680
startLoc = lowerRange oldPos
624
681
endLoc = upperRange oldPos
625
682
localCompls = map (uncurry localBindsToCompItem) $ getFuzzyScope localBindings startLoc endLoc
@@ -632,10 +689,11 @@ getCompletions plugins ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls,
632
689
ty = showForSnippet <$> typ
633
690
thisModName = Local $ nameSrcSpan name
634
691
635
- compls = if T. null prefixModule
636
- then map (notQual,) localCompls ++ map (qual,) unqualCompls ++ ((notQual,) . ($ Nothing ) <$> anyQualCompls)
637
- else ((qual,) <$> Map. findWithDefault [] prefixModule (getQualCompls qualCompls))
638
- ++ ((notQual,) . ($ Just prefixModule) <$> anyQualCompls)
692
+ compls
693
+ | T. null prefixScope = map (notQual,) localCompls ++ map (qual,) unqualCompls ++ ((notQual,) . ($ Nothing ) <$> anyQualCompls)
694
+ | not $ null tst = tst
695
+ | otherwise = ((qual,) <$> Map. findWithDefault [] prefixScope (getQualCompls qualCompls))
696
+ ++ ((notQual,) . ($ Just prefixScope) <$> anyQualCompls)
639
697
640
698
filtListWith f list =
641
699
[ fmap f label
@@ -646,7 +704,7 @@ getCompletions plugins ideOpts CC {allModNamesAsNS, anyQualCompls, unqualCompls,
646
704
filtImportCompls = filtListWith (mkImportCompl enteredQual) importableModules
647
705
filterModuleExports moduleName = filtListWith $ mkModuleFunctionImport moduleName
648
706
filtKeywordCompls
649
- | T. null prefixModule = filtListWith mkExtCompl (optKeywords ideOpts)
707
+ | T. null prefixScope = filtListWith mkExtCompl (optKeywords ideOpts)
650
708
| otherwise = []
651
709
652
710
if
@@ -890,3 +948,33 @@ mergeListsBy cmp all_lists = merge_lists all_lists
890
948
[] -> []
891
949
[xs] -> xs
892
950
lists' -> merge_lists lists'
951
+
952
+
953
+ getCompletionPrefix :: (Monad m ) => Position -> VFS. VirtualFile -> m (Maybe PosPrefixInfo )
954
+ getCompletionPrefix pos@ (Position l c) (VFS. VirtualFile _ _ ropetext) =
955
+ return $ Just $ fromMaybe (PosPrefixInfo " " " " " " pos) $ do -- Maybe monad
956
+ let headMaybe [] = Nothing
957
+ headMaybe (x: _) = Just x
958
+ lastMaybe [] = Nothing
959
+ lastMaybe xs = Just $ last xs
960
+
961
+ curLine <- headMaybe $ T. lines $ Rope. toText
962
+ $ fst $ Rope. splitAtLine 1 $ snd $ Rope. splitAtLine (fromIntegral l) ropetext
963
+ let beforePos = T. take (fromIntegral c) curLine
964
+ curWord <-
965
+ if | T. null beforePos -> Just " "
966
+ | T. last beforePos == ' ' -> Just " " -- don't count abc as the curword in 'abc '
967
+ | otherwise -> lastMaybe (T. words beforePos)
968
+
969
+ let parts = T. split (== ' .' )
970
+ $ T. takeWhileEnd (\ x -> isAlphaNum x || x `elem` (" ._'" :: String )) curWord
971
+ case reverse parts of
972
+ [] -> Nothing
973
+ (x: xs) -> do
974
+ let modParts = dropWhile (\ _ -> False )
975
+ $ reverse $ filter (not . T. null ) xs
976
+ modName = T. intercalate " ." modParts
977
+ return $ PosPrefixInfo { fullLine = curLine, prefixScope = modName, prefixText = x, cursorPos = pos }
978
+
979
+ completionPrefixPos :: PosPrefixInfo -> Position
980
+ completionPrefixPos PosPrefixInfo { cursorPos = Position ln co, prefixText = str} = Position ln (co - (fromInteger . toInteger . T. length $ str) - 1 )
0 commit comments