2323from robotcode .core .utils .inspect import iter_methods
2424from robotcode .language_server .common .decorators import code_action_kinds , command , language_id
2525from robotcode .language_server .common .text_document import TextDocument
26- from robotcode .language_server .robotframework .utils .ast_utils import Token , get_node_at_position , range_from_node
26+ from robotcode .language_server .robotframework .utils .ast_utils import (
27+ Token ,
28+ get_node_at_position ,
29+ get_nodes_at_position ,
30+ range_from_node ,
31+ range_from_token ,
32+ )
2733from robotcode .language_server .robotframework .utils .async_ast import Visitor
2834
2935from .model_helper import ModelHelperMixin
@@ -78,7 +84,7 @@ def __init__(self, parent: RobotLanguageServerProtocol) -> None:
7884 self .parent .commands .register_all (self )
7985
8086 @language_id ("robotframework" )
81- @code_action_kinds ([CodeActionKind .QUICK_FIX ])
87+ @code_action_kinds ([CodeActionKind .QUICK_FIX , "other" ])
8288 async def collect (
8389 self , sender : Any , document : TextDocument , range : Range , context : CodeActionContext
8490 ) -> Optional [List [Union [Command , CodeAction ]]]:
@@ -96,24 +102,31 @@ async def collect(
96102 async def code_action_create_keyword (
97103 self , sender : Any , document : TextDocument , range : Range , context : CodeActionContext
98104 ) -> Optional [List [Union [Command , CodeAction ]]]:
99- kw_not_found_in_diagnostics = next ((d for d in context .diagnostics if d .code == "KeywordNotFoundError" ), None )
100-
101- if kw_not_found_in_diagnostics and (
102- (context .only and CodeActionKind .QUICK_FIX .value in context .only )
105+ if range .start == range .end and (
106+ (context .only and CodeActionKind .QUICK_FIX in context .only )
103107 or context .trigger_kind in [CodeActionTriggerKind .INVOKED , CodeActionTriggerKind .AUTOMATIC ]
104108 ):
105- return [
106- CodeAction (
107- "Create Keyword" ,
108- kind = CodeActionKind .QUICK_FIX ,
109- command = Command (
110- self .parent .commands .get_command_name (self .create_keyword_command ),
111- self .parent .commands .get_command_name (self .create_keyword_command ),
112- [document .document_uri , range ],
113- ),
114- diagnostics = [kw_not_found_in_diagnostics ],
115- )
116- ]
109+ diagnostics = next (
110+ (
111+ d
112+ for d in context .diagnostics
113+ if d .source == "robotcode.namespace" and d .code == "KeywordNotFoundError"
114+ ),
115+ None ,
116+ )
117+ if diagnostics is not None :
118+ return [
119+ CodeAction (
120+ "Create Keyword" ,
121+ kind = CodeActionKind .QUICK_FIX ,
122+ command = Command (
123+ self .parent .commands .get_command_name (self .create_keyword_command ),
124+ self .parent .commands .get_command_name (self .create_keyword_command ),
125+ [document .document_uri , range ],
126+ ),
127+ diagnostics = [diagnostics ],
128+ )
129+ ]
117130
118131 return None
119132
@@ -133,8 +146,6 @@ async def create_keyword_command(self, document_uri: DocumentUri, range: Range)
133146 if document is None :
134147 return
135148
136- namespace = await self .parent .documents_cache .get_namespace (document )
137-
138149 model = await self .parent .documents_cache .get_model (document , False )
139150 node = await get_node_at_position (model , range .start )
140151
@@ -148,6 +159,8 @@ async def create_keyword_command(self, document_uri: DocumentUri, range: Range)
148159 if keyword_token is None :
149160 return
150161
162+ namespace = await self .parent .documents_cache .get_namespace (document )
163+
151164 bdd_token , token = self .split_bdd_prefix (namespace , keyword_token )
152165 if bdd_token is not None and token is not None :
153166 keyword = token .value
@@ -205,11 +218,250 @@ async def create_keyword_command(self, document_uri: DocumentUri, range: Range)
205218 change_annotations = {"create_keyword" : ChangeAnnotation ("Create Keyword" , False )},
206219 )
207220
208- await self .parent .workspace .apply_edit (we , "Rename Keyword" )
221+ if (await self .parent .workspace .apply_edit (we )).applied :
222+ lines = insert_text .rstrip ().splitlines ()
223+ insert_range .start .line += len (lines ) - 1
224+ insert_range .start .character = 4
225+ insert_range .end = Position (insert_range .start .line , insert_range .start .character )
226+ insert_range .end .character += len (lines [- 1 ])
227+ await self .parent .window .show_document (str (document .uri ), take_focus = True , selection = insert_range )
228+
229+ async def code_action_assign_result_to_variable (
230+ self , sender : Any , document : TextDocument , range : Range , context : CodeActionContext
231+ ) -> Optional [List [Union [Command , CodeAction ]]]:
232+ from robot .parsing .lexer import Token as RobotToken
233+ from robot .parsing .model .statements import (
234+ Fixture ,
235+ KeywordCall ,
236+ Template ,
237+ TestTemplate ,
238+ )
239+
240+ if range .start == range .end and (
241+ (context .only and "other" in context .only )
242+ or context .trigger_kind in [CodeActionTriggerKind .INVOKED , CodeActionTriggerKind .AUTOMATIC ]
243+ ):
244+ model = await self .parent .documents_cache .get_model (document , False )
245+ node = await get_node_at_position (model , range .start )
246+
247+ if not isinstance (node , KeywordCall ) or node .assign :
248+ return None
249+
250+ keyword_token = (
251+ node .get_token (RobotToken .NAME )
252+ if isinstance (node , (TestTemplate , Template , Fixture ))
253+ else node .get_token (RobotToken .KEYWORD )
254+ )
255+
256+ if keyword_token is None or range .start not in range_from_token (keyword_token ):
257+ return None
258+
259+ return [
260+ CodeAction (
261+ "Assign Result To Variable" ,
262+ kind = "other" ,
263+ command = Command (
264+ self .parent .commands .get_command_name (self .assign_result_to_variable_command ),
265+ self .parent .commands .get_command_name (self .assign_result_to_variable_command ),
266+ [document .document_uri , range ],
267+ ),
268+ )
269+ ]
270+
271+ return None
272+
273+ @command ("robotcode.assignResultToVariable" )
274+ async def assign_result_to_variable_command (self , document_uri : DocumentUri , range : Range ) -> None :
275+ from robot .parsing .lexer import Token as RobotToken
276+ from robot .parsing .model .statements import (
277+ Fixture ,
278+ KeywordCall ,
279+ Template ,
280+ TestTemplate ,
281+ )
282+
283+ if range .start == range .end :
284+ document = await self .parent .documents .get (document_uri )
285+ if document is None :
286+ return
287+
288+ model = await self .parent .documents_cache .get_model (document , False )
289+ node = await get_node_at_position (model , range .start )
290+
291+ if not isinstance (node , KeywordCall ) or node .assign :
292+ return
293+
294+ keyword_token = (
295+ node .get_token (RobotToken .NAME )
296+ if isinstance (node , (TestTemplate , Template , Fixture ))
297+ else node .get_token (RobotToken .KEYWORD )
298+ )
299+
300+ if keyword_token is None or range .start not in range_from_token (keyword_token ):
301+ return
302+
303+ start = range_from_token (keyword_token ).start
304+ we = WorkspaceEdit (
305+ document_changes = [
306+ TextDocumentEdit (
307+ OptionalVersionedTextDocumentIdentifier (str (document .uri ), document .version ),
308+ [AnnotatedTextEdit ("assign_result_to_variable" , Range (start , start ), "${result} " )],
309+ )
310+ ],
311+ change_annotations = {"assign_result_to_variable" : ChangeAnnotation ("Assign result to variable" , False )},
312+ )
313+
314+ if (await self .parent .workspace .apply_edit (we )).applied :
315+ insert_range = Range (start , start ).extend (start_character = 2 , end_character = 8 )
316+
317+ await self .parent .window .show_document (str (document .uri ), take_focus = True , selection = insert_range )
318+
319+ async def code_action_create_local_variable (
320+ self , sender : Any , document : TextDocument , range : Range , context : CodeActionContext
321+ ) -> Optional [List [Union [Command , CodeAction ]]]:
322+ from robot .parsing .model .blocks import Keyword , TestCase
323+ from robot .parsing .model .statements import Documentation , Fixture , Statement , Template
324+
325+ if range .start == range .end and (
326+ (context .only and CodeActionKind .QUICK_FIX in context .only )
327+ or context .trigger_kind in [CodeActionTriggerKind .INVOKED , CodeActionTriggerKind .AUTOMATIC ]
328+ ):
329+ diagnostics = next (
330+ (d for d in context .diagnostics if d .source == "robotcode.namespace" and d .code == "VariableNotFound" ),
331+ None ,
332+ )
333+ if (
334+ diagnostics is not None
335+ and diagnostics .range .start .line == diagnostics .range .end .line
336+ and diagnostics .range .start .character < diagnostics .range .end .character
337+ ):
338+ model = await self .parent .documents_cache .get_model (document , False )
339+ nodes = await get_nodes_at_position (model , range .start )
340+
341+ if not any (n for n in nodes if isinstance (n , (Keyword , TestCase ))):
342+ return None
343+
344+ node = nodes [- 1 ] if nodes else None
345+ if node is None or isinstance (node , (Documentation , Fixture , Template )):
346+ return None
347+
348+ if not isinstance (node , Statement ):
349+ return None
350+ return [
351+ CodeAction (
352+ "Create Local Variable" ,
353+ kind = CodeActionKind .QUICK_FIX ,
354+ command = Command (
355+ self .parent .commands .get_command_name (self .create_local_variable_command ),
356+ self .parent .commands .get_command_name (self .create_local_variable_command ),
357+ [document .document_uri , diagnostics .range ],
358+ ),
359+ diagnostics = [diagnostics ],
360+ )
361+ ]
362+
363+ return None
364+
365+ @command ("robotcode.createLocalVariable" )
366+ async def create_local_variable_command (self , document_uri : DocumentUri , range : Range ) -> None :
367+ from robot .parsing .model .blocks import Keyword , TestCase
368+ from robot .parsing .model .statements import Documentation , Fixture , Statement , Template
369+
370+ if range .start .line == range .end .line and range .start .character < range .end .character :
371+ document = await self .parent .documents .get (document_uri )
372+ if document is None :
373+ return
374+
375+ model = await self .parent .documents_cache .get_model (document , False )
376+ nodes = await get_nodes_at_position (model , range .start )
377+
378+ if not any (n for n in nodes if isinstance (n , (Keyword , TestCase ))):
379+ return
380+
381+ node = nodes [- 1 ] if nodes else None
382+ if node is None or isinstance (node , (Documentation , Fixture , Template )):
383+ return
384+
385+ if not isinstance (node , Statement ):
386+ return
387+
388+ text = document .get_lines ()[range .start .line ][range .start .character : range .end .character ]
389+ if not text :
390+ return
391+
392+ spaces = node .tokens [0 ].value if node .tokens and node .tokens [0 ].type == "SEPARATOR" else " "
393+
394+ insert_text = f"{ spaces } ${{{ text } }} Set Variable value\n "
395+ node_range = range_from_node (node )
396+ insert_range = Range (start = Position (node_range .start .line , 0 ), end = Position (node_range .start .line , 0 ))
397+ we = WorkspaceEdit (
398+ document_changes = [
399+ TextDocumentEdit (
400+ OptionalVersionedTextDocumentIdentifier (str (document .uri ), document .version ),
401+ [AnnotatedTextEdit ("create_local_variable" , insert_range , insert_text )],
402+ )
403+ ],
404+ change_annotations = {"create_local_variable" : ChangeAnnotation ("Create Local Variable" , False )},
405+ )
406+
407+ if (await self .parent .workspace .apply_edit (we )).applied :
408+ insert_range .start .character += insert_text .index ("value" )
409+ insert_range .end .character = insert_range .start .character + len ("value" )
410+
411+ await self .parent .window .show_document (str (document .uri ), take_focus = False , selection = insert_range )
412+
413+ async def code_action_disable_robotcode_diagnostics_for_line (
414+ self , sender : Any , document : TextDocument , range : Range , context : CodeActionContext
415+ ) -> Optional [List [Union [Command , CodeAction ]]]:
416+ if range .start == range .end and (
417+ (context .only and CodeActionKind .QUICK_FIX in context .only )
418+ or context .trigger_kind in [CodeActionTriggerKind .INVOKED , CodeActionTriggerKind .AUTOMATIC ]
419+ ):
420+ diagnostics = next ((d for d in context .diagnostics if d .source and d .source .startswith ("robotcode." )), None )
421+
422+ if diagnostics is not None :
423+ return [
424+ CodeAction (
425+ f"Disable '{ diagnostics .code } ' for this line" ,
426+ kind = CodeActionKind .QUICK_FIX ,
427+ command = Command (
428+ self .parent .commands .get_command_name (self .disable_robotcode_diagnostics_for_line_command ),
429+ self .parent .commands .get_command_name (self .disable_robotcode_diagnostics_for_line_command ),
430+ [document .document_uri , range ],
431+ ),
432+ diagnostics = [diagnostics ],
433+ )
434+ ]
435+
436+ return None
437+
438+ @command ("robotcode.disableRobotcodeDiagnosticsForLine" )
439+ async def disable_robotcode_diagnostics_for_line_command (self , document_uri : DocumentUri , range : Range ) -> None :
440+ if range .start .line == range .end .line :
441+ document = await self .parent .documents .get (document_uri )
442+ if document is None :
443+ return
444+
445+ insert_text = " # robotcode: ignore"
446+
447+ line = document .get_lines ()[range .start .line ]
448+ stripped_line = line .rstrip ()
449+
450+ insert_range = Range (
451+ start = Position (range .start .line , len (stripped_line )), end = Position (range .start .line , len (line ))
452+ )
453+ we = WorkspaceEdit (
454+ document_changes = [
455+ TextDocumentEdit (
456+ OptionalVersionedTextDocumentIdentifier (str (document .uri ), document .version ),
457+ [AnnotatedTextEdit ("disable_robotcode_diagnostics_for_line" , insert_range , insert_text )],
458+ )
459+ ],
460+ change_annotations = {
461+ "disable_robotcode_diagnostics_for_line" : ChangeAnnotation (
462+ "Disable robotcode diagnostics for line" , False
463+ )
464+ },
465+ )
209466
210- lines = insert_text .rstrip ().splitlines ()
211- insert_range .start .line += len (lines ) - 1
212- insert_range .start .character = 4
213- insert_range .end = Position (insert_range .start .line , insert_range .start .character )
214- insert_range .end .character += len (lines [- 1 ])
215- await self .parent .window .show_document (str (document .uri ), take_focus = True , selection = insert_range )
467+ await self .parent .workspace .apply_edit (we )
0 commit comments