Skip to content

Commit dc5d0be

Browse files
committed
fix(repl): keep keyword completion working after a return-value assignment
Starting a line with a return-value assignment — `${result}= Some Keyword`, or several targets like `${a} ${b}= …` — no longer turns off keyword completion in the keyword cell. Completion and Tab now skip the assignment targets and complete the keyword (and then its arguments) just as they would without an assignment.
1 parent 6507af5 commit dc5d0be

2 files changed

Lines changed: 45 additions & 1 deletion

File tree

packages/repl/src/robotcode/repl/_pt/completion.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,11 @@ def current_keyword_and_arg_index(buffer: str, cursor: int) -> Optional[Tuple[st
158158

159159
_VALID_SIGILS = "$@&%"
160160

161+
# A return-value assignment target: `${x}` / `@{x}` / `&{x}` (no `%{env}`), with
162+
# an optional trailing `=` (Robot attaches it to the last target). Used to skip
163+
# the assignment prefix before the keyword cell, e.g. `${r}= Some Keyword`.
164+
_ASSIGN_CELL = re.compile(r"^[$@&]\{[^{}]*\}\s*=?$")
165+
161166

162167
@dataclass(frozen=True)
163168
class CompletionContext:
@@ -209,7 +214,21 @@ def tokenize(buffer: str, cursor: int, *, setting_import_aliases: bool = False)
209214
if not prior_cells:
210215
return CompletionContext("keyword", current_cell, cell_start)
211216

212-
first_cell = prior_cells[0].strip()
217+
# Return-value assignment: skip block-body indentation (leading empty cells)
218+
# and any leading assignment targets (`${x}` / `@{x}` / `&{x}`, the last
219+
# optionally ending with `=`) so the keyword cell still gets keyword
220+
# completion and what follows is classified against the real keyword.
221+
lead = 0
222+
while lead < len(prior_cells) and not prior_cells[lead].strip():
223+
lead += 1
224+
assign_end = lead
225+
while assign_end < len(prior_cells) and _ASSIGN_CELL.match(prior_cells[assign_end].strip()):
226+
assign_end += 1
227+
if assign_end == len(prior_cells):
228+
# nothing but indentation and assignment targets so far → keyword cell
229+
return CompletionContext("keyword", current_cell, cell_start)
230+
231+
first_cell = prior_cells[assign_end].strip()
213232
folded = first_cell.casefold()
214233
if folded == "import library" or (setting_import_aliases and folded == "library"):
215234
return CompletionContext("library", current_cell, cell_start)

tests/robotcode/repl/test_completion.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,31 @@ def test_tokenize_setting_aliases_not_routed_without_optin() -> None:
129129
assert tokenize("Library Coll", cursor=15).kind == "argument"
130130

131131

132+
def test_tokenize_return_assignment_keeps_keyword_completion() -> None:
133+
# A leading return-value assignment must not steal keyword completion from
134+
# the keyword cell. The `=` is optional and may sit on the last of several
135+
# targets; `${}`/`@{}`/`&{}` all count.
136+
for buf in (
137+
"${result}= Log To Con",
138+
"${result} Log To Con",
139+
"${a} ${b}= Some Key",
140+
"@{items}= Create Lis",
141+
"&{opts}= Create Dic",
142+
):
143+
assert tokenize(buf, len(buf)).kind == "keyword", buf
144+
145+
146+
def test_tokenize_return_assignment_then_keyword_args_route_normally() -> None:
147+
# After the assignment, the keyword and its args classify against the real
148+
# keyword — import routing and plain argument cells still work.
149+
lib = "${x}= Import Library Coll"
150+
assert tokenize(lib, len(lib)).kind == "library"
151+
alias = "${x}= Library Coll"
152+
assert tokenize(alias, len(alias), setting_import_aliases=True).kind == "library"
153+
arg = "${x}= Log msg"
154+
assert tokenize(arg, len(arg)).kind == "argument"
155+
156+
132157
@pytest.mark.parametrize("sigil", ["$", "@", "&", "%"])
133158
def test_tokenize_variable_opener_anywhere_in_cell(sigil: str) -> None:
134159
line = f"Log Hello {sigil}{{world"

0 commit comments

Comments
 (0)