1
1
{-# LANGUAGE OverloadedRecordDot #-}
2
2
{-# LANGUAGE OverloadedStrings #-}
3
3
4
- module Ide.Plugin.SemanticTokens.Tokenize (hieAstSpanIdentifiers ) where
4
+ module Ide.Plugin.SemanticTokens.Tokenize (computeRangeHsSemanticTokenTypeList ) where
5
5
6
6
import Control.Lens (Identity (runIdentity ))
7
- import Control.Monad (forM_ , guard )
7
+ import Control.Monad (foldM , guard )
8
8
import Control.Monad.State.Strict (MonadState (get ),
9
9
MonadTrans (lift ),
10
- execStateT , modify , put )
11
- import Control.Monad.Trans.State.Strict (StateT )
10
+ evalStateT , modify , put )
11
+ import Control.Monad.Trans.State.Strict (StateT , runStateT )
12
12
import Data.Char (isAlphaNum )
13
+ import Data.DList (DList )
14
+ import qualified Data.DList as DL
13
15
import qualified Data.Map.Strict as M
14
16
import qualified Data.Map.Strict as Map
15
- import qualified Data.Set as S
16
17
import Data.Text (Text )
17
18
import qualified Data.Text as T
18
19
import qualified Data.Text.Rope as Char
@@ -22,95 +23,100 @@ import Data.Text.Utf16.Rope.Mixed (Rope)
22
23
import qualified Data.Text.Utf16.Rope.Mixed as Rope
23
24
import Development.IDE.GHC.Compat
24
25
import Development.IDE.GHC.Error (realSrcSpanToCodePointRange )
25
- import Ide.Plugin.SemanticTokens.Types (RangeIdSetMap )
26
+ import Ide.Plugin.SemanticTokens.Types (HsSemanticTokenType (TModule ),
27
+ RangeHsSemanticTokenTypes (.. ))
26
28
import Language.LSP.Protocol.Types (Position (Position ),
27
29
Range (Range ), UInt , mkRange )
28
30
import Language.LSP.VFS hiding (line )
29
31
import Prelude hiding (length , span )
30
32
31
33
type Tokenizer m a = StateT PTokenState m a
34
+ type HsSemanticLookup = Identifier -> Maybe HsSemanticTokenType
32
35
33
36
34
37
data PTokenState = PTokenState
35
- { rangeIdSetMap :: ! RangeIdSetMap ,
36
- rope :: ! Rope , -- the remains of rope we are working on
37
- cursor :: ! Char. Position, -- the cursor position of the current rope to the start of the original file in code point position
38
- columnsInUtf16 :: ! UInt -- the column of the start of the current rope in utf16
38
+ {
39
+ rope :: ! Rope -- the remains of rope we are working on
40
+ , cursor :: ! Char. Position -- the cursor position of the current rope to the start of the original file in code point position
41
+ , columnsInUtf16 :: ! UInt -- the column of the start of the current rope in utf16
39
42
}
40
43
41
- runTokenizer :: (Monad m ) => Tokenizer m a -> PTokenState -> m RangeIdSetMap
42
- runTokenizer p st = rangeIdSetMap <$> execStateT p st
43
-
44
44
data SplitResult
45
45
= NoSplit (Text , Range ) -- does not need to split, token text, token range
46
46
| Split (Text , Range , Range ) -- token text, prefix range(module range), token range
47
47
deriving (Show )
48
48
49
+ getSplitTokenText :: SplitResult -> Text
50
+ getSplitTokenText (NoSplit (t, _)) = t
51
+ getSplitTokenText (Split (t, _, _)) = t
52
+
49
53
50
54
mkPTokenState :: VirtualFile -> PTokenState
51
55
mkPTokenState vf =
52
56
PTokenState
53
- { rangeIdSetMap = mempty ,
57
+ {
54
58
rope = Rope. fromText $ toText vf. _file_text,
55
59
cursor = Char. Position 0 0 ,
56
60
columnsInUtf16 = 0
57
61
}
58
62
59
- addRangeIdSetMap :: (Monad m ) => Range -> Identifier -> Tokenizer m ()
60
- addRangeIdSetMap r i = modify $ \ s -> s {rangeIdSetMap = Map. insertWith (<>) r (S. singleton i) $ rangeIdSetMap s}
61
-
62
- -- lift a Tokenizer Maybe () to Tokenizer m (),
63
- -- if the Maybe is Nothing, do nothing, recover the state
64
- -- if the Maybe is Just (), do the action, and keep the state
65
- liftMaybeM :: (Monad m ) => Tokenizer Maybe () -> Tokenizer m ()
63
+ -- lift a Tokenizer Maybe a to Tokenizer m a,
64
+ -- if the Maybe is Nothing, do nothing, recover the state, and return the mempty value
65
+ -- if the Maybe is Just x, do the action, and keep the state, and return x
66
+ liftMaybeM :: (Monad m , Monoid a ) => Tokenizer Maybe a -> Tokenizer m a
66
67
liftMaybeM p = do
67
68
st <- get
68
- forM_ (execStateT p st) put
69
+ maybe ( return mempty ) ( \ (ans, st') -> put st' >> return ans) $ runStateT p st
69
70
70
- hieAstSpanIdentifiers :: VirtualFile -> HieAST a -> RangeIdSetMap
71
- hieAstSpanIdentifiers vf ast = runIdentity $ runTokenizer (foldAst ast) (mkPTokenState vf)
71
+ foldMapM :: ( Monad m , Monoid b , Foldable t ) => ( a -> m b ) -> t a -> m b
72
+ foldMapM f ta = foldM ( \ b a -> mappend b <$> f a) mempty ta
72
73
74
+ computeRangeHsSemanticTokenTypeList :: HsSemanticLookup -> VirtualFile -> HieAST a -> RangeHsSemanticTokenTypes
75
+ computeRangeHsSemanticTokenTypeList lookupHsTokenType vf ast =
76
+ RangeHsSemanticTokenTypes $ DL. toList $ runIdentity $ evalStateT (foldAst lookupHsTokenType ast) (mkPTokenState vf)
73
77
-- | foldAst
74
78
-- visit every leaf node in the ast in depth first order
75
- foldAst :: (Monad m ) => HieAST t -> Tokenizer m ()
76
- foldAst ast = if null (nodeChildren ast)
77
- then liftMaybeM (visitLeafIds ast)
78
- else mapM_ foldAst $ nodeChildren ast
79
+ foldAst :: (Monad m ) => HsSemanticLookup -> HieAST t -> Tokenizer m (DList ( Range , HsSemanticTokenType ) )
80
+ foldAst lookupHsTokenType ast = if null (nodeChildren ast)
81
+ then liftMaybeM (visitLeafIds lookupHsTokenType ast)
82
+ else foldMapM ( foldAst lookupHsTokenType) $ nodeChildren ast
79
83
80
- visitLeafIds :: HieAST t -> Tokenizer Maybe ()
81
- visitLeafIds leaf = liftMaybeM $ do
84
+ visitLeafIds :: HsSemanticLookup -> HieAST t -> Tokenizer Maybe (DList ( Range , HsSemanticTokenType ) )
85
+ visitLeafIds lookupHsTokenType leaf = liftMaybeM $ do
82
86
let span = nodeSpan leaf
83
87
(ran, token) <- focusTokenAt leaf
84
88
-- if `focusTokenAt` succeed, we can safely assume we have shift the cursor correctly
85
89
-- we do not need to recover the cursor state, even if the following computation failed
86
90
liftMaybeM $ do
87
91
-- only handle the leaf node with single column token
88
92
guard $ srcSpanStartLine span == srcSpanEndLine span
89
- splitResult <- lift $ splitRangeByText token ran
90
- mapM_ (combineNodeIds ran splitResult) $ Map. filterWithKey (\ k _ -> k == SourceInfo ) $ getSourcedNodeInfo $ sourcedNodeInfo leaf
93
+ splitResult <- lift $ splitRangeByText token ran
94
+ foldMapM (combineNodeIds lookupHsTokenType ran splitResult) $ Map. filterWithKey (\ k _ -> k == SourceInfo ) $ getSourcedNodeInfo $ sourcedNodeInfo leaf
91
95
where
92
- combineNodeIds :: (Monad m ) => Range -> SplitResult -> NodeInfo a -> Tokenizer m ()
93
- combineNodeIds ran ranSplit (NodeInfo _ _ bd) = mapM_ (getIdentifier ran ranSplit) (M. keys bd)
94
- getIdentifier :: (Monad m ) => Range -> SplitResult -> Identifier -> Tokenizer m ()
95
- getIdentifier ran ranSplit idt = liftMaybeM $ do
96
+ combineNodeIds :: (Monad m ) => HsSemanticLookup -> Range -> SplitResult -> NodeInfo a -> Tokenizer m (DList (Range , HsSemanticTokenType ))
97
+ combineNodeIds lookupHsTokenType ran ranSplit (NodeInfo _ _ bd) =
98
+ case (maybeTokenType, ranSplit) of
99
+ (Nothing , _) -> return mempty
100
+ (Just TModule , _) -> return $ DL. singleton (ran, TModule )
101
+ (Just tokenType, NoSplit (_, tokenRan)) -> return $ DL. singleton (tokenRan, tokenType)
102
+ (Just tokenType, Split (_, ranPrefix, tokenRan)) -> return $ DL. fromList [(ranPrefix, TModule ),(tokenRan, tokenType)]
103
+ where maybeTokenType = foldMap (getIdentifier lookupHsTokenType ranSplit) (M. keys bd)
104
+
105
+ getIdentifier :: HsSemanticLookup -> SplitResult -> Identifier -> Maybe HsSemanticTokenType
106
+ getIdentifier lookupHsTokenType ranSplit idt = do
96
107
case idt of
97
- Left _moduleName -> addRangeIdSetMap ran idt
108
+ Left _moduleName -> Just TModule
98
109
Right name -> do
99
- occStr <- lift $ T. pack <$> case (occNameString . nameOccName) name of
110
+ occStr <- T. pack <$> case (occNameString . nameOccName) name of
100
111
-- the generated selector name with {-# LANGUAGE DuplicateRecordFields #-}
101
112
' $' : ' s' : ' e' : ' l' : ' :' : xs -> Just $ takeWhile (/= ' :' ) xs
102
113
-- other generated names that should not be visible
103
114
' $' : c : _ | isAlphaNum c -> Nothing
104
115
c : ' :' : _ | isAlphaNum c -> Nothing
105
116
ns -> Just ns
106
- case ranSplit of
107
- (NoSplit (tk, r)) -> do
108
- guard $ tk == occStr
109
- addRangeIdSetMap r idt
110
- (Split (tk, r1, r2)) -> do
111
- guard $ tk == occStr
112
- addRangeIdSetMap r1 (Left $ mkModuleName " " )
113
- addRangeIdSetMap r2 idt
117
+ guard $ getSplitTokenText ranSplit == occStr
118
+ lookupHsTokenType idt
119
+
114
120
115
121
focusTokenAt ::
116
122
-- | leaf node we want to focus on
0 commit comments