Skip to content

Commit 4c03a80

Browse files
committed
feat(langserver): new code action quick fix - create suite variable
1 parent 1c333d3 commit 4c03a80

File tree

1 file changed

+159
-7
lines changed

1 file changed

+159
-7
lines changed

packages/language_server/src/robotcode/language_server/robotframework/parts/code_action_quick_fixes.py

Lines changed: 159 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,16 +59,53 @@
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

70107
def 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

Comments
 (0)