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