Skip to content

feat(tui): completion dropdown for slash and @path - #576

Merged
emal-avala merged 2 commits into
mainfrom
feat/completion-dropdown
Jul 29, 2026
Merged

feat(tui): completion dropdown for slash and @path#576
emal-avala merged 2 commits into
mainfrom
feat/completion-dropdown

Conversation

@emal-avala

Copy link
Copy Markdown
Member

Summary

  • Shared completion dropdown for slash commands and @path mentions (MAX_VISIBLE_ROWS=6, label/description columns).
  • Unique prefix slash match still completes inline (/hel/help ).
  • Multiple matches: Tab/↓/↑ cycle, Enter accept, Esc dismiss — no more transcript spam.
  • Bundles ui/fuzzy + palette ranking (overlaps feat(tui): add fuzzy subsequence matcher and rank the command palette #573; can supersede it).

Addresses #560.

Test plan

  • completion unit tests (slash items, wrap, window)
  • tab completion path/slash tests updated for dropdown
  • tab_opens_slash_dropdown_for_ambiguous_prefix
  • clippy clean
  • CI green

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c9b4ba7089

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/cli/src/ui/modern/render.rs Outdated
Comment on lines +114 to +117
draw_input(frame, chunks[6], app);
if let Some(menu) = app.completion.as_ref() {
// Float above the composer within the full frame.
super::completion::draw(frame, chunks[6], menu);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Render the completion menu above the composer

For the default one-line fullscreen composer, chunks[6] is only four rows high, so passing it as the menu's clipping area makes the dropdown clear and overwrite the composer itself; after borders, menus with three or more matches display only two rows, and cycling can leave the selected row invisible. The menu needs an area extending above the composer while retaining the composer position as its anchor.

Useful? React with 👍 / 👎.

Comment on lines +2454 to +2456
// Completion dropdown captures navigation / accept / dismiss.
if app.completion.is_some() && app.phase != super::app::Phase::Permission {
match key.code {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Dismiss completion before opening another overlay

When a completion is open and the user presses Ctrl+P, Ctrl+F, or another overlay chord, this block leaves the completion alive and the later dispatch opens the requested overlay. Because completion handling runs before all picker/search handlers, the next plain Enter or arrow key operates on the hidden completion instead of the visible overlay; for example, Ctrl+P followed by Enter accepts the old slash candidate while leaving the command palette open.

Useful? React with 👍 / 👎.

Comment on lines +2485 to +2488
// Typing rebuilds the menu from the new input on next Tab;
// dismiss so characters land in the composer.
KeyCode::Char(_) | KeyCode::Backspace if key.modifiers.is_empty() => {
app.dismiss_completion();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Invalidate completion after pasted composer edits

The menu is dismissed only for character and Backspace key events, but bracketed paste bypasses this branch and calls insert_str directly. If a user opens completion for /se, pastes more text, and presses Enter, the old menu still replaces only its original byte range, potentially inserting a stale command and leaving the pasted suffix attached; any composer mutation, including paste and Delete, should dismiss or rebuild the menu.

Useful? React with 👍 / 👎.

@emal-avala

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c9b4ba7089

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread crates/cli/src/ui/modern/render.rs Outdated
draw_input(frame, chunks[6], app);
if let Some(menu) = app.completion.as_ref() {
// Float above the composer within the full frame.
super::completion::draw(frame, chunks[6], menu);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Render the completion menu outside the composer

Passing chunks[6] gives the dropdown only the composer rectangle, so completion::draw clips its height to that rectangle and then clears and redraws the same area. With the normal single-line fullscreen composer this hides the prompt and leaves only two menu rows inside the border, even though window() assumes six rows are visible; after cycling past the second item, the highlighted selection is therefore off-screen. Pass an area that extends above the composer or calculate the floating rectangle from the full frame.

Useful? React with 👍 / 👎.

insert.push(' ');
}
}
self.input.replace_range(start..end, &insert);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Invalidate completion ranges after pasted edits

The menu stores byte offsets from when Tab was pressed, but bracketed paste edits the composer without dismissing or rebuilding the menu. For example, after opening a menu, moving the cursor inside its token, and pasting a multibyte character such as é, the saved replace_end can fall inside that character; pressing Enter then panics at this replace_range (or the earlier path-only slice) because the index is no longer a UTF-8 boundary. Dismiss completion on paste and other composer mutations, or recompute and validate the token range before accepting.

Useful? React with 👍 / 👎.

Comment on lines +2454 to +2455
// Completion dropdown captures navigation / accept / dismiss.
if app.completion.is_some() && app.phase != super::app::Phase::Permission {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Close completion before opening another overlay

Completion routing runs before search and picker routing but allows chords such as Ctrl+P to fall through without closing the menu. Starting from an ambiguous completion, Ctrl+P therefore opens the command palette while leaving completion set; the next Up/Down, Enter, or Esc is consumed here by the hidden completion instead of the visible palette, and Enter can rewrite the composer behind it. Ensure overlays are mutually exclusive or only route completion keys when no higher-level overlay is open.

Useful? React with 👍 / 👎.

Tab with multiple matches opens a shared dropdown (label + description,
Tab cycle, Enter accept, Esc dismiss) instead of dumping names into the
transcript. Unique name-prefix slash hits still complete inline. Includes
the fuzzy matcher and palette ranking so providers share one scorer.
- Anchor the dropdown to the region above the composer so it no longer
  overwrites the input row.
- Dismiss completion when opening palette/search/theme/model/session
  pickers or the shortcuts overlay.
- Dismiss on paste so Enter cannot apply a stale replace range.
@emal-avala
emal-avala force-pushed the feat/completion-dropdown branch from 5dce377 to 02bbca9 Compare July 29, 2026 07:55
@emal-avala
emal-avala merged commit 5e01437 into main Jul 29, 2026
12 of 13 checks passed
@emal-avala
emal-avala deleted the feat/completion-dropdown branch July 29, 2026 07:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant