1
1
from __future__ import annotations
2
2
3
+ import ast
3
4
import asyncio
4
5
import itertools
5
6
import operator
12
13
Dict ,
13
14
FrozenSet ,
14
15
Generator ,
15
- Iterable ,
16
16
NamedTuple ,
17
17
Optional ,
18
18
Set ,
19
19
Tuple ,
20
20
Union ,
21
+ cast ,
21
22
)
22
23
23
24
from ....utils .async_event import CancelationToken
33
34
SemanticTokensPartialResult ,
34
35
SemanticTokenTypes ,
35
36
)
36
- from ..utils .ast import Token , token_in_range
37
+ from ..utils .ast import HasTokens , Token , iter_nodes , token_in_range
37
38
38
39
if TYPE_CHECKING :
39
40
from ..protocol import RobotLanguageServerProtocol
@@ -171,26 +172,40 @@ def mapping(cls) -> Dict[str, Tuple[Enum, Optional[Set[Enum]]]]:
171
172
172
173
@classmethod
173
174
def generate_sem_sub_tokens (
174
- cls , token : Token , col_offset : Optional [int ] = None , length : Optional [int ] = None
175
+ cls , token : Token , node : ast . AST , col_offset : Optional [int ] = None , length : Optional [int ] = None
175
176
) -> Generator [SemTokenInfo , None , None ]:
176
177
from robot .parsing .lexer .tokens import Token as RobotToken
178
+ from robot .parsing .model .statements import (
179
+ Documentation ,
180
+ Fixture ,
181
+ LibraryImport ,
182
+ Metadata ,
183
+ ResourceImport ,
184
+ VariablesImport ,
185
+ )
177
186
from robot .variables .search import is_variable
178
187
179
188
sem_info = cls .mapping ().get (token .type , None ) if token .type is not None else None
180
-
181
189
if sem_info is not None :
190
+ sem_type , sem_mod = sem_info
191
+
192
+ if isinstance (node , (Documentation , Metadata )):
193
+ sem_mod = {SemanticTokenModifiers .DOCUMENTATION }
194
+
182
195
if token .type == RobotToken .VARIABLE :
183
196
if col_offset is None :
184
197
col_offset = token .col_offset
185
198
if length is None :
186
199
length = token .end_col_offset - token .col_offset
187
200
188
201
if is_variable (token .value ):
189
- yield SemTokenInfo (token .lineno , col_offset , 2 , RobotSemTokenTypes .VARIABLE_BEGIN )
190
- yield SemTokenInfo .from_token (token , sem_info [0 ], sem_info [1 ], col_offset + 2 , length - 3 )
191
- yield SemTokenInfo (token .lineno , col_offset + length - 1 , 1 , RobotSemTokenTypes .VARIABLE_END )
202
+ yield SemTokenInfo (token .lineno , col_offset , 2 , RobotSemTokenTypes .VARIABLE_BEGIN , sem_mod )
203
+ yield SemTokenInfo .from_token (token , sem_type , sem_mod , col_offset + 2 , length - 3 )
204
+ yield SemTokenInfo (
205
+ token .lineno , col_offset + length - 1 , 1 , RobotSemTokenTypes .VARIABLE_END , sem_mod
206
+ )
192
207
else :
193
- yield SemTokenInfo .from_token (token , sem_info [ 0 ], sem_info [ 1 ] )
208
+ yield SemTokenInfo .from_token (token , sem_type , sem_mod )
194
209
195
210
elif token .type == RobotToken .ARGUMENT and "\\ " in token .value :
196
211
if col_offset is None :
@@ -206,7 +221,7 @@ def generate_sem_sub_tokens(
206
221
col_offset + g .start (),
207
222
g .end () - g .start (),
208
223
)
209
- elif token .type == RobotToken .KEYWORD :
224
+ elif token .type == RobotToken .KEYWORD or ( token . type == RobotToken . NAME and isinstance ( node , Fixture )) :
210
225
if col_offset is None :
211
226
col_offset = token .col_offset
212
227
if length is None :
@@ -225,7 +240,7 @@ def generate_sem_sub_tokens(
225
240
if token .value [:index ].casefold () == "BuiltIn" .casefold ()
226
241
else None ,
227
242
)
228
- yield SemTokenInfo (token .lineno , col_offset + index , 1 , RobotSemTokenTypes .SEPARATOR )
243
+ yield SemTokenInfo (token .lineno , col_offset + index , 1 , RobotSemTokenTypes .SEPARATOR , sem_mod )
229
244
230
245
new_index = token .value .find ("." , index + 1 )
231
246
if new_index >= 0 :
@@ -234,58 +249,67 @@ def generate_sem_sub_tokens(
234
249
else :
235
250
break
236
251
237
- yield SemTokenInfo .from_token (
238
- token , sem_info [ 0 ], sem_info [ 1 ], col_offset + index + 1 , length - index - 1
239
- )
252
+ yield SemTokenInfo .from_token (token , sem_type , sem_mod , col_offset + index + 1 , length - index - 1 )
253
+ elif token . type == RobotToken . NAME and isinstance ( node , ( LibraryImport , ResourceImport , VariablesImport )):
254
+ yield SemTokenInfo . from_token ( token , RobotSemTokenTypes . NAMESPACE , sem_mod , col_offset , length )
240
255
else :
241
- yield SemTokenInfo .from_token (token , sem_info [ 0 ], sem_info [ 1 ] , col_offset , length )
256
+ yield SemTokenInfo .from_token (token , sem_type , sem_mod , col_offset , length )
242
257
243
258
@classmethod
244
- def generate_sem_tokens (cls , token : Token ) -> Generator [SemTokenInfo , None , None ]:
259
+ def generate_sem_tokens (cls , token : Token , node : ast . AST ) -> Generator [SemTokenInfo , None , None ]:
245
260
from robot .parsing .lexer .tokens import Token as RobotToken
246
261
247
262
if token .type in RobotToken .ALLOW_VARIABLES :
248
263
last_sub_token = token
249
264
try :
250
265
for sub_token in token .tokenize_variables ():
251
266
last_sub_token = sub_token
252
- for e in cls .generate_sem_sub_tokens (sub_token ):
267
+ for e in cls .generate_sem_sub_tokens (sub_token , node ):
253
268
yield e
254
269
except BaseException :
255
270
pass
256
271
if last_sub_token == token :
257
- for e in cls .generate_sem_sub_tokens (last_sub_token ):
272
+ for e in cls .generate_sem_sub_tokens (last_sub_token , node ):
258
273
yield e
259
274
elif last_sub_token is not None and last_sub_token .end_col_offset < token .end_col_offset :
260
275
for e in cls .generate_sem_sub_tokens (
261
276
token ,
277
+ node ,
262
278
last_sub_token .end_col_offset ,
263
279
token .end_col_offset - last_sub_token .end_col_offset - last_sub_token .col_offset ,
264
280
):
265
281
yield e
266
282
267
283
else :
268
- for e in cls .generate_sem_sub_tokens (token ):
284
+ for e in cls .generate_sem_sub_tokens (token , node ):
269
285
yield e
270
286
271
287
def collect (
272
- self , tokens : Iterable [ Token ] , range : Optional [Range ], cancel_token : CancelationToken
288
+ self , model : ast . AST , range : Optional [Range ], cancel_token : CancelationToken
273
289
) -> Union [SemanticTokens , SemanticTokensPartialResult , None ]:
274
290
275
291
data = []
276
292
last_line = 0
277
293
last_col = 0
278
294
279
- for robot_token in itertools .takewhile (
280
- lambda t : not cancel_token .throw_if_canceled () and (range is None or token_in_range (t , range )),
295
+ def get_tokens () -> Generator [Tuple [Token , ast .AST ], None , None ]:
296
+ for node in iter_nodes (model ):
297
+ if isinstance (node , HasTokens ):
298
+ for token in cast (HasTokens , node ).tokens :
299
+ yield token , node
300
+
301
+ for robot_token , robot_node in itertools .takewhile (
302
+ lambda t : not cancel_token .throw_if_canceled () and (range is None or token_in_range (t [0 ], range )),
281
303
itertools .dropwhile (
282
- lambda t : not cancel_token .throw_if_canceled () and range is not None and not token_in_range (t , range ),
283
- tokens ,
304
+ lambda t : not cancel_token .throw_if_canceled ()
305
+ and range is not None
306
+ and not token_in_range (t [0 ], range ),
307
+ get_tokens (),
284
308
),
285
309
):
286
310
cancel_token .throw_if_canceled ()
287
311
288
- for token in self .generate_sem_tokens (robot_token ):
312
+ for token in self .generate_sem_tokens (robot_token , robot_node ):
289
313
current_line = token .lineno - 1
290
314
291
315
data .append (current_line - last_line )
@@ -321,7 +345,7 @@ async def collect_threading(
321
345
try :
322
346
cancel_token = CancelationToken ()
323
347
return await asyncio .get_event_loop ().run_in_executor (
324
- None , self .collect , await self .parent .documents_cache .get_tokens (document ), range , cancel_token
348
+ None , self .collect , await self .parent .documents_cache .get_model (document ), range , cancel_token
325
349
)
326
350
except BaseException :
327
351
cancel_token .cancel ()
0 commit comments