From 997fe4695642f974f0479c6bc72dc66287e6efde Mon Sep 17 00:00:00 2001 From: altair823 Date: Sun, 3 May 2026 09:38:52 +0000 Subject: [PATCH] review(p9-fb-10-task2): cursor comment + clamp + test rigor + dedup alloc Co-Authored-By: Claude Sonnet 4.6 --- crates/kebab-tui/src/search.rs | 21 +++++++++++++-------- crates/kebab-tui/tests/search.rs | 4 +--- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/crates/kebab-tui/src/search.rs b/crates/kebab-tui/src/search.rs index d069a73..a13a255 100644 --- a/crates/kebab-tui/src/search.rs +++ b/crates/kebab-tui/src/search.rs @@ -86,12 +86,16 @@ fn render_input_bar(f: &mut Frame, area: Rect, s: &SearchState, theme: &crate::t .borders(Borders::ALL); let inner = block.inner(area); f.render_widget(Paragraph::new(line).block(block), area); - // p9-fb-10: place the terminal cursor inside the block border at - // `prompt_w + cursor_col` columns from the inner left edge. - // The `Hide` call in terminal.rs keeps the caret invisible in - // normal use; placing it here is still correct for any consumer - // that flips the cursor on (e.g. a future INSERT-mode `Show`). - let cursor_x = inner.x + (prompt_w + s.input.cursor_col()) as u16; + // p9-fb-10: ratatui calls show_cursor + MoveTo whenever + // cursor_position is Some (our case here). When a render fn + // omits set_cursor_position (Library/Inspect), ratatui calls + // hide_cursor instead. So this single call both positions and + // unhides the caret for the Search input column. + let raw_x = inner.x + (prompt_w + s.input.cursor_col()) as u16; + // Clamp to the right edge of the inner area — a long CJK query + // in a narrow terminal could otherwise place the caret beyond + // the box; crossterm passes coords through verbatim. + let cursor_x = raw_x.min(inner.x + inner.width.saturating_sub(1)); let cursor_y = inner.y; f.set_cursor_position((cursor_x, cursor_y)); } @@ -512,8 +516,9 @@ pub(crate) fn fire_search(state: &mut App) -> anyhow::Result<()> { s.generation = s.generation.wrapping_add(1); s.searching = true; s.input_dirty_at = None; - s.last_query = Some((s.input.as_str().to_string(), s.mode)); - (s.input.as_str().to_string(), s.mode, s.generation) + let q_text = s.input.as_str().to_string(); + s.last_query = Some((q_text.clone(), s.mode)); + (q_text, s.mode, s.generation) }; let (tx, rx) = std::sync::mpsc::channel(); diff --git a/crates/kebab-tui/tests/search.rs b/crates/kebab-tui/tests/search.rs index 66ef83c..0d03f43 100644 --- a/crates/kebab-tui/tests/search.rs +++ b/crates/kebab-tui/tests/search.rs @@ -92,7 +92,6 @@ fn backspace_removes_last_char() { let mut app = fresh_app(); { let s = app.search.as_mut().unwrap(); - s.input.clear(); s.input.push_str("abc"); } handle_key_search( @@ -100,6 +99,7 @@ fn backspace_removes_last_char() { KeyEvent::new(KeyCode::Backspace, KeyModifiers::NONE), ); assert_eq!(app.search.as_ref().unwrap().input.as_str(), "ab"); + assert_eq!(app.search.as_ref().unwrap().input.cursor_col(), 2); } #[test] @@ -125,7 +125,6 @@ fn enter_with_query_emits_refresh() { let mut app = fresh_app(); { let s = app.search.as_mut().unwrap(); - s.input.clear(); s.input.push_str("rust"); } let outcome = handle_key_search( @@ -218,7 +217,6 @@ fn render_search_with_hits_shows_input_and_path() { let mut app = fresh_app(); { let s = app.search.as_mut().unwrap(); - s.input.clear(); s.input.push_str("rust traits"); s.mode = SearchMode::Hybrid; s.hits = vec![