Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 22 additions & 18 deletions cli/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,32 +155,36 @@ def get_completions(self, document, complete_event):

for cmd, desc in SLASH_COMMANDS.items():
if cmd.startswith(partial):
padded_cmd = cmd[1:].ljust(16)
safe_desc = _xml_escape(desc)
yield Completion(
cmd,
start_position=-len(text),
display=HTML(
f"<command>{padded_cmd}</command><description>{safe_desc}</description>"
),
display_meta=desc,
)
yield self._command_completion(cmd, cmd[1:], desc, desc, text)

if self._skill_manager:
for skill in self._skill_manager.list_skills():
skill_cmd = f"/{skill['name']}"
if skill_cmd.startswith(partial) and skill_cmd not in SLASH_COMMANDS:
padded_cmd = skill["name"].ljust(16)
safe_desc = _xml_escape(skill["description"])
yield Completion(
yield self._command_completion(
skill_cmd,
start_position=-len(text),
display=HTML(
f"<command>{padded_cmd}</command><description>{safe_desc}</description>"
),
display_meta=f"skill: {skill['description']}",
skill["name"],
skill["description"],
f"skill: {skill['description']}",
text,
)

@staticmethod
def _command_completion(cmd: str, label: str, desc: str, meta: str, typed: str) -> Completion:
# prompt_toolkit hides a completion that is identical to the text already typed, so the
# dropdown (and the command's description) vanished the moment you finished typing `/cost`.
# On an exact match offer "<cmd> " instead: it differs, so the entry stays visible, and a
# trailing space is where arguments start anyway.
insert = cmd + " " if cmd == typed.lower() else cmd
return Completion(
insert,
start_position=-len(typed),
display=HTML(
f"<command>{label.ljust(16)}</command><description>{_xml_escape(desc)}</description>"
),
display_meta=meta,
)


def _path_completions_for(partial: str, limit: int = 30):
"""Yield (completion_text, is_dir) for paths matching a partial @mention."""
Expand Down
16 changes: 16 additions & 0 deletions tests/test_boxed_input.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,3 +191,19 @@ def test_empty_prompt_is_one_line_and_toolbar_shows_the_mode(cols):
assert "[ASK]" in lines[-1], "mode clipped off the toolbar" # the reported bug
if cols >= 60:
assert "tok" in lines[-1]


def test_dropdown_stays_open_when_the_command_is_fully_typed():
# regression: typing `/cos` showed the dropdown but `/cost` hid it
for text in ("/cos", "/cost", "/models"):
lines = render_live(
text,
rows=12,
wait=0.5,
completer=SlashCommandCompleter(),
complete_while_typing=True,
**common(Holder()),
)
menu = [line for line in lines[2:] if line.strip()]
assert menu, f"dropdown vanished at {text!r}"
assert menu[0].strip().startswith(text[1:]), menu
56 changes: 56 additions & 0 deletions tests/test_prompt_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -229,3 +229,59 @@ def test_clear_screen_only_on_a_real_terminal():
def test_clear_screen_can_be_disabled_with_env():
assert not should_clear_screen(True, env={"DEVORCH_NO_CLEAR": "1"})
assert should_clear_screen(True, env={"DEVORCH_NO_CLEAR": ""})


# ── slash dropdown stays visible on an exact match ──────────────────────────

from prompt_toolkit.document import Document # noqa: E402

from cli.constants import SLASH_COMMANDS, SlashCommandCompleter # noqa: E402


def _complete(text):
return [c.text for c in SlashCommandCompleter().get_completions(Document(text), None)]


@pytest.mark.parametrize("cmd", list(SLASH_COMMANDS))
def test_every_command_keeps_a_dropdown_entry_as_you_finish_typing_it(cmd):
# prompt_toolkit hides a completion identical to the typed text; the entry must differ from it
for i in range(2, len(cmd) + 1):
typed = cmd[:i]
entries = _complete(typed)
assert entries, f"dropdown empty at {typed!r}"
assert all(e != typed for e in entries), (
f"identical completion at {typed!r} would be hidden"
)
assert cmd + " " in _complete(cmd)


def test_exact_match_inserts_a_trailing_space_other_matches_do_not():
assert _complete("/cost") == ["/cost "]
assert "/mode " in _complete("/mode") and "/models" in _complete("/mode")


def test_no_dropdown_once_arguments_start():
assert _complete("/cost ") == []
assert _complete("/model gpt") == []


def test_exact_match_is_case_insensitive():
assert _complete("/COST") == ["/cost "]


def test_enter_on_an_exact_match_submits_the_typed_text_unchanged():
assert (
run_prompt("/cost\r", completer=SlashCommandCompleter(), complete_while_typing=True)
== "/cost"
)


def test_skills_also_keep_their_dropdown_entry():
class Skills:
def list_skills(self):
return [{"name": "deploy", "description": "Deploy it"}]

comps = [
c.text for c in SlashCommandCompleter(Skills()).get_completions(Document("/deploy"), None)
]
assert comps == ["/deploy "]
Loading