Skip to content

Commit 8b1b16a

Browse files
committed
implement variable renaming
1 parent f757fa1 commit 8b1b16a

File tree

2 files changed

+135
-73
lines changed

2 files changed

+135
-73
lines changed

robotcode/language_server/robotframework/parts/references.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -217,16 +217,20 @@ async def _references_default(
217217
if context.include_declaration and variable.source
218218
else []
219219
),
220-
*(await self.find_variable_references(document, variable)),
220+
*(await self.find_variable_references(document, variable, context.include_declaration)),
221221
]
222222

223223
return None
224224

225-
async def find_variable_references(self, document: TextDocument, variable: VariableDefinition) -> List[Location]:
225+
async def find_variable_references(
226+
self, document: TextDocument, variable: VariableDefinition, include_declaration: bool = True
227+
) -> List[Location]:
226228
return (
227-
await create_sub_task(self.find_variable_references_in_file(document, variable))
229+
await create_sub_task(self.find_variable_references_in_file(document, variable, include_declaration))
228230
if isinstance(variable, (ArgumentDefinition, LocalVariableDefinition))
229-
else await self._find_references(document, self.find_variable_references_in_file, variable)
231+
else await self._find_references(
232+
document, self.find_variable_references_in_file, variable, include_declaration
233+
)
230234
)
231235

232236
async def yield_argument_name_and_rest(self, node: ast.AST, token: Token) -> AsyncGenerator[Token, None]:
@@ -260,9 +264,7 @@ async def yield_argument_name_and_rest(self, node: ast.AST, token: Token) -> Asy
260264

261265
@_logger.call
262266
async def find_variable_references_in_file(
263-
self,
264-
doc: TextDocument,
265-
variable: VariableDefinition,
267+
self, doc: TextDocument, variable: VariableDefinition, include_declaration: bool = True
266268
) -> List[Location]:
267269
from robot.parsing.lexer.tokens import Token as RobotToken
268270
from robot.parsing.model.blocks import Block, Keyword, Section, TestCase
@@ -309,7 +311,14 @@ async def find_variable_references_in_file(
309311
sub_token, found_variable = token_and_var
310312

311313
if found_variable == variable:
312-
result.append(Location(str(doc.uri), range_from_token(sub_token)))
314+
if (
315+
include_declaration
316+
or found_variable.name_token is None
317+
or not include_declaration
318+
and variable.name_token is not None
319+
and range_from_token(variable.name_token) != range_from_token(sub_token)
320+
):
321+
result.append(Location(str(doc.uri), range_from_token(sub_token)))
313322

314323
if (
315324
isinstance(node, Statement)

robotcode/language_server/robotframework/parts/rename.py

Lines changed: 118 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,9 @@
1515
cast,
1616
)
1717

18-
# from ....utils.async_itertools import async_next
18+
from ....utils.async_itertools import async_next
1919
from ....utils.async_tools import threaded
2020
from ....utils.logging import LoggingDescriptor
21-
22-
# from ....utils.uri import Uri
2321
from ...common.decorators import language_id
2422
from ...common.lsp_types import (
2523
AnnotatedTextEdit,
@@ -36,12 +34,14 @@
3634
)
3735
from ...common.parts.rename import CantRenameException
3836
from ...common.text_document import TextDocument
39-
40-
# from ..diagnostics.entities import VariableDefinition
37+
from ..diagnostics.entities import VariableDefinition, VariableDefinitionType
4138
from ..diagnostics.library_doc import KeywordDoc
42-
from ..utils.ast_utils import ( # HasTokens,; Statement,; get_tokens_at_position,
39+
from ..utils.ast_utils import (
40+
HasTokens,
41+
Statement,
4342
Token,
4443
get_nodes_at_position,
44+
get_tokens_at_position,
4545
range_from_token,
4646
)
4747

@@ -148,67 +148,119 @@ async def collect_prepare(
148148
async def _prepare_rename_default(
149149
self, nodes: List[ast.AST], document: TextDocument, position: Position
150150
) -> Optional[PrepareRenameResult]:
151-
pass
151+
result = await self._find_default(nodes, document, position)
152+
if result is not None:
153+
var, token = result
154+
155+
if var.type == VariableDefinitionType.BUILTIN_VARIABLE:
156+
self.parent.window.show_message("You cannot rename a builtin variable, only references are renamed.")
157+
158+
elif var.type == VariableDefinitionType.IMPORTED_VARIABLE:
159+
self.parent.window.show_message(
160+
"You are about to rename an imported variable. "
161+
"Only references are renamed and you have to rename the variable definition yourself."
162+
)
163+
elif var.type == VariableDefinitionType.COMMAND_LINE_VARIABLE:
164+
self.parent.window.show_message(
165+
"You are about to rename a variable defined at commandline. "
166+
"Only references are renamed and you have to rename the variable definition yourself."
167+
)
168+
elif var.type == VariableDefinitionType.ENVIRONMENT_VARIABLE:
169+
self.parent.window.show_message(
170+
"You are about to rename an environment variable. "
171+
"Only references are renamed and you have to rename the variable definition yourself."
172+
)
173+
174+
return PrepareRenameResultWithPlaceHolder(range_from_token(token), token.value)
175+
176+
return None
152177

153178
async def _rename_default(
154179
self, nodes: List[ast.AST], document: TextDocument, position: Position, new_name: str
155180
) -> Optional[WorkspaceEdit]:
156-
# from robot.parsing.lexer.tokens import Token as RobotToken
157-
158-
# namespace = await self.parent.documents_cache.get_namespace(document)
159-
# if namespace is None:
160-
# return None
161-
162-
# if not nodes:
163-
# return None
164-
165-
# node = nodes[-1]
166-
167-
# if not isinstance(node, HasTokens):
168-
# return None
169-
170-
# tokens = get_tokens_at_position(node, position)
171-
172-
# token_and_var: Optional[Tuple[Token, VariableDefinition]] = None
173-
174-
# for token in tokens:
175-
# token_and_var = await async_next(
176-
# (
177-
# (var_token, var)
178-
# async for var_token, var in self.iter_variables_from_token(token, namespace, nodes, position)
179-
# if position in range_from_token(var_token)
180-
# ),
181-
# None,
182-
# )
183-
184-
# if (
185-
# token_and_var is None
186-
# and isinstance(node, Statement)
187-
# and isinstance(node, self.get_expression_statement_types())
188-
# and (token := node.get_token(RobotToken.ARGUMENT)) is not None
189-
# and position in range_from_token(token)
190-
# ):
191-
# token_and_var = await async_next(
192-
# (
193-
# (var_token, var)
194-
# async for var_token, var in self.iter_expression_variables_from_token(
195-
# token, namespace, nodes, position
196-
# )
197-
# if position in range_from_token(var_token)
198-
# ),
199-
# None,
200-
# )
201-
202-
# if token_and_var is not None:
203-
# _, variable = token_and_var
204-
205-
# return [
206-
# DocumentHighlight(e.range, DocumentHighlightKind.TEXT)
207-
# for e in await self.parent.robot_references.find_variable_references_in_file(document, variable)
208-
# ]
181+
result = await self._find_default(nodes, document, position)
182+
183+
if result is not None:
184+
var, _ = result
185+
186+
references = await self.parent.robot_references.find_variable_references(
187+
document,
188+
var,
189+
include_declaration=var.type
190+
in [
191+
VariableDefinitionType.VARIABLE,
192+
VariableDefinitionType.ARGUMENT,
193+
VariableDefinitionType.LOCAL_VARIABLE,
194+
],
195+
)
196+
changes: List[Union[TextDocumentEdit, CreateFile, RenameFile, DeleteFile]] = []
197+
198+
for reference in references:
199+
changes.append(
200+
TextDocumentEdit(
201+
OptionalVersionedTextDocumentIdentifier(reference.uri, None),
202+
[AnnotatedTextEdit(reference.range, new_name, annotation_id="rename_variable")],
203+
)
204+
)
205+
206+
return WorkspaceEdit(
207+
document_changes=changes,
208+
change_annotations={"rename_variable": ChangeAnnotation("Rename Variable", False)},
209+
)
209210

210211
return None
211212

213+
async def _find_default(
214+
self, nodes: List[ast.AST], document: TextDocument, position: Position
215+
) -> Optional[Tuple[VariableDefinition, Token]]:
216+
from robot.parsing.lexer.tokens import Token as RobotToken
217+
218+
namespace = await self.parent.documents_cache.get_namespace(document)
219+
if namespace is None:
220+
return None
221+
222+
if not nodes:
223+
return None
224+
225+
node = nodes[-1]
226+
227+
if not isinstance(node, HasTokens):
228+
return None
229+
230+
tokens = get_tokens_at_position(node, position)
231+
232+
token_and_var: Optional[Tuple[VariableDefinition, Token]] = None
233+
234+
for token in tokens:
235+
token_and_var = await async_next(
236+
(
237+
(var, var_token)
238+
async for var_token, var in self.iter_variables_from_token(token, namespace, nodes, position)
239+
if position in range_from_token(var_token)
240+
),
241+
None,
242+
)
243+
244+
if (
245+
token_and_var is None
246+
and isinstance(node, Statement)
247+
and isinstance(node, self.get_expression_statement_types())
248+
and (token := node.get_token(RobotToken.ARGUMENT)) is not None
249+
and position in range_from_token(token)
250+
):
251+
token_and_var = await async_next(
252+
(
253+
(var, var_token)
254+
async for var_token, var in self.iter_expression_variables_from_token(
255+
token, namespace, nodes, position
256+
)
257+
if position in range_from_token(var_token)
258+
),
259+
None,
260+
)
261+
262+
return token_and_var
263+
212264
def _prepare_rename_keyword(self, result: Optional[Tuple[KeywordDoc, Token]]) -> Optional[PrepareRenameResult]:
213265
if result is not None:
214266
kw_doc, token = result
@@ -230,7 +282,7 @@ async def _rename_keyword(
230282
self, document: TextDocument, new_name: str, result: Optional[Tuple[KeywordDoc, Token]]
231283
) -> Optional[WorkspaceEdit]:
232284
if result is not None:
233-
kw_doc, token = result
285+
kw_doc, _ = result
234286

235287
references = await self.parent.robot_references.find_keyword_references(
236288
document, kw_doc, include_declaration=kw_doc.is_resource_keyword
@@ -241,12 +293,13 @@ async def _rename_keyword(
241293
changes.append(
242294
TextDocumentEdit(
243295
OptionalVersionedTextDocumentIdentifier(reference.uri, None),
244-
[AnnotatedTextEdit(reference.range, new_name, annotation_id="a")],
296+
[AnnotatedTextEdit(reference.range, new_name, annotation_id="rename_keyword")],
245297
)
246298
)
247299

248300
return WorkspaceEdit(
249-
document_changes=changes, change_annotations={"a": ChangeAnnotation("refactor", False, "replace call")}
301+
document_changes=changes,
302+
change_annotations={"rename_keyword": ChangeAnnotation("Rename Keyword", False)},
250303
)
251304

252305
return None
@@ -404,7 +457,7 @@ async def _find_Fixture( # noqa: N802
404457
r.end.character = r.start.character + len(kw_namespace)
405458
kw_range.start.character = r.end.character + 1
406459
if position in r:
407-
# TODO highlight namespaces
460+
# TODO namespaces
408461
return None
409462

410463
if position in kw_range and keyword_doc is not None and not keyword_doc.is_error_handler:
@@ -456,7 +509,7 @@ async def _find_Template_or_TestTemplate( # noqa: N802
456509
r.end.character = r.start.character + len(kw_namespace)
457510
kw_range.start.character = r.end.character + 1
458511
if position in r:
459-
# TODO highlight namespaces
512+
# TODO namespaces
460513
return None
461514

462515
if not keyword_doc.is_error_handler:

0 commit comments

Comments
 (0)