5959)
6060
6161
62- class FindKeywordSectionVisitor (Visitor ):
62+ class LastRealStatementFinder (Visitor ):
63+ def __init__ (self ) -> None :
64+ self .statement : Optional [ast .AST ] = None
65+
66+ @classmethod
67+ def find_from (cls , model : ast .AST ) -> Optional [ast .AST ]:
68+ finder = cls ()
69+ finder .visit (model )
70+ return finder .statement
71+
72+ def visit_Statement (self , statement : ast .AST ) -> None : # noqa: N802
73+ from robot .parsing .model .statements import EmptyLine
74+
75+ if not isinstance (statement , EmptyLine ):
76+ self .statement = statement
77+
78+
79+ class FindSectionsVisitor (Visitor ):
6380 def __init__ (self ) -> None :
6481 self .keyword_sections : List [ast .AST ] = []
82+ self .variable_sections : List [ast .AST ] = []
83+ self .setting_sections : List [ast .AST ] = []
84+ self .testcase_sections : List [ast .AST ] = []
85+ self .sections : List [ast .AST ] = []
6586
6687 def visit_KeywordSection (self , node : ast .AST ) -> None : # noqa: N802
6788 self .keyword_sections .append (node )
89+ self .sections .append (node )
90+
91+ def visit_VariableSection (self , node : ast .AST ) -> None : # noqa: N802
92+ self .variable_sections .append (node )
93+ self .sections .append (node )
94+
95+ def visit_SettingSection (self , node : ast .AST ) -> None : # noqa: N802
96+ self .setting_sections .append (node )
97+ self .sections .append (node )
98+
99+ def visit_TestCaseSection (self , node : ast .AST ) -> None : # noqa: N802
100+ self .testcase_sections .append (node )
101+ self .sections .append (node )
102+
103+ def visit_CommentSection (self , node : ast .AST ) -> None : # noqa: N802
104+ self .sections .append (node )
68105
69106
70107def find_keyword_sections (node : ast .AST ) -> Optional [List [ast .AST ]]:
71- visitor = FindKeywordSectionVisitor ()
108+ visitor = FindSectionsVisitor ()
72109 visitor .visit (node )
73110 return visitor .keyword_sections if visitor .keyword_sections else None
74111
@@ -95,7 +132,7 @@ async def collect(
95132 result .extend (code_actions )
96133
97134 if result :
98- return result
135+ return list ( sorted ( result , key = lambda ca : ca . title ))
99136
100137 return None
101138
@@ -258,7 +295,7 @@ async def code_action_assign_result_to_variable(
258295
259296 return [
260297 CodeAction (
261- "Assign Result To Variable " ,
298+ "Assign result to variable " ,
262299 kind = "other" ,
263300 command = Command (
264301 self .parent .commands .get_command_name (self .assign_result_to_variable_command ),
@@ -349,7 +386,7 @@ async def code_action_create_local_variable(
349386 return None
350387 return [
351388 CodeAction (
352- "Create Local Variable " ,
389+ "Create local variable " ,
353390 kind = CodeActionKind .QUICK_FIX ,
354391 command = Command (
355392 self .parent .commands .get_command_name (self .create_local_variable_command ),
@@ -391,7 +428,7 @@ async def create_local_variable_command(self, document_uri: DocumentUri, range:
391428
392429 spaces = node .tokens [0 ].value if node .tokens and node .tokens [0 ].type == "SEPARATOR" else " "
393430
394- insert_text = f"{ spaces } ${{{ text } }} Set Variable value\n "
431+ insert_text = f"{ spaces } ${{{ text } }} Set Variable value\n "
395432 node_range = range_from_node (node )
396433 insert_range = Range (start = Position (node_range .start .line , 0 ), end = Position (node_range .start .line , 0 ))
397434 we = WorkspaceEdit (
@@ -401,7 +438,7 @@ async def create_local_variable_command(self, document_uri: DocumentUri, range:
401438 [AnnotatedTextEdit ("create_local_variable" , insert_range , insert_text )],
402439 )
403440 ],
404- change_annotations = {"create_local_variable" : ChangeAnnotation ("Create Local Variable " , False )},
441+ change_annotations = {"create_local_variable" : ChangeAnnotation ("Create Local variable " , False )},
405442 )
406443
407444 if (await self .parent .workspace .apply_edit (we )).applied :
@@ -465,3 +502,118 @@ async def disable_robotcode_diagnostics_for_line_command(self, document_uri: Doc
465502 )
466503
467504 await self .parent .workspace .apply_edit (we )
505+
506+ async def code_action_create_suite_variable (
507+ self , sender : Any , document : TextDocument , range : Range , context : CodeActionContext
508+ ) -> Optional [List [Union [Command , CodeAction ]]]:
509+ if range .start == range .end and (
510+ (context .only and CodeActionKind .QUICK_FIX in context .only )
511+ or context .trigger_kind in [CodeActionTriggerKind .INVOKED , CodeActionTriggerKind .AUTOMATIC ]
512+ ):
513+ diagnostics = next (
514+ (d for d in context .diagnostics if d .source == "robotcode.namespace" and d .code == "VariableNotFound" ),
515+ None ,
516+ )
517+ if (
518+ diagnostics is not None
519+ and diagnostics .range .start .line == diagnostics .range .end .line
520+ and diagnostics .range .start .character < diagnostics .range .end .character
521+ ):
522+ return [
523+ CodeAction (
524+ "Create suite variable" ,
525+ kind = CodeActionKind .QUICK_FIX ,
526+ command = Command (
527+ self .parent .commands .get_command_name (self .create_suite_variable_command ),
528+ self .parent .commands .get_command_name (self .create_suite_variable_command ),
529+ [document .document_uri , diagnostics .range ],
530+ ),
531+ diagnostics = [diagnostics ],
532+ )
533+ ]
534+
535+ return None
536+
537+ @command ("robotcode.createSuiteVariable" )
538+ async def create_suite_variable_command (self , document_uri : DocumentUri , range : Range ) -> None :
539+ from robot .parsing .model .blocks import VariableSection
540+ from robot .parsing .model .statements import Variable
541+
542+ if range .start .line == range .end .line and range .start .character < range .end .character :
543+ document = await self .parent .documents .get (document_uri )
544+ if document is None :
545+ return
546+
547+ model = await self .parent .documents_cache .get_model (document , False )
548+ nodes = await get_nodes_at_position (model , range .start )
549+
550+ node = nodes [- 1 ] if nodes else None
551+
552+ if node is None :
553+ return
554+
555+ insert_range_prefix = ""
556+ insert_range_suffix = ""
557+
558+ if any (n for n in nodes if isinstance (n , (VariableSection ))) and isinstance (node , Variable ):
559+ node_range = range_from_node (node )
560+ insert_range = Range (start = Position (node_range .start .line , 0 ), end = Position (node_range .start .line , 0 ))
561+ else :
562+ finder = FindSectionsVisitor ()
563+ finder .visit (model )
564+
565+ if finder .variable_sections :
566+ section = finder .variable_sections [- 1 ]
567+
568+ last_stmt = LastRealStatementFinder .find_from (section )
569+ end_lineno = last_stmt .end_lineno if last_stmt else section .end_lineno
570+ if end_lineno is None :
571+ return
572+
573+ insert_range = Range (start = Position (end_lineno , 0 ), end = Position (end_lineno , 0 ))
574+ else :
575+ insert_range_prefix = "\n \n *** Variables ***\n "
576+ if finder .setting_sections :
577+ insert_range_prefix = "\n \n *** Variables ***\n "
578+ insert_range_suffix = "\n \n "
579+ section = finder .setting_sections [- 1 ]
580+
581+ last_stmt = LastRealStatementFinder .find_from (section )
582+ end_lineno = last_stmt .end_lineno if last_stmt else section .end_lineno
583+ if end_lineno is None :
584+ return
585+
586+ insert_range = Range (start = Position (end_lineno , 0 ), end = Position (end_lineno , 0 ))
587+ else :
588+ insert_range_prefix = "*** Variables ***\n "
589+ insert_range_suffix = "\n \n "
590+ insert_range = Range (start = Position (0 , 0 ), end = Position (0 , 0 ))
591+
592+ lines = document .get_lines ()
593+ text = lines [range .start .line ][range .start .character : range .end .character ]
594+ if not text :
595+ return
596+ if insert_range .start .line == insert_range .end .line and insert_range .start .line >= len (lines ):
597+ insert_range .start .line = len (lines ) - 1
598+ insert_range .start .character = len (lines [- 1 ])
599+ insert_range_prefix = "\n " + insert_range_prefix
600+ insert_text = insert_range_prefix + f"${{{ text } }} value\n " + insert_range_suffix
601+ we = WorkspaceEdit (
602+ document_changes = [
603+ TextDocumentEdit (
604+ OptionalVersionedTextDocumentIdentifier (str (document .uri ), document .version ),
605+ [AnnotatedTextEdit ("create_suite_variable" , insert_range , insert_text )],
606+ )
607+ ],
608+ change_annotations = {"create_suite_variable" : ChangeAnnotation ("Create suite variable" , False )},
609+ )
610+
611+ if (await self .parent .workspace .apply_edit (we )).applied :
612+ splitted = insert_text .splitlines ()
613+ start_line = next ((i for i , l in enumerate (splitted ) if "value" in l ), 0 )
614+ insert_range .start .line = insert_range .start .line + start_line
615+ insert_range .end .line = insert_range .start .line
616+ insert_range .start .character = splitted [start_line ].index ("value" )
617+ insert_range .end .character = insert_range .start .character + len ("value" )
618+
619+ await self .parent .window .show_document (str (document .uri ), take_focus = False , selection = insert_range )
0 commit comments