Skip to content

Commit a21c05b

Browse files
committed
feat(langserver): new code action quick fix - Add argument
1 parent 125c091 commit a21c05b

File tree

1 file changed

+167
-27
lines changed

1 file changed

+167
-27
lines changed

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

Lines changed: 167 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ async def collect(
128128
) -> Optional[List[Union[Command, CodeAction]]]:
129129
result = []
130130
for method in iter_methods(self, lambda m: m.__name__.startswith("code_action_")):
131-
code_actions = await method(self, document, range, context)
131+
code_actions = await method(document, range, context)
132132
if code_actions:
133133
result.extend(code_actions)
134134

@@ -138,11 +138,15 @@ async def collect(
138138
return None
139139

140140
async def code_action_create_keyword(
141-
self, sender: Any, document: TextDocument, range: Range, context: CodeActionContext
141+
self, document: TextDocument, range: Range, context: CodeActionContext
142142
) -> Optional[List[Union[Command, CodeAction]]]:
143-
if range.start == range.end and (
144-
(context.only and CodeActionKind.QUICK_FIX in context.only)
145-
or context.trigger_kind in [CodeActionTriggerKind.INVOKED, CodeActionTriggerKind.AUTOMATIC]
143+
if (
144+
range.start.line == range.end.line
145+
and range.start.character <= range.end.character
146+
and (
147+
(context.only and CodeActionKind.QUICK_FIX in context.only)
148+
or context.trigger_kind in [CodeActionTriggerKind.INVOKED, CodeActionTriggerKind.AUTOMATIC]
149+
)
146150
):
147151
diagnostics = next(
148152
(
@@ -265,7 +269,7 @@ async def create_keyword_command(self, document_uri: DocumentUri, range: Range)
265269
await self.parent.window.show_document(str(document.uri), take_focus=True, selection=insert_range)
266270

267271
async def code_action_assign_result_to_variable(
268-
self, sender: Any, document: TextDocument, range: Range, context: CodeActionContext
272+
self, document: TextDocument, range: Range, context: CodeActionContext
269273
) -> Optional[List[Union[Command, CodeAction]]]:
270274
from robot.parsing.lexer import Token as RobotToken
271275
from robot.parsing.model.statements import (
@@ -275,9 +279,13 @@ async def code_action_assign_result_to_variable(
275279
TestTemplate,
276280
)
277281

278-
if range.start == range.end and (
279-
(context.only and "other" in context.only)
280-
or context.trigger_kind in [CodeActionTriggerKind.INVOKED, CodeActionTriggerKind.AUTOMATIC]
282+
if (
283+
range.start.line == range.end.line
284+
and range.start.character <= range.end.character
285+
and (
286+
(context.only and "other" in context.only)
287+
or context.trigger_kind in [CodeActionTriggerKind.INVOKED, CodeActionTriggerKind.AUTOMATIC]
288+
)
281289
):
282290
model = await self.parent.documents_cache.get_model(document, False)
283291
node = await get_node_at_position(model, range.start)
@@ -318,7 +326,7 @@ async def assign_result_to_variable_command(self, document_uri: DocumentUri, ran
318326
TestTemplate,
319327
)
320328

321-
if range.start == range.end:
329+
if range.start.line == range.end.line and range.start.character <= range.end.character:
322330
document = await self.parent.documents.get(document_uri)
323331
if document is None:
324332
return
@@ -355,14 +363,18 @@ async def assign_result_to_variable_command(self, document_uri: DocumentUri, ran
355363
await self.parent.window.show_document(str(document.uri), take_focus=True, selection=insert_range)
356364

357365
async def code_action_create_local_variable(
358-
self, sender: Any, document: TextDocument, range: Range, context: CodeActionContext
366+
self, document: TextDocument, range: Range, context: CodeActionContext
359367
) -> Optional[List[Union[Command, CodeAction]]]:
360368
from robot.parsing.model.blocks import Keyword, TestCase
361369
from robot.parsing.model.statements import Documentation, Fixture, Statement, Template
362370

363-
if range.start == range.end and (
364-
(context.only and CodeActionKind.QUICK_FIX in context.only)
365-
or context.trigger_kind in [CodeActionTriggerKind.INVOKED, CodeActionTriggerKind.AUTOMATIC]
371+
if (
372+
range.start.line == range.end.line
373+
and range.start.character <= range.end.character
374+
and (
375+
(context.only and CodeActionKind.QUICK_FIX in context.only)
376+
or context.trigger_kind in [CodeActionTriggerKind.INVOKED, CodeActionTriggerKind.AUTOMATIC]
377+
)
366378
):
367379
diagnostics = next(
368380
(
@@ -409,7 +421,7 @@ async def create_local_variable_command(self, document_uri: DocumentUri, range:
409421
from robot.parsing.model.blocks import Keyword, TestCase
410422
from robot.parsing.model.statements import Documentation, Fixture, Statement, Template
411423

412-
if range.start.line == range.end.line and range.start.character < range.end.character:
424+
if range.start.line == range.end.line and range.start.character <= range.end.character:
413425
document = await self.parent.documents.get(document_uri)
414426
if document is None:
415427
return
@@ -447,17 +459,21 @@ async def create_local_variable_command(self, document_uri: DocumentUri, range:
447459
)
448460

449461
if (await self.parent.workspace.apply_edit(we)).applied:
450-
insert_range.start.character += insert_text.index("value")
462+
insert_range.start.character += insert_text.rindex("value")
451463
insert_range.end.character = insert_range.start.character + len("value")
452464

453465
await self.parent.window.show_document(str(document.uri), take_focus=False, selection=insert_range)
454466

455467
async def code_action_disable_robotcode_diagnostics_for_line(
456-
self, sender: Any, document: TextDocument, range: Range, context: CodeActionContext
468+
self, document: TextDocument, range: Range, context: CodeActionContext
457469
) -> Optional[List[Union[Command, CodeAction]]]:
458-
if range.start == range.end and (
459-
(context.only and CodeActionKind.QUICK_FIX in context.only)
460-
or context.trigger_kind in [CodeActionTriggerKind.INVOKED, CodeActionTriggerKind.AUTOMATIC]
470+
if (
471+
range.start.line == range.end.line
472+
and range.start.character <= range.end.character
473+
and (
474+
(context.only and CodeActionKind.QUICK_FIX in context.only)
475+
or context.trigger_kind in [CodeActionTriggerKind.INVOKED, CodeActionTriggerKind.AUTOMATIC]
476+
)
461477
):
462478
diagnostics = next((d for d in context.diagnostics if d.source and d.source.startswith("robotcode.")), None)
463479

@@ -479,7 +495,7 @@ async def code_action_disable_robotcode_diagnostics_for_line(
479495

480496
@command("robotcode.disableRobotcodeDiagnosticsForLine")
481497
async def disable_robotcode_diagnostics_for_line_command(self, document_uri: DocumentUri, range: Range) -> None:
482-
if range.start.line == range.end.line:
498+
if range.start.line == range.end.line and range.start.character <= range.end.character:
483499
document = await self.parent.documents.get(document_uri)
484500
if document is None:
485501
return
@@ -509,11 +525,15 @@ async def disable_robotcode_diagnostics_for_line_command(self, document_uri: Doc
509525
await self.parent.workspace.apply_edit(we)
510526

511527
async def code_action_create_suite_variable(
512-
self, sender: Any, document: TextDocument, range: Range, context: CodeActionContext
528+
self, document: TextDocument, range: Range, context: CodeActionContext
513529
) -> Optional[List[Union[Command, CodeAction]]]:
514-
if range.start == range.end and (
515-
(context.only and CodeActionKind.QUICK_FIX in context.only)
516-
or context.trigger_kind in [CodeActionTriggerKind.INVOKED, CodeActionTriggerKind.AUTOMATIC]
530+
if (
531+
range.start.line == range.end.line
532+
and range.start.character <= range.end.character
533+
and (
534+
(context.only and CodeActionKind.QUICK_FIX in context.only)
535+
or context.trigger_kind in [CodeActionTriggerKind.INVOKED, CodeActionTriggerKind.AUTOMATIC]
536+
)
517537
):
518538
diagnostics = next(
519539
(
@@ -548,7 +568,7 @@ async def create_suite_variable_command(self, document_uri: DocumentUri, range:
548568
from robot.parsing.model.blocks import VariableSection
549569
from robot.parsing.model.statements import Variable
550570

551-
if range.start.line == range.end.line and range.start.character < range.end.character:
571+
if range.start.line == range.end.line and range.start.character <= range.end.character:
552572
document = await self.parent.documents.get(document_uri)
553573
if document is None:
554574
return
@@ -622,7 +642,127 @@ async def create_suite_variable_command(self, document_uri: DocumentUri, range:
622642
start_line = next((i for i, l in enumerate(splitted) if "value" in l), 0)
623643
insert_range.start.line = insert_range.start.line + start_line
624644
insert_range.end.line = insert_range.start.line
625-
insert_range.start.character = splitted[start_line].index("value")
645+
insert_range.start.character = splitted[start_line].rindex("value")
626646
insert_range.end.character = insert_range.start.character + len("value")
627647

628648
await self.parent.window.show_document(str(document.uri), take_focus=False, selection=insert_range)
649+
650+
async def code_action_add_argument(
651+
self, document: TextDocument, range: Range, context: CodeActionContext
652+
) -> Optional[List[Union[Command, CodeAction]]]:
653+
from robot.parsing.model.blocks import Keyword
654+
655+
if (
656+
range.start.line == range.end.line
657+
and range.start.character <= range.end.character
658+
and (
659+
(context.only and CodeActionKind.QUICK_FIX in context.only)
660+
or context.trigger_kind in [CodeActionTriggerKind.INVOKED, CodeActionTriggerKind.AUTOMATIC]
661+
)
662+
):
663+
diagnostics = next(
664+
(
665+
d
666+
for d in context.diagnostics
667+
if d.source == DIAGNOSTICS_SOURCE_NAME and d.code == Error.VARIABLE_NOT_FOUND
668+
),
669+
None,
670+
)
671+
if (
672+
diagnostics is not None
673+
and diagnostics.range.start.line == diagnostics.range.end.line
674+
and diagnostics.range.start.character < diagnostics.range.end.character
675+
):
676+
text = document.get_lines()[range.start.line][range.start.character : range.end.character]
677+
if not text:
678+
return None
679+
680+
model = await self.parent.documents_cache.get_model(document, False)
681+
nodes = await get_nodes_at_position(model, range.start)
682+
683+
if not any(n for n in nodes if isinstance(n, Keyword)):
684+
return None
685+
686+
return [
687+
CodeAction(
688+
"Add argument",
689+
kind=CodeActionKind.QUICK_FIX,
690+
command=Command(
691+
self.parent.commands.get_command_name(self.action_add_argument_command),
692+
self.parent.commands.get_command_name(self.action_add_argument_command),
693+
[document.document_uri, diagnostics.range],
694+
),
695+
diagnostics=[diagnostics],
696+
)
697+
]
698+
699+
return None
700+
701+
@command("robotcode.actionAddArgument")
702+
async def action_add_argument_command(self, document_uri: DocumentUri, range: Range) -> None:
703+
from robot.parsing.lexer.tokens import Token
704+
from robot.parsing.model.blocks import Keyword
705+
from robot.parsing.model.statements import Arguments, Documentation
706+
707+
if range.start.line == range.end.line and range.start.character <= range.end.character:
708+
document = await self.parent.documents.get(document_uri)
709+
if document is None:
710+
return
711+
712+
text = document.get_lines()[range.start.line][range.start.character : range.end.character]
713+
if not text:
714+
return
715+
716+
model = await self.parent.documents_cache.get_model(document, False)
717+
nodes = await get_nodes_at_position(model, range.start)
718+
719+
keyword = next((n for n in nodes if isinstance(n, Keyword)), None)
720+
if keyword is None:
721+
return
722+
723+
arguments = next((n for n in keyword.body if isinstance(n, Arguments)), None)
724+
725+
if arguments is None:
726+
i = 0
727+
first_stmt = keyword.body[i]
728+
729+
while isinstance(first_stmt, Documentation) and i < len(keyword.body):
730+
i += 1
731+
first_stmt = keyword.body[i]
732+
733+
if i >= len(keyword.body):
734+
return
735+
736+
spaces = (
737+
first_stmt.tokens[0].value
738+
if first_stmt is not None and first_stmt.tokens and first_stmt.tokens[0].type == "SEPARATOR"
739+
else " "
740+
)
741+
742+
insert_text = f"{spaces}[Arguments] ${{{text}}}=\n"
743+
node_range = range_from_node(first_stmt)
744+
insert_range = Range(start=Position(node_range.start.line, 0), end=Position(node_range.start.line, 0))
745+
else:
746+
insert_text = f" ${{{text}}}="
747+
argument_tokens = arguments.get_tokens(Token.ARGUMENT)
748+
if argument_tokens:
749+
token_range = range_from_token(argument_tokens[-1])
750+
else:
751+
token_range = range_from_token(arguments.get_token(Token.ARGUMENTS))
752+
insert_range = Range(start=token_range.end, end=token_range.end)
753+
754+
we = WorkspaceEdit(
755+
document_changes=[
756+
TextDocumentEdit(
757+
OptionalVersionedTextDocumentIdentifier(str(document.uri), document.version),
758+
[AnnotatedTextEdit("add_argument", insert_range, insert_text)],
759+
)
760+
],
761+
change_annotations={"add_argument": ChangeAnnotation("Add Argument", False)},
762+
)
763+
764+
if (await self.parent.workspace.apply_edit(we)).applied:
765+
insert_range.start.character += len(insert_text)
766+
insert_range.end.character = insert_range.start.character
767+
768+
await self.parent.window.show_document(str(document.uri), take_focus=False, selection=insert_range)

0 commit comments

Comments
 (0)