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