Skip to main content

mermaid_cli/tui/state/
input.rs

1//! Input state management
2//!
3//! User input buffer and cursor handling.
4
5/// Input state - user input buffer and cursor
6///
7/// All cursor positions are byte offsets that are guaranteed to sit on
8/// UTF-8 char boundaries. Methods navigate by whole characters, not
9/// raw bytes, so multi-byte input (emoji, CJK, accented chars) is safe.
10pub struct InputBuffer {
11    /// User input buffer
12    pub content: String,
13    /// Cursor position as a **byte offset** (always on a char boundary)
14    pub cursor_position: usize,
15}
16
17impl InputBuffer {
18    /// Create a new empty input buffer
19    pub fn new() -> Self {
20        Self {
21            content: String::new(),
22            cursor_position: 0,
23        }
24    }
25
26    /// Clear the input buffer
27    pub fn clear(&mut self) {
28        self.content.clear();
29        self.cursor_position = 0;
30    }
31
32    /// Check if input is empty
33    pub fn is_empty(&self) -> bool {
34        self.content.is_empty()
35    }
36
37    /// Get the input content
38    pub fn get(&self) -> &str {
39        &self.content
40    }
41
42    /// Set the input content
43    pub fn set(&mut self, content: impl Into<String>) {
44        self.content = content.into();
45        self.cursor_position = self.content.len();
46    }
47
48    /// Insert a character at cursor position
49    pub fn insert(&mut self, c: char) {
50        self.content.insert(self.cursor_position, c);
51        self.cursor_position += c.len_utf8();
52    }
53
54    /// Insert a string at cursor position
55    pub fn insert_str(&mut self, s: &str) {
56        self.content.insert_str(self.cursor_position, s);
57        self.cursor_position += s.len();
58    }
59
60    /// Delete character before cursor (backspace)
61    pub fn backspace(&mut self) -> bool {
62        if self.cursor_position > 0 {
63            // Find the start of the previous character
64            let prev_boundary = self.content[..self.cursor_position]
65                .char_indices()
66                .next_back()
67                .map(|(idx, _)| idx)
68                .unwrap_or(0);
69            self.content.remove(prev_boundary);
70            self.cursor_position = prev_boundary;
71            true
72        } else {
73            false
74        }
75    }
76
77    /// Delete character at cursor (delete key)
78    pub fn delete(&mut self) -> bool {
79        if self.cursor_position < self.content.len() {
80            self.content.remove(self.cursor_position);
81            true
82        } else {
83            false
84        }
85    }
86
87    /// Move cursor left by one character
88    pub fn move_left(&mut self) {
89        if self.cursor_position > 0 {
90            // Find the previous char boundary
91            self.cursor_position = self.content[..self.cursor_position]
92                .char_indices()
93                .next_back()
94                .map(|(idx, _)| idx)
95                .unwrap_or(0);
96        }
97    }
98
99    /// Move cursor right by one character
100    pub fn move_right(&mut self) {
101        if self.cursor_position < self.content.len() {
102            // Find the next char boundary
103            self.cursor_position = self.content[self.cursor_position..]
104                .char_indices()
105                .nth(1)
106                .map(|(idx, _)| self.cursor_position + idx)
107                .unwrap_or(self.content.len());
108        }
109    }
110
111    /// Move cursor to start
112    pub fn move_home(&mut self) {
113        self.cursor_position = 0;
114    }
115
116    /// Move cursor to end
117    pub fn move_end(&mut self) {
118        self.cursor_position = self.content.len();
119    }
120}
121
122impl Default for InputBuffer {
123    fn default() -> Self {
124        Self::new()
125    }
126}