Skip to content

Fix @ context menu not triggering when the prompt contains multi-byte text - #14798

Draft
warp-agent-staging[bot] wants to merge 1 commit into
masterfrom
fix/14786-at-menu-multibyte-cursor-offset
Draft

Fix @ context menu not triggering when the prompt contains multi-byte text#14798
warp-agent-staging[bot] wants to merge 1 commit into
masterfrom
fix/14786-at-menu-multibyte-cursor-offset

Conversation

@warp-agent-staging

Copy link
Copy Markdown
Contributor

Description

The @ context menu did not open when the prompt already contained multi-byte text (e.g. Chinese).

Root cause: the editor reports cursor positions as byte offsets (EditorView::start_byte_index_of_last_selection), but the AI context menu code inspected the surrounding buffer by char index. For ASCII the two coincide, so the bug was invisible; with multi-byte text they diverge. For 你好 @ the cursor is at byte 8 while the buffer only has 4 chars, so should_enable_ai_context did buffer_text.chars().nth(7)None, never saw the just-typed @, and the menu never opened.

Changes (all in the @ menu path):

  • Added text_before_byte_offset in app/src/terminal/input.rs, built on the existing search::ai_context_menu::floor_char_boundary, and used it to read the characters before the cursor.
  • should_enable_ai_context: detect the just-typed @ and its preceding character by slicing on the byte offset instead of chars().nth(..).
  • handle_ai_context_menu_search: extract the filter text between @ and the cursor with a byte-range slice instead of chars().skip(..).take(..), so the query is correct after multi-byte text.
  • should_close_ai_context_menu: walk back from the cursor over text_before_byte_offset(..) rather than chars().take(byte_offset), which previously scanned characters after the cursor for multi-byte buffers (and dropped an intermediate Vec<char> allocation).
  • set_ai_context_menu_open (the @ toolbar button): detect an existing trailing @ by byte offset, and anchor at_symbol_position to the byte offset of that @. Previously, with a multi-byte prompt ending in @, the button inserted a second @; and when it did reuse an existing @ it stored the offset one byte too far, so accepting an item left a stray @ in the buffer.
  • is_valid_search_query: skip by prev_query.chars().count() rather than prev_query.len() (bytes), so cursor-navigation invalidation works for multi-byte queries.

at_symbol_position remains a byte offset everywhere (it is fed to ByteOffset::from(..) in replace_at_symbol_with_text, clear_and_reset_ai_context_menu_query, and SelectAIContextMenuCategory), so no call site changes semantics.

Linked Issue

Fixes #14786

  • The linked issue is labeled factory-auto-implement.
  • Where appropriate, screenshots or a short video of the implementation are included below.

Testing

Added regression tests that fail before this change and pass after it:

  • app/src/terminal/input_tests.rs::test_ai_context_menu_opens_after_multibyte_text — types 你好 then @, asserts the menu opens with at_symbol_position == 7, then types f and asserts filter_text == "f"; also re-checks the ASCII hello @ baseline. The multi-byte case runs first so stale menu state from the ASCII case cannot mask the bug.
  • app/src/terminal/input_tests.rs::test_ai_context_menu_button_reuses_existing_at_symbol_after_multibyte_text — clicking the @ button with the buffer 你好 @ must not insert a duplicate @ and must anchor at byte 7.
  • app/src/search/ai_context_menu/search_tests.rs — new unit tests for is_valid_search_query, including the byte-vs-char skip.

Commands run (Linux sandbox, x86_64 Debian):

  • cargo test -p warp --lib ai_context_menu75 passed, 0 failed (includes the three new tests).
  • Same filter with the production fix reverted (tests kept) → both new input_tests.rs tests fail, confirming they are real regression tests.
  • cargo test -p warp --lib terminal::input207 passed, 2 failed. The two failures — terminal::input::decorations::tests::test_decorations_with_multibyte_chars and terminal::input::tests::test_histignorespace_support_in_zsh — are pre-existing: they fail identically on this branch with all of my changes stashed (verified), and are environmental (no zsh / no shell-metadata in the sandbox).
  • ./script/format --check → clean.
  • cargo clippy -p warp --all-targets --tests -- -D warnings → clean (exit 0).

Not verified: this was developed in a headless Linux sandbox with no display, so the change was not exercised by running the GUI app (./script/run) and the reporter's macOS scenario was not reproduced manually. Verification is limited to the automated tests above.

  • I have manually tested my changes locally with ./script/run

Agent Mode

  • Warp Agent Mode - This PR was created via Warp's AI Agent Mode

Conversation: https://staging.warp.dev/conversation/2024116e-c43f-49c7-b142-a14dcdf8e2b3
Run: https://oz.staging.warp.dev/runs/019fd7cf-1d07-77c1-bec4-d8386a7c8c84

This PR was generated with Oz.

The editor reports cursor positions as byte offsets, but the AI context
menu ("@" menu) inspected the surrounding text by `char` index. The two
coincide for ASCII, so the bug only showed up once the prompt contained
multi-byte characters: in `你好 @` the cursor is at byte 8 while the
buffer only has 4 chars, so `should_enable_ai_context` looked past the
end of the buffer, never saw the just-typed "@", and the menu never
opened.

Slice the buffer by byte offset instead, via a new
`text_before_byte_offset` helper built on the existing
`floor_char_boundary`. The same mix-up is fixed in the filter-text
extraction, the close-the-menu heuristic, the "@" toolbar button, and
the navigation heuristic in `is_valid_search_query`.

Fixes #14786

Co-Authored-By: Warp Agent <agent@warp.dev>
@cla-bot cla-bot Bot added the cla-signed label Aug 6, 2026

@warp-agent-staging warp-agent-staging Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Review: byte-offset fix for the @ context menu

Verdict: Request changes — the code change itself looks correct and I found no correctness defect in it, but this is a user-visible behavior fix with no visual evidence and no CI signal yet (see Blockers).

What I verified

I audited every at_symbol_position producer and consumer in app/src/terminal/input.rs, and independently re-ran the changed logic (old vs. new implementations) in a standalone std-only harness.

  • Root cause is accurate. EditorView::start_byte_index_of_last_selection returns a byte offset; should_enable_ai_context indexed the buffer by char. For 你好 @ the cursor is byte 8 with only 4 chars, so chars().nth(7)None. Confirmed: old impl returns false, new impl returns true.
  • at_symbol_position is now consistent end to end. All producers (Edited handler cursor_pos - 1; set_ai_context_menu_open) and all consumers (handle_ai_context_menu_search, clear_and_reset_ai_context_menu_query, should_close_ai_context_menu, the SelectionChanged arm, the cursor_pos == at + 1 reset check, replace_at_symbol_with_text, SelectAIContextMenuCategory) treat it as the byte offset of the @ itself. I found no remaining mismatch.
  • No remaining char/byte mixing in this path. After the change, input.rs has no chars().nth(...), chars().take(...), or chars().skip(...) left in the @ menu code, and search.rs:13 was the last one.
  • command_at_cursor_has_common_package_installer_prefix(buffer_text, cursor_position - 1, ..) was already byte-correct — it wraps its argument in ByteOffset::from(..). No change needed there, good call leaving it alone.
  • No new panic risk. floor_char_boundary clamps past-the-end and floors mid-char offsets; str::get(..) returns None instead of panicking on a non-boundary range; cursor_position - 1 cannot underflow because ends_with('@') implies a non-empty prefix. Verified all four edge cases.
  • ASCII parity holds for should_enable_ai_context (hello @, @, mail@, a @, x@ all produce identical old/new results) and for filter-text extraction.

Blockers

  1. Missing visual evidence for a user-facing change (blocking per .agents/skills/review-pr-local/SKILL.md). The @ menu opening is directly user-perceivable, the Screenshots/Videos section is empty, and ./script/run was not exercised. Per repo policy, author environment limitations (headless sandbox) do not exempt a UI-impacting change from this requirement. Please attach a short recording of typing 你好 @ and the menu opening, plus selecting an item so the @ is replaced correctly. If no local desktop is available, capture it from a coding agent with computer use enabled.
  2. No CI signal. The Warp CI jobs are currently SKIPPED (draft), so build/test/clippy have not been confirmed on any platform other than the author's Linux sandbox — notably not macOS, where the bug was reported. CI must be green before this is taken out of draft.

Medium

  1. set_ai_context_menu_open also fixes a pre-existing ASCII bug, and it is untested. Beyond the duplicate-@ insertion for multi-byte prompts, this changes at_symbol_position from cursor to cursor - 1 when reusing an existing @. Confirmed: for hello @ with cursor at byte 7, the old code anchored at 7 (one past the @), so replace_at_symbol_with_text deleted an empty range and accepting an item left a stray @ (hello @file.txt). The new anchor of 6 is right, and this is a worthwhile fix — but it is a behavior change for all users, not just multi-byte ones, and the PR description frames it as multi-byte-only. Please add an ASCII case asserting at_symbol_position == 6 for hello @, and ideally an end-to-end assertion that accepting a menu item replaces the @ rather than appending after it. That end-to-end assertion is the strongest available regression guard, since it is the actual consumer of at_symbol_position.
  2. should_close_ai_context_menu — one of the three fixed paths — has no regression test. The old chars().take(cursor_pos) scanned characters after the cursor for multi-byte buffers (e.g. with 你好 @f\nbar and the cursor at byte 9, the old walk sees the \n and closes the menu incorrectly). Please add a case that keeps the menu open while editing after multi-byte text.

Low

  1. handle_ai_context_menu_search silently swallows an invalid range. buffer_text.get(at + 1..cursor).unwrap_or_default() turns any invalid/stale/non-boundary range into an empty filter, which shows the unfiltered menu rather than closing it. That is a safe default and matches the old behavior closely enough, but consider closing the menu when get returns None — an out-of-range or mid-char at_symbol_position means the anchor is stale (e.g. after BufferReplaced) and the menu is no longer meaningful.
  2. test_ai_context_menu_opens_after_multibyte_text covers three behaviors (menu opens after multi-byte text, filter text extracted after multi-byte text, ASCII baseline). .agents/skills/rust-unit-tests/SKILL.md asks for one behavior per test named after that behavior; the ASCII tail is also partly redundant with existing coverage. Suggest splitting into ..._opens_after_multibyte_text and ..._extracts_filter_text_after_multibyte_text.
  3. Expected values are computed rather than literal. at_symbol_position: "你好 ".len() re-derives the expected value; the same skill asks for literal expected values (7) so an assertion cannot reproduce the bug under test. The inline comment already explains where 7 comes from.

Nits

  1. The doc comment on text_before_byte_offset is genuinely helpful. Consider adding one sentence noting it floors to a char boundary, so callers know out-of-range or mid-char offsets are clamped silently rather than rejected.

Verification I could not perform

I did not independently reproduce the reported cargo test numbers. Building this workspace makes cargo clone third-party git dependency repositories, which is outside the set of repositories this review is permitted to access, so I stopped the build. The logic verification above was done with a standalone std-only reimplementation of the changed functions instead. CI (blocker 2) is the authoritative check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@ context menu does not trigger when the prompt contains Chinese text

1 participant