vtcode_tui/core_tui/session/
editing.rs1use super::{InlinePromptSuggestionSource, 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.clear_inline_prompt_suggestion();
17 self.input_compact_mode = self.input_compact_placeholder().is_some();
18 }
19
20 pub(crate) fn set_inline_prompt_suggestion(&mut self, suggestion: String, llm_generated: bool) {
21 let trimmed = suggestion.trim();
22 if trimmed.is_empty() {
23 self.clear_inline_prompt_suggestion();
24 return;
25 }
26
27 self.inline_prompt_suggestion.suggestion = Some(trimmed.to_string());
28 self.inline_prompt_suggestion.source = Some(if llm_generated {
29 InlinePromptSuggestionSource::Llm
30 } else {
31 InlinePromptSuggestionSource::Local
32 });
33 self.mark_dirty();
34 }
35
36 pub(crate) fn accept_inline_prompt_suggestion(&mut self) -> bool {
37 let Some(suffix) = self.visible_inline_prompt_suggestion_suffix() else {
38 return false;
39 };
40
41 self.input_manager.insert_text(&suffix);
42 self.clear_inline_prompt_suggestion();
43 self.mark_dirty();
44 true
45 }
46
47 pub(crate) fn insert_char(&mut self, ch: char) {
49 if ch == '\u{7f}' {
50 return;
51 }
52 if ch == '\n' && !self.can_insert_newline() {
53 return;
54 }
55 self.input_manager.insert_char(ch);
56 self.refresh_input_edit_state();
57 }
58
59 pub fn insert_paste_text(&mut self, text: &str) {
65 let sanitized: String = text
66 .chars()
67 .filter(|&ch| ch != '\r' && ch != '\u{7f}')
68 .collect();
69
70 if sanitized.is_empty() {
71 return;
72 }
73
74 self.input_manager.insert_text(&sanitized);
75 self.refresh_input_edit_state();
76 }
77
78 pub(crate) fn apply_suggested_prompt(&mut self, text: String) {
79 let trimmed = text.trim();
80 if trimmed.is_empty() {
81 return;
82 }
83
84 let merged = if self.input_manager.content().trim().is_empty() {
85 trimmed.to_string()
86 } else {
87 format!("{}\n\n{}", self.input_manager.content().trim_end(), trimmed)
88 };
89
90 self.input_manager.set_content(merged);
91 self.input_manager
92 .set_cursor(self.input_manager.content().len());
93 self.suggested_prompt_state.active = true;
94 self.input_compact_mode = self.input_compact_placeholder().is_some();
95 self.mark_dirty();
96 }
97
98 pub(crate) fn remaining_newline_capacity(&self) -> usize {
100 ui::INLINE_INPUT_MAX_LINES
101 .saturating_sub(1)
102 .saturating_sub(self.input_manager.content().matches('\n').count())
103 }
104
105 pub(crate) fn can_insert_newline(&self) -> bool {
107 self.remaining_newline_capacity() > 0
108 }
109
110 pub(crate) fn delete_char(&mut self) {
112 self.input_manager.backspace();
113 self.refresh_input_edit_state();
114 }
115
116 pub(crate) fn delete_char_forward(&mut self) {
118 self.input_manager.delete();
119 self.refresh_input_edit_state();
120 }
121
122 pub(crate) fn delete_word_backward(&mut self) {
124 if self.input_manager.delete_selection() {
125 self.refresh_input_edit_state();
126 return;
127 }
128 if self.input_manager.cursor() == 0 {
129 return;
130 }
131
132 let graphemes: Vec<(usize, &str)> = self
134 .input_manager
135 .content()
136 .grapheme_indices(true)
137 .take_while(|(idx, _)| *idx < self.input_manager.cursor())
138 .collect();
139
140 if graphemes.is_empty() {
141 return;
142 }
143
144 let mut index = graphemes.len();
145
146 while index > 0 {
148 let (_, grapheme) = graphemes[index - 1];
149 if !grapheme.chars().all(char::is_whitespace) {
150 break;
151 }
152 index -= 1;
153 }
154
155 while index > 0 {
157 let (_, grapheme) = graphemes[index - 1];
158 if grapheme.chars().all(char::is_whitespace) {
159 break;
160 }
161 index -= 1;
162 }
163
164 let delete_start = if index < graphemes.len() {
166 graphemes[index].0
167 } else {
168 self.input_manager.cursor()
169 };
170
171 if delete_start < self.input_manager.cursor() {
173 let before = &self.input_manager.content()[..delete_start];
174 let after = &self.input_manager.content()[self.input_manager.cursor()..];
175 let new_content = format!("{}{}", before, after);
176
177 self.input_manager.set_content(new_content);
178 self.input_manager.set_cursor(delete_start);
179 self.refresh_input_edit_state();
180 }
181 }
182
183 #[allow(dead_code)]
184 pub(crate) fn delete_word_forward(&mut self) {
185 self.input_manager.delete_word_forward();
186 self.refresh_input_edit_state();
187 }
188 pub(crate) fn delete_to_start_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 before = &content[..cursor];
199 let delete_start = if let Some(newline_pos) = before.rfind('\n') {
200 newline_pos + 1 } else {
202 0 };
204
205 if delete_start < cursor {
206 let new_content = format!("{}{}", &content[..delete_start], &content[cursor..]);
207 self.input_manager.set_content(new_content);
208 self.input_manager.set_cursor(delete_start);
209 self.refresh_input_edit_state();
210 }
211 }
212
213 pub(crate) fn delete_to_end_of_line(&mut self) {
215 if self.input_manager.delete_selection() {
216 self.refresh_input_edit_state();
217 return;
218 }
219 let content = self.input_manager.content();
220 let cursor = self.input_manager.cursor();
221
222 let rest = &content[cursor..];
224 let delete_len = if let Some(newline_pos) = rest.find('\n') {
225 newline_pos
226 } else {
227 rest.len()
228 };
229
230 if delete_len > 0 {
231 let new_content = format!("{}{}", &content[..cursor], &content[cursor + delete_len..]);
232 self.input_manager.set_content(new_content);
233 self.refresh_input_edit_state();
234 }
235 }
236
237 pub(crate) fn move_left(&mut self) {
239 self.input_manager.move_cursor_left();
240 }
241
242 pub(crate) fn move_right(&mut self) {
244 self.input_manager.move_cursor_right();
245 }
246
247 pub(crate) fn select_left(&mut self) {
248 let cursor = self.input_manager.cursor();
249 if cursor == 0 {
250 self.input_manager.set_cursor_with_selection(0);
251 return;
252 }
253
254 let mut pos = cursor - 1;
255 let content = self.input_manager.content();
256 while pos > 0 && !content.is_char_boundary(pos) {
257 pos -= 1;
258 }
259 self.input_manager.set_cursor_with_selection(pos);
260 }
261
262 pub(crate) fn select_right(&mut self) {
263 let cursor = self.input_manager.cursor();
264 let content = self.input_manager.content();
265 if cursor >= content.len() {
266 self.input_manager.set_cursor_with_selection(content.len());
267 return;
268 }
269
270 let mut pos = cursor + 1;
271 while pos < content.len() && !content.is_char_boundary(pos) {
272 pos += 1;
273 }
274 self.input_manager.set_cursor_with_selection(pos);
275 }
276
277 pub(crate) fn move_left_word(&mut self) {
279 if self.input_manager.cursor() == 0 {
280 return;
281 }
282
283 let graphemes: Vec<(usize, &str)> = self
284 .input_manager
285 .content()
286 .grapheme_indices(true)
287 .take_while(|(idx, _)| *idx < self.input_manager.cursor())
288 .collect();
289
290 if graphemes.is_empty() {
291 return;
292 }
293
294 let mut index = graphemes.len();
295
296 while index > 0 {
298 let (_, grapheme) = graphemes[index - 1];
299 if !grapheme.chars().all(char::is_whitespace) {
300 break;
301 }
302 index -= 1;
303 }
304
305 while index > 0 {
307 let (_, grapheme) = graphemes[index - 1];
308 if grapheme.chars().all(char::is_whitespace) {
309 break;
310 }
311 index -= 1;
312 }
313
314 if index < graphemes.len() {
315 self.input_manager.set_cursor(graphemes[index].0);
316 } else {
317 self.input_manager.set_cursor(0);
318 }
319 }
320 pub(crate) fn move_right_word(&mut self) {
322 if self.input_manager.cursor() >= self.input_manager.content().len() {
323 return;
324 }
325
326 let graphemes: Vec<(usize, &str)> = self
327 .input_manager
328 .content()
329 .grapheme_indices(true)
330 .skip_while(|(idx, _)| *idx < self.input_manager.cursor())
331 .collect();
332
333 if graphemes.is_empty() {
334 self.input_manager.move_cursor_to_end();
335 return;
336 }
337
338 let mut index = 0;
339 let mut skipped_whitespace = false;
340
341 while index < graphemes.len() {
343 let (_, grapheme) = graphemes[index];
344 if !grapheme.chars().all(char::is_whitespace) {
345 break;
346 }
347 skipped_whitespace = true;
348 index += 1;
349 }
350
351 if index >= graphemes.len() {
352 self.input_manager.move_cursor_to_end();
353 return;
354 }
355
356 if skipped_whitespace {
358 self.input_manager.set_cursor(graphemes[index].0);
359 return;
360 }
361
362 while index < graphemes.len() {
364 let (_, grapheme) = graphemes[index];
365 if grapheme.chars().all(char::is_whitespace) {
366 break;
367 }
368 index += 1;
369 }
370
371 if index < graphemes.len() {
372 self.input_manager.set_cursor(graphemes[index].0);
373 } else {
374 self.input_manager.move_cursor_to_end();
375 }
376 }
377 pub(crate) fn move_to_start(&mut self) {
379 self.input_manager.move_cursor_to_start();
380 }
381
382 pub(crate) fn move_to_end(&mut self) {
384 self.input_manager.move_cursor_to_end();
385 }
386
387 pub(crate) fn select_to_start(&mut self) {
388 self.input_manager.set_cursor_with_selection(0);
389 }
390
391 pub(crate) fn select_to_end(&mut self) {
392 self.input_manager
393 .set_cursor_with_selection(self.input_manager.content().len());
394 }
395
396 pub(crate) fn remember_submitted_input(
398 &mut self,
399 submitted: super::input_manager::InputHistoryEntry,
400 ) {
401 self.input_manager.add_to_history(submitted);
402 }
403
404 #[allow(dead_code)]
406 pub(crate) fn navigate_history_previous(&mut self) -> bool {
407 if let Some(previous) = self.input_manager.go_to_previous_history() {
408 self.input_manager.apply_history_entry(previous);
409 true
410 } else {
411 false
412 }
413 }
414
415 #[allow(dead_code)]
417 pub(crate) fn navigate_history_next(&mut self) -> bool {
418 if let Some(next) = self.input_manager.go_to_next_history() {
419 self.input_manager.apply_history_entry(next);
420 true
421 } else {
422 false
423 }
424 }
425
426 pub fn history_position(&self) -> Option<(usize, usize)> {
429 self.input_manager.history_index().map(|idx| {
430 let total = self.input_manager.history().len();
431 (total - idx, total)
432 })
433 }
434}