Skip to content

Commit 779fd70

Browse files
committed
refactor(langserver): simplify variable renaming
1 parent 46e6b8a commit 779fd70

File tree

4 files changed

+99
-54
lines changed

4 files changed

+99
-54
lines changed

packages/core/src/robotcode/core/text_document.py

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,70 @@ def get_lines(self) -> List[str]:
216216

217217
return self._lines
218218

219+
def get_text(self, r: Range) -> str:
220+
lines = self.get_lines()
221+
222+
start_pos = r.start
223+
end_pos = r.end
224+
225+
# Validate range
226+
if start_pos.line < 0 or start_pos.character < 0:
227+
raise InvalidRangeError(f"Invalid start position: {start_pos}")
228+
if end_pos.line < 0 or end_pos.character < 0:
229+
raise InvalidRangeError(f"Invalid end position: {end_pos}")
230+
if start_pos > end_pos:
231+
raise InvalidRangeError(f"Start position is greater than end position: {r}")
232+
233+
# Handle case where range is beyond document
234+
if start_pos.line >= len(lines):
235+
return ""
236+
237+
# Single line case
238+
if start_pos.line == end_pos.line:
239+
if start_pos.line < len(lines):
240+
line = lines[start_pos.line]
241+
# Handle newline character at end of line
242+
if line.endswith(("\n", "\r\n")):
243+
line_content = line.rstrip("\r\n")
244+
if end_pos.character > len(line_content):
245+
# Include the newline character(s)
246+
return line[start_pos.character :]
247+
248+
return line_content[start_pos.character : end_pos.character]
249+
250+
return line[start_pos.character : end_pos.character]
251+
return ""
252+
253+
# Multi-line case
254+
result_lines = []
255+
256+
# First line
257+
if start_pos.line < len(lines):
258+
first_line = lines[start_pos.line]
259+
if first_line.endswith(("\n", "\r\n")):
260+
result_lines.append(first_line[start_pos.character :])
261+
else:
262+
result_lines.append(first_line[start_pos.character :] + "\n")
263+
264+
# Middle lines
265+
for line_idx in range(start_pos.line + 1, min(end_pos.line, len(lines))):
266+
result_lines.append(lines[line_idx])
267+
268+
# Last line
269+
if end_pos.line < len(lines):
270+
last_line = lines[end_pos.line]
271+
if last_line.endswith(("\n", "\r\n")):
272+
line_content = last_line.rstrip("\r\n")
273+
if end_pos.character > len(line_content):
274+
# Include the newline character(s)
275+
result_lines.append(last_line)
276+
else:
277+
result_lines.append(line_content[: end_pos.character])
278+
else:
279+
result_lines.append(last_line[: end_pos.character])
280+
281+
return "".join(result_lines)
282+
219283
@event
220284
def cache_invalidate(sender) -> None: ...
221285

packages/language_server/src/robotcode/language_server/common/parts/rename.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,7 @@ def extend_capabilities(self, capabilities: ServerCapabilities) -> None:
4646
)
4747

4848
@event
49-
def collect(
50-
sender,
51-
document: TextDocument,
52-
position: Position,
53-
new_name: str,
54-
) -> Optional[WorkspaceEdit]: ...
49+
def collect(sender, document: TextDocument, position: Position, new_name: str) -> Optional[WorkspaceEdit]: ...
5550

5651
@event
5752
def collect_prepare(sender, document: TextDocument, position: Position) -> Optional[PrepareRenameResult]: ...
@@ -81,6 +76,9 @@ def _text_document_rename(
8176
check_current_task_canceled()
8277

8378
if isinstance(result, BaseException):
79+
if isinstance(result, CantRenameError):
80+
raise JsonRPCErrorException(ErrorCodes.INVALID_PARAMS, str(result))
81+
8482
if not isinstance(result, CancelledError):
8583
self._logger.exception(result, exc_info=result)
8684
else:

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

Lines changed: 29 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
from robot.parsing.lexer.tokens import Token
1616
from robot.parsing.model.statements import Statement
1717

18+
from robotcode.core.concurrent import check_current_task_canceled
1819
from robotcode.core.language import language_id
1920
from robotcode.core.lsp.types import (
2021
AnnotatedTextEdit,
@@ -25,6 +26,7 @@
2526
Position,
2627
PrepareRenameResult,
2728
PrepareRenameResultType1,
29+
Range,
2830
RenameFile,
2931
TextDocumentEdit,
3032
WorkspaceEdit,
@@ -142,7 +144,7 @@ def _prepare_rename_default(
142144
) -> Optional[PrepareRenameResult]:
143145
result = self._find_default(nodes, document, position)
144146
if result is not None:
145-
var, token = result
147+
var, found_range = result
146148

147149
if var.type == VariableDefinitionType.BUILTIN_VARIABLE:
148150
self.parent.window.show_message("You cannot rename a builtin variable, only references are renamed.")
@@ -158,13 +160,12 @@ def _prepare_rename_default(
158160
"Only references are renamed and you have to rename the variable definition yourself."
159161
)
160162
elif var.type == VariableDefinitionType.ENVIRONMENT_VARIABLE:
161-
token.value, _, _ = token.value.partition("=")
162163
self.parent.window.show_message(
163164
"You are about to rename an environment variable. "
164165
"Only references are renamed and you have to rename the variable definition yourself."
165166
)
166167

167-
return PrepareRenameResultType1(range_from_token(token), token.value)
168+
return PrepareRenameResultType1(found_range, document.get_text(found_range))
168169

169170
return None
170171

@@ -176,7 +177,11 @@ def _rename_default(
176177
new_name: str,
177178
) -> Optional[WorkspaceEdit]:
178179
result = self._find_default(nodes, document, position)
179-
180+
if " " in new_name or "\t" in new_name:
181+
raise CantRenameError(
182+
"Variable names cannot contain more then one spaces or tabs. "
183+
"Please use only one space or underscores instead.",
184+
)
180185
if result is not None:
181186
var, _ = result
182187

@@ -209,49 +214,29 @@ def _rename_default(
209214

210215
def _find_default(
211216
self, nodes: List[ast.AST], document: TextDocument, position: Position
212-
) -> Optional[Tuple[VariableDefinition, Token]]:
213-
from robot.parsing.lexer.tokens import Token as RobotToken
214-
217+
) -> Optional[Tuple[VariableDefinition, Range]]:
215218
namespace = self.parent.documents_cache.get_namespace(document)
216219

217-
if not nodes:
218-
return None
219-
220-
node = nodes[-1]
221-
222-
if not isinstance(node, Statement):
223-
return None
224-
225-
tokens = get_tokens_at_position(node, position)
226-
227-
token_and_var: Optional[Tuple[VariableDefinition, Token]] = None
228-
229-
for token in tokens:
230-
token_and_var = next(
231-
(
232-
(var, var_token)
233-
for var_token, var in self.iter_variables_from_token(token, namespace, nodes, position)
234-
if position in range_from_token(var_token)
235-
),
236-
None,
237-
)
238-
239-
if (
240-
token_and_var is None
241-
and isinstance(node, self.get_expression_statement_types())
242-
and (token := node.get_token(RobotToken.ARGUMENT)) is not None
243-
and position in range_from_token(token)
244-
):
245-
token_and_var = next(
246-
(
247-
(var, var_token)
248-
for var_token, var in self.iter_expression_variables_from_token(token, namespace, nodes, position)
249-
if position in range_from_token(var_token)
250-
),
251-
None,
252-
)
220+
all_variable_refs = namespace.get_variable_references()
221+
if all_variable_refs:
222+
for variable, var_refs in all_variable_refs.items():
223+
check_current_task_canceled()
224+
225+
found_range = (
226+
variable.name_range
227+
if variable.source == namespace.source and position.is_in_range(variable.name_range, False)
228+
else cast(
229+
Optional[Range],
230+
next(
231+
(r.range for r in var_refs if position.is_in_range(r.range)),
232+
None,
233+
),
234+
)
235+
)
253236

254-
return token_and_var
237+
if found_range is not None:
238+
return variable, found_range
239+
return None
255240

256241
def _prepare_rename_keyword(self, result: Optional[Tuple[KeywordDoc, Token]]) -> Optional[PrepareRenameResult]:
257242
if result is not None:

packages/robot/src/robotcode/robot/diagnostics/namespace_analyzer.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ def visit_Var(self, node: Var) -> None: # noqa: N802
357357

358358
var = var_type(
359359
name=matcher.name,
360-
name_token=strip_variable_token(stripped_name_token),
360+
name_token=stripped_name_token,
361361
line_no=stripped_name_token.lineno,
362362
col_offset=stripped_name_token.col_offset,
363363
end_line_no=stripped_name_token.lineno,
@@ -372,9 +372,7 @@ def visit_Var(self, node: Var) -> None: # noqa: N802
372372
else:
373373
existing_var = self._variables[var.matcher]
374374

375-
location = Location(
376-
self._namespace.document_uri, range_from_token(strip_variable_token(stripped_name_token))
377-
)
375+
location = Location(self._namespace.document_uri, range_from_token(stripped_name_token))
378376
self._variable_references[existing_var].add(location)
379377
if existing_var in self._overridden_variables:
380378
self._variable_references[self._overridden_variables[existing_var]].add(location)

0 commit comments

Comments
 (0)