Skip to main content

codetether_agent/tui/app/state/
input_cursor.rs

1//! Input cursor movement methods.
2//!
3//! Character-index-aware cursor operations (arrows, word jumps, home/end)
4//! and visibility clamping for multi-byte UTF-8 input.
5
6impl super::AppState {
7    pub(crate) fn input_char_count(&self) -> usize {
8        self.input.chars().count()
9    }
10
11    pub(crate) fn char_to_byte_index(&self, char_index: usize) -> usize {
12        if char_index == 0 {
13            return 0;
14        }
15        self.input
16            .char_indices()
17            .nth(char_index)
18            .map(|(idx, _)| idx)
19            .unwrap_or_else(|| self.input.len())
20    }
21
22    fn char_at(&self, char_index: usize) -> Option<char> {
23        self.input.chars().nth(char_index)
24    }
25
26    pub fn clamp_input_cursor(&mut self) {
27        self.input_cursor = self.input_cursor.min(self.input_char_count());
28    }
29
30    #[allow(dead_code)]
31    pub fn ensure_input_cursor_visible(&mut self, visible_width: usize) {
32        if visible_width == 0 {
33            self.input_scroll = self.input_cursor;
34            return;
35        }
36        if self.input_cursor < self.input_scroll {
37            self.input_scroll = self.input_cursor;
38        } else if self.input_cursor >= self.input_scroll.saturating_add(visible_width) {
39            self.input_scroll = self
40                .input_cursor
41                .saturating_sub(visible_width.saturating_sub(1));
42        }
43    }
44
45    pub fn move_cursor_left(&mut self) {
46        if self.input_cursor > 0 {
47            self.input_cursor -= 1;
48        }
49    }
50
51    pub fn move_cursor_right(&mut self) {
52        if self.input_cursor < self.input_char_count() {
53            self.input_cursor += 1;
54        }
55    }
56
57    pub fn move_cursor_word_left(&mut self) {
58        self.clamp_input_cursor();
59        while self.input_cursor > 0
60            && self
61                .char_at(self.input_cursor - 1)
62                .is_some_and(char::is_whitespace)
63        {
64            self.input_cursor -= 1;
65        }
66        while self.input_cursor > 0
67            && self
68                .char_at(self.input_cursor - 1)
69                .is_some_and(|c| !c.is_whitespace())
70        {
71            self.input_cursor -= 1;
72        }
73    }
74
75    pub fn move_cursor_word_right(&mut self) {
76        self.clamp_input_cursor();
77        while self.input_cursor < self.input_char_count()
78            && self
79                .char_at(self.input_cursor)
80                .is_some_and(char::is_whitespace)
81        {
82            self.input_cursor += 1;
83        }
84        while self.input_cursor < self.input_char_count()
85            && self
86                .char_at(self.input_cursor)
87                .is_some_and(|c| !c.is_whitespace())
88        {
89            self.input_cursor += 1;
90        }
91    }
92
93    pub fn move_cursor_home(&mut self) {
94        self.input_cursor = 0;
95    }
96
97    pub fn move_cursor_end(&mut self) {
98        self.input_cursor = self.input_char_count();
99    }
100}