Skip to content

feat: wire autocomplete state and input handling#134

Draft
yamaceay wants to merge 5 commits into
stippi:mainfrom
yamaceay:feat/autocomplete-state
Draft

feat: wire autocomplete state and input handling#134
yamaceay wants to merge 5 commits into
stippi:mainfrom
yamaceay:feat/autocomplete-state

Conversation

@yamaceay

@yamaceay yamaceay commented Jun 10, 2026

Copy link
Copy Markdown

Context

PR 4 of 5 — slash-command autocomplete series.

Dependency / merge order:

#136 → #131 → #132 → #133 → #134 (this PR) → #135

Why

With the command registry (PR #131) and current_line() (PR #133) in place,
this PR adds the data-flow layer: detecting when the user types a / prefix,
tracking which commands match, maintaining the selection index, and handling
the keyboard shortcuts that control the popup. No rendering yet — that's
PR #135.

Separating state/wiring from rendering keeps each PR reviewable on its own and
makes the renderer easier to test in isolation.

What

state.rs — autocomplete state in AppState

Three new fields:

Field Type Purpose
autocomplete_active bool Whether the popup is currently shown
autocomplete_query String Text after / on the current line
autocomplete_selected usize Index of the highlighted row

Three new helper methods:

  • open_autocomplete(query) — opens/refreshes the popup; resets selection to 0.
  • close_autocomplete() — hides the popup and clears all state.
  • move_autocomplete_selection(delta, count) — moves selection by ±1,
    wrapping with rem_euclid so arrow keys cycle naturally.

input.rs — input layer changes

  • InputManager.autocomplete_active: bool — mirrors AppState so key
    handling can intercept Up/Down/Tab/Escape without locking AppState.
  • slash_prefix() -> Option<String> — reads current_line(), returns
    Some(text_after_slash) or None. Called after every keystroke.
  • Three new KeyEventResult variants:
Variant When emitted
UpdateAutocomplete { query: Option<String> } After every keystroke / paste — None = close, Some(q) = open/refresh
MoveAutocomplete(i32) Up (-1) / Down (+1) while popup is open
SelectAutocomplete Tab while popup is open
  • Escape while autocomplete_active emits UpdateAutocomplete { query: None }
    (closes popup) instead of propagating as a session-cancel event.
  • Existing tests updated for the new return type of the _ => arm.
  • New tests: slash detection, arrow interception, Tab, Escape.

app.rs — event loop wiring

Three new match arms:

  • UpdateAutocomplete — filters all_commands() by prefix match on name
    or aliases; calls state.open_autocomplete / state.close_autocomplete;
    syncs input_manager.autocomplete_active.
  • MoveAutocomplete — delegates to state.move_autocomplete_selection.
  • SelectAutocomplete — finds the selected command name, replaces
    "/<query>" on the current input line with "/<name> " using
    TextArea::replace_range, closes the popup.
  • Bracketed-paste events also call slash_prefix() to keep state in sync.

Scope

state.rs, input.rs, app.rs.

Test plan

  • cargo test — new unit tests pass:
    • test_slash_prefix_detected
    • test_slash_prefix_absent_for_normal_text
    • test_escape_closes_autocomplete_when_active
    • test_arrow_keys_navigate_autocomplete_when_active
    • test_tab_selects_autocomplete_when_active
  • cargo build --no-default-features passes.
  • Manual (with PR feat: render slash-command autocomplete popup in TUI #135 on top): type /m → only /model matches;
    Tab → textarea shows /model .

yamaceay added 5 commits June 11, 2026 10:40
The GPUI desktop UI depends on Metal shader compilation, which requires
Xcode developer tools to be installed (xcrun must find the `metal` binary).
On machines without Xcode this hard-blocked every `cargo build`, even when
only the terminal TUI was needed.

Introduce a `gpui-ui` Cargo feature that gates all GPUI-related code:

- `Cargo.toml`: `gpui`, `gpui_platform`, and `gpui-component` are now
  optional dependencies, activated only by `--features gpui-ui`.
  The gpui entry in `[dev-dependencies]` is removed (it was unused in
  the unit-test suite).

- `src/app/mod.rs`, `src/ui/mod.rs`: `pub mod gpui` gated behind
  `#[cfg(feature = "gpui-ui")]`.

- `src/ui/backend.rs`: The `GpuiTerminalCommandExecutor` import and its
  single usage site are gated; the TUI-only path falls back to
  `DefaultCommandExecutor` (which `GpuiTerminalCommandExecutor` itself
  delegates to internally).

- `src/main.rs`: The `--ui` code paths (logging setup, model resolution,
  `app::gpui::run()`) are gated.  Passing `--ui` without the feature
  produces a clear error message rather than a linker crash.

- `src/cli.rs`: The `UiSettings::load()` call that reads the GPUI-
  persisted default model is gated; TUI-only builds fall through to the
  alphabetical-first-model fallback.

Build commands:
  # TUI only (no Xcode required):
  cargo build --no-default-features

  # With GPUI desktop UI (requires Xcode / Metal):
  cargo build --features gpui-ui
Introduce a `SlashCommand` struct (name, aliases, description) and an
`all_commands()` function that returns a static slice of all registered
commands.  This gives the rest of the codebase — in particular the
upcoming autocomplete popup — a single, authoritative place to discover
what slash commands exist, without needing to duplicate the match arms
in `process_command`.

The help text is now derived from the registry instead of being a hard-
coded string, so adding a new command in one place is sufficient.

No behavior change: all existing commands keep the same names, aliases,
and runtime behaviour.
Adds two new slash commands to the terminal TUI:

- `/clear`   — wipes the conversation message history (message nodes,
               active path, plan) for the current session, then clears
               the UI transcript.  The session itself stays alive so the
               user can continue without re-starting.
- `/compact` — placeholder that shows an informational error until a
               proper summarisation implementation lands.

The data path is:
  CommandProcessor (commands.rs)
    → KeyEventResult::{ClearContext,CompactContext}  (input.rs)
    → BackendEvent::{ClearContext,CompactContext}     (app.rs)
    → handler in handle_backend_events               (backend.rs)
    → UiEvent::ClearMessages / UiEvent::DisplayError (terminal UI)
Add a public `current_line() -> &str` method that returns the text of the
logical line the cursor is currently on (i.e. the content between the two
surrounding newlines, or the start/end of the buffer).

This is a thin wrapper over the two existing private helpers
`beginning_of_current_line()` and `end_of_current_line()`; no logic
is duplicated and no behavior changes.

The method is the entry point for the upcoming slash-command autocomplete
(PR 4), which needs to inspect what the user has typed on the current line
without re-parsing the entire textarea buffer.

Four unit tests cover: single-line content, cursor on first/last line of
multi-line content, and the empty-textarea edge case.
Adds the data-flow layer for the slash-command autocomplete popup.
No visual rendering yet — that lands in the next PR.

## state.rs
- Three new fields on `AppState`: `autocomplete_active`, `autocomplete_query`
  (text after the `/`), `autocomplete_selected` (highlighted row index).
- Three new helpers: `open_autocomplete(query)`, `close_autocomplete()`,
  `move_autocomplete_selection(delta, count)` (wraps around with rem_euclid).

## input.rs
- `autocomplete_active: bool` added to `InputManager` so Up/Down/Tab can be
  intercepted when the popup is open without checking `AppState` from inside
  the input layer.
- New `KeyEventResult` variants:
  - `UpdateAutocomplete { query: Option<String> }` — emitted after every
    keystroke; `None` means close the popup, `Some(q)` means open/update.
  - `MoveAutocomplete(i32)` — Up/Down while popup open.
  - `SelectAutocomplete` — Tab (or Enter via popup) while popup open.
- The `_ =>` fallback arm now returns `UpdateAutocomplete` instead of
  `Continue`; the rest of the code-paths (Shift+Enter) do likewise.
- Escape while `autocomplete_active` closes the popup instead of bubbling.
- `slash_prefix()` is now public so `app.rs` can call it after paste events.
- Tests updated for the new return types; new tests cover slash detection,
  arrow-key interception, Tab selection, and Escape dismissal.

## app.rs
- Imports `all_commands()` for filtering.
- Three new match arms in the event loop:
  - `UpdateAutocomplete` — filters `all_commands()` by prefix, calls
    `state.open_autocomplete` / `state.close_autocomplete`, syncs
    `input_manager.autocomplete_active`.
  - `MoveAutocomplete` — delegates to `state.move_autocomplete_selection`.
  - `SelectAutocomplete` — computes the selected command name, replaces
    `"/<query>"` on the current line with `"/<name> "`, closes popup.
- Paste handling now also checks `slash_prefix()` to keep autocomplete in
  sync after bracketed-paste events.
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