Skip to main content

codetether_agent/tui/ui/chat_view/
cursor.rs

1//! Input-area cursor placement.
2//!
3//! [`place_cursor`] positions the terminal cursor inside the input rectangle
4//! accounting for multi-line text and Unicode wide characters.
5
6use ratatui::{Frame, layout::Rect};
7use unicode_width::UnicodeWidthStr;
8
9use crate::tui::app::state::App;
10
11/// Set the terminal cursor inside the input `area`.
12///
13/// Accounts for newlines (row offset) and wide characters (column offset)
14/// so the cursor visually lands at [`AppState::input_cursor`].
15///
16/// # Examples
17///
18/// ```rust,no_run
19/// // Requires a live terminal backend; cannot run in doc tests.
20/// use codetether_agent::tui::ui::chat_view::cursor::place_cursor;
21/// # fn demo(f: &mut ratatui::Frame, app: &codetether_agent::tui::app::state::App) {
22/// let area = ratatui::layout::Rect::new(0, 0, 40, 3);
23/// place_cursor(f, app, area);
24/// # }
25/// ```
26pub fn place_cursor(f: &mut Frame, app: &App, area: Rect) {
27    let text = &app.state.input;
28    let cursor_byte = text
29        .char_indices()
30        .nth(app.state.input_cursor)
31        .map(|(i, _)| i)
32        .unwrap_or(text.len());
33    let before = &text[..cursor_byte];
34    let row = before.matches('\n').count();
35    let last_nl = before.rfind('\n').map(|i| i + 1).unwrap_or(0);
36    let col = before[last_nl..].width();
37    let x = area
38        .x
39        .saturating_add(1)
40        .saturating_add(col as u16)
41        .min(area.x.saturating_add(area.width.saturating_sub(2)));
42    let y = area
43        .y
44        .saturating_add(1)
45        .saturating_add(row as u16)
46        .min(area.y.saturating_add(area.height.saturating_sub(2)));
47    f.set_cursor_position((x, y));
48}