vtcode_tui/core_tui/session/
editing.rs1use super::Session;
2use crate::config::constants::ui;
3use unicode_segmentation::UnicodeSegmentation;
12
13impl Session {
14 pub(crate) fn refresh_input_edit_state(&mut self) {
15 self.clear_suggested_prompt_state();
16 self.input_compact_mode = self.input_compact_placeholder().is_some();
17 }
18
19 pub(crate) fn insert_char(&mut self, ch: char) {
21 if ch == '\u{7f}' {
22 return;
23 }
24 if ch == '\n' && !self.can_insert_newline() {
25 return;
26 }
27 self.input_manager.insert_char(ch);
28 self.refresh_input_edit_state();
29 }
30
31 pub fn insert_paste_text(&mut self, text: &str) {
37 let sanitized: String = text
38 .chars()
39 .filter(|&ch| ch != '\r' && ch != '\u{7f}')
40 .collect();
41
42 if sanitized.is_empty() {
43 return;
44 }
45
46 self.input_manager.insert_text(&sanitized);
47 self.refresh_input_edit_state();
48 }
49
50 pub(crate) fn apply_suggested_prompt(&mut self, text: String) {
51 let trimmed = text.trim();
52 if trimmed.is_empty() {
53 return;
54 }
55
56 let merged = if self.input_manager.content().trim().is_empty() {
57 trimmed.to_string()
58 } else {
59 format!("{}\n\n{}", self.input_manager.content().trim_end(), trimmed)
60 };
61
62 self.input_manager.set_content(merged);
63 self.input_manager
64 .set_cursor(self.input_manager.content().len());
65 self.suggested_prompt_state.active = true;
66 self.input_compact_mode = self.input_compact_placeholder().is_some();
67 self.mark_dirty();
68 }
69
70 pub(crate) fn remaining_newline_capacity(&self) -> usize {
72 ui::INLINE_INPUT_MAX_LINES
73 .saturating_sub(1)
74 .saturating_sub(self.input_manager.content().matches('\n').count())
75 }
76
77 pub(crate) fn can_insert_newline(&self) -> bool {
79 self.remaining_newline_capacity() > 0
80 }
81
82 pub(crate) fn delete_char(&mut self) {
84 self.input_manager.backspace();
85 self.refresh_input_edit_state();
86 }
87
88 pub(crate) fn delete_char_forward(&mut self) {
90 self.input_manager.delete();
91 self.refresh_input_edit_state();
92 }
93
94 pub(crate) fn delete_word_backward(&mut self) {
96 if self.input_manager.delete_selection() {
97 self.refresh_input_edit_state();
98 return;
99 }
100 if self.input_manager.cursor() == 0 {
101 return;
102 }
103
104 let graphemes: Vec<(usize, &str)> = self
106 .input_manager
107 .content()
108 .grapheme_indices(true)
109 .take_while(|(idx, _)| *idx < self.input_manager.cursor())
110 .collect();
111
112 if graphemes.is_empty() {
113 return;
114 }
115
116 let mut index = graphemes.len();
117
118 while index > 0 {
120 let (_, grapheme) = graphemes[index - 1];
121 if !grapheme.chars().all(char::is_whitespace) {
122 break;
123 }
124 index -= 1;
125 }
126
127 while index > 0 {
129 let (_, grapheme) = graphemes[index - 1];
130 if grapheme.chars().all(char::is_whitespace) {
131 break;
132 }
133 index -= 1;
134 }
135
136 let delete_start = if index < graphemes.len() {
138 graphemes[index].0
139 } else {
140 self.input_manager.cursor()
141 };
142
143 if delete_start < self.input_manager.cursor() {
145 let before = &self.input_manager.content()[..delete_start];
146 let after = &self.input_manager.content()[self.input_manager.cursor()..];
147 let new_content = format!("{}{}", before, after);
148
149 self.input_manager.set_content(new_content);
150 self.input_manager.set_cursor(delete_start);
151 self.refresh_input_edit_state();
152 }
153 }
154
155 #[allow(dead_code)]
156 pub(crate) fn delete_word_forward(&mut self) {
157 self.input_manager.delete_word_forward();
158 self.refresh_input_edit_state();
159 }
160 pub(crate) fn delete_to_start_of_line(&mut self) {
162 if self.input_manager.delete_selection() {
163 self.refresh_input_edit_state();
164 return;
165 }
166 let content = self.input_manager.content();
167 let cursor = self.input_manager.cursor();
168
169 let before = &content[..cursor];
171 let delete_start = if let Some(newline_pos) = before.rfind('\n') {
172 newline_pos + 1 } else {
174 0 };
176
177 if delete_start < cursor {
178 let new_content = format!("{}{}", &content[..delete_start], &content[cursor..]);
179 self.input_manager.set_content(new_content);
180 self.input_manager.set_cursor(delete_start);
181 self.refresh_input_edit_state();
182 }
183 }
184
185 pub(crate) fn delete_to_end_of_line(&mut self) {
187 if self.input_manager.delete_selection() {
188 self.refresh_input_edit_state();
189 return;
190 }
191 let content = self.input_manager.content();
192 let cursor = self.input_manager.cursor();
193
194 let rest = &content[cursor..];
196 let delete_len = if let Some(newline_pos) = rest.find('\n') {
197 newline_pos
198 } else {
199 rest.len()
200 };
201
202 if delete_len > 0 {
203 let new_content = format!("{}{}", &content[..cursor], &content[cursor + delete_len..]);
204 self.input_manager.set_content(new_content);
205 self.refresh_input_edit_state();
206 }
207 }
208
209 pub(crate) fn move_left(&mut self) {
211 self.input_manager.move_cursor_left();
212 }
213
214 pub(crate) fn move_right(&mut self) {
216 self.input_manager.move_cursor_right();
217 }
218
219 pub(crate) fn select_left(&mut self) {
220 let cursor = self.input_manager.cursor();
221 if cursor == 0 {
222 self.input_manager.set_cursor_with_selection(0);
223 return;
224 }
225
226 let mut pos = cursor - 1;
227 let content = self.input_manager.content();
228 while pos > 0 && !content.is_char_boundary(pos) {
229 pos -= 1;
230 }
231 self.input_manager.set_cursor_with_selection(pos);
232 }
233
234 pub(crate) fn select_right(&mut self) {
235 let cursor = self.input_manager.cursor();
236 let content = self.input_manager.content();
237 if cursor >= content.len() {
238 self.input_manager.set_cursor_with_selection(content.len());
239 return;
240 }
241
242 let mut pos = cursor + 1;
243 while pos < content.len() && !content.is_char_boundary(pos) {
244 pos += 1;
245 }
246 self.input_manager.set_cursor_with_selection(pos);
247 }
248
249 pub(crate) fn move_left_word(&mut self) {
251 if self.input_manager.cursor() == 0 {
252 return;
253 }
254
255 let graphemes: Vec<(usize, &str)> = self
256 .input_manager
257 .content()
258 .grapheme_indices(true)
259 .take_while(|(idx, _)| *idx < self.input_manager.cursor())
260 .collect();
261
262 if graphemes.is_empty() {
263 return;
264 }
265
266 let mut index = graphemes.len();
267
268 while index > 0 {
270 let (_, grapheme) = graphemes[index - 1];
271 if !grapheme.chars().all(char::is_whitespace) {
272 break;
273 }
274 index -= 1;
275 }
276
277 while index > 0 {
279 let (_, grapheme) = graphemes[index - 1];
280 if grapheme.chars().all(char::is_whitespace) {
281 break;
282 }
283 index -= 1;
284 }
285
286 if index < graphemes.len() {
287 self.input_manager.set_cursor(graphemes[index].0);
288 } else {
289 self.input_manager.set_cursor(0);
290 }
291 }
292 pub(crate) fn move_right_word(&mut self) {
294 if self.input_manager.cursor() >= self.input_manager.content().len() {
295 return;
296 }
297
298 let graphemes: Vec<(usize, &str)> = self
299 .input_manager
300 .content()
301 .grapheme_indices(true)
302 .skip_while(|(idx, _)| *idx < self.input_manager.cursor())
303 .collect();
304
305 if graphemes.is_empty() {
306 self.input_manager.move_cursor_to_end();
307 return;
308 }
309
310 let mut index = 0;
311 let mut skipped_whitespace = false;
312
313 while index < graphemes.len() {
315 let (_, grapheme) = graphemes[index];
316 if !grapheme.chars().all(char::is_whitespace) {
317 break;
318 }
319 skipped_whitespace = true;
320 index += 1;
321 }
322
323 if index >= graphemes.len() {
324 self.input_manager.move_cursor_to_end();
325 return;
326 }
327
328 if skipped_whitespace {
330 self.input_manager.set_cursor(graphemes[index].0);
331 return;
332 }
333
334 while index < graphemes.len() {
336 let (_, grapheme) = graphemes[index];
337 if grapheme.chars().all(char::is_whitespace) {
338 break;
339 }
340 index += 1;
341 }
342
343 if index < graphemes.len() {
344 self.input_manager.set_cursor(graphemes[index].0);
345 } else {
346 self.input_manager.move_cursor_to_end();
347 }
348 }
349 pub(crate) fn move_to_start(&mut self) {
351 self.input_manager.move_cursor_to_start();
352 }
353
354 pub(crate) fn move_to_end(&mut self) {
356 self.input_manager.move_cursor_to_end();
357 }
358
359 pub(crate) fn select_to_start(&mut self) {
360 self.input_manager.set_cursor_with_selection(0);
361 }
362
363 pub(crate) fn select_to_end(&mut self) {
364 self.input_manager
365 .set_cursor_with_selection(self.input_manager.content().len());
366 }
367
368 pub(crate) fn remember_submitted_input(
370 &mut self,
371 submitted: super::input_manager::InputHistoryEntry,
372 ) {
373 self.input_manager.add_to_history(submitted);
374 }
375
376 #[allow(dead_code)]
378 pub(crate) fn navigate_history_previous(&mut self) -> bool {
379 if let Some(previous) = self.input_manager.go_to_previous_history() {
380 self.input_manager.apply_history_entry(previous);
381 true
382 } else {
383 false
384 }
385 }
386
387 #[allow(dead_code)]
389 pub(crate) fn navigate_history_next(&mut self) -> bool {
390 if let Some(next) = self.input_manager.go_to_next_history() {
391 self.input_manager.apply_history_entry(next);
392 true
393 } else {
394 false
395 }
396 }
397
398 pub fn history_position(&self) -> Option<(usize, usize)> {
401 self.input_manager.history_index().map(|idx| {
402 let total = self.input_manager.history().len();
403 (total - idx, total)
404 })
405 }
406}