Skip to main content

stynx_code_tui/state/
input_state.rs

1pub struct InputState {
2    pub buffer: String,
3    pub cursor_pos: usize,
4    pub history: Vec<String>,
5    pub history_index: Option<usize>,
6    pub suggestion: String,
7
8    pub pasted_buffer: Option<String>,
9
10    pub pasted_images: Vec<std::path::PathBuf>,
11
12    pub slash_matches: Vec<(String, String)>,
13    pub slash_selected: usize,
14}
15
16impl InputState {
17    pub fn new() -> Self {
18        Self {
19            buffer: String::new(),
20            cursor_pos: 0,
21            history: Vec::new(),
22            history_index: None,
23            suggestion: String::new(),
24            pasted_buffer: None,
25            pasted_images: Vec::new(),
26            slash_matches: Vec::new(),
27            slash_selected: 0,
28        }
29    }
30
31    pub fn insert_image_paste(&mut self, path: std::path::PathBuf) -> usize {
32        self.pasted_images.push(path);
33        let idx = self.pasted_images.len();
34        let token = format!("[Image #{idx}]");
35        for c in token.chars() {
36            self.insert_char(c);
37        }
38        idx
39    }
40
41    pub fn expand_for_submit(&self) -> String {
42        let mut out = self.buffer.clone();
43        if let Some(real) = &self.pasted_buffer {
44            if let Some(start) = out.find("[Pasted ") {
45                if let Some(rel_end) = out[start..].find(']') {
46                    let end = start + rel_end + 1;
47                    let mut s = String::with_capacity(out.len() + real.len());
48                    s.push_str(&out[..start]);
49                    s.push_str(real);
50                    s.push_str(&out[end..]);
51                    out = s;
52                }
53            }
54        }
55        for (i, path) in self.pasted_images.iter().enumerate() {
56            let token = format!("[Image #{}]", i + 1);
57            let replacement = format!("@{}", path.display());
58            out = out.replace(&token, &replacement);
59        }
60        out
61    }
62
63    pub fn complete_suggestion(&mut self) {
64        if !self.suggestion.is_empty() {
65            self.buffer.push_str(&self.suggestion);
66            self.cursor_pos = self.buffer.len();
67            self.suggestion.clear();
68        }
69    }
70
71    pub fn insert_char(&mut self, c: char) {
72        if self.cursor_pos >= self.buffer.len() {
73            self.buffer.push(c);
74        } else {
75            self.buffer.insert(self.cursor_pos, c);
76        }
77        self.cursor_pos += c.len_utf8();
78    }
79
80    pub fn delete_char(&mut self) {
81        if self.cursor_pos > 0 {
82            let prev = self.buffer[..self.cursor_pos]
83                .char_indices()
84                .next_back()
85                .map(|(i, _)| i)
86                .unwrap_or(0);
87            self.buffer.drain(prev..self.cursor_pos);
88            self.cursor_pos = prev;
89        }
90    }
91
92    pub fn delete_char_forward(&mut self) {
93        if self.cursor_pos < self.buffer.len() {
94            let next = self.buffer[self.cursor_pos..]
95                .char_indices()
96                .nth(1)
97                .map(|(i, _)| self.cursor_pos + i)
98                .unwrap_or(self.buffer.len());
99            self.buffer.drain(self.cursor_pos..next);
100        }
101    }
102
103    pub fn move_cursor_left(&mut self) {
104        if self.cursor_pos > 0 {
105            self.cursor_pos = self.buffer[..self.cursor_pos]
106                .char_indices()
107                .next_back()
108                .map(|(i, _)| i)
109                .unwrap_or(0);
110        }
111    }
112
113    pub fn move_cursor_right(&mut self) {
114        if self.cursor_pos < self.buffer.len() {
115            self.cursor_pos = self.buffer[self.cursor_pos..]
116                .char_indices()
117                .nth(1)
118                .map(|(i, _)| self.cursor_pos + i)
119                .unwrap_or(self.buffer.len());
120        }
121    }
122
123    pub fn move_word_left(&mut self) {
124        let s = &self.buffer[..self.cursor_pos];
125        let trimmed = s.trim_end_matches(|c: char| !c.is_alphanumeric());
126        let word_end = trimmed.rfind(|c: char| !c.is_alphanumeric()).map(|i| i + 1).unwrap_or(0);
127        self.cursor_pos = word_end;
128    }
129
130    pub fn move_word_right(&mut self) {
131        let s = &self.buffer[self.cursor_pos..];
132        let skip = s.find(|c: char| c.is_alphanumeric()).unwrap_or(s.len());
133        let after = &s[skip..];
134        let word_end = after.find(|c: char| !c.is_alphanumeric()).unwrap_or(after.len());
135        self.cursor_pos = (self.cursor_pos + skip + word_end).min(self.buffer.len());
136    }
137
138    pub fn history_prev(&mut self) {
139        if self.history.is_empty() { return; }
140        let idx = match self.history_index {
141            None => self.history.len() - 1,
142            Some(i) if i > 0 => i - 1,
143            Some(i) => i,
144        };
145        self.history_index = Some(idx);
146        self.buffer = self.history[idx].clone();
147        self.cursor_pos = self.buffer.len();
148    }
149
150    pub fn history_next(&mut self) {
151        match self.history_index {
152            None => {}
153            Some(i) if i + 1 < self.history.len() => {
154                let idx = i + 1;
155                self.history_index = Some(idx);
156                self.buffer = self.history[idx].clone();
157                self.cursor_pos = self.buffer.len();
158            }
159            _ => { self.history_index = None; self.buffer.clear(); self.cursor_pos = 0; }
160        }
161    }
162
163    pub fn push_history(&mut self, text: String) {
164        if !text.is_empty() && self.history.last().map(|s| s.as_str()) != Some(&text) {
165            self.history.push(text);
166        }
167        self.history_index = None;
168    }
169
170    pub fn clear(&mut self) {
171        self.buffer.clear();
172        self.cursor_pos = 0;
173        self.history_index = None;
174        self.pasted_buffer = None;
175        self.pasted_images.clear();
176    }
177
178    pub fn get_display_text(&self) -> &str { &self.buffer }
179
180    pub fn cursor_line_col(&self) -> (usize, usize) {
181        use unicode_width::UnicodeWidthChar;
182        let mut line = 0;
183        let mut last_nl = 0;
184        for (i, b) in self.buffer.as_bytes().iter().enumerate() {
185            if i >= self.cursor_pos { break; }
186            if *b == b'\n' {
187                line += 1;
188                last_nl = i + 1;
189            }
190        }
191        let cursor = self.cursor_pos.min(self.buffer.len());
192        let col: usize = self.buffer[last_nl..cursor]
193            .chars()
194            .map(|c| c.width().unwrap_or(0))
195            .sum();
196        (line, col)
197    }
198
199    pub fn line_count(&self) -> usize {
200        self.buffer.matches('\n').count() + 1
201    }
202
203    pub fn cursor_up_line(&mut self) -> bool {
204        let pos = self.cursor_pos;
205        let before = &self.buffer[..pos];
206        if let Some(nl) = before.rfind('\n') {
207            let line_start = before[..nl].rfind('\n').map(|i| i + 1).unwrap_or(0);
208            let col = pos - (nl + 1);
209            let prev_line_len = nl - line_start;
210            self.cursor_pos = line_start + col.min(prev_line_len);
211            true
212        } else {
213            false
214        }
215    }
216
217    pub fn cursor_down_line(&mut self) -> bool {
218        let pos = self.cursor_pos;
219        let before = &self.buffer[..pos];
220        let line_start = before.rfind('\n').map(|i| i + 1).unwrap_or(0);
221        let col = pos - line_start;
222        let after = &self.buffer[pos..];
223        if let Some(nl) = after.find('\n') {
224            let next_start = pos + nl + 1;
225            let next_line = &self.buffer[next_start..];
226            let next_len = next_line.find('\n').unwrap_or(next_line.len());
227            self.cursor_pos = next_start + col.min(next_len);
228            true
229        } else {
230            false
231        }
232    }
233}
234
235impl Default for InputState {
236    fn default() -> Self { Self::new() }
237}