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 insert_char(&mut self, ch: char) {
17 if ch == '\u{7f}' {
18 return;
19 }
20 if ch == '\n' && !self.can_insert_newline() {
21 return;
22 }
23 self.input_manager.insert_char(ch);
24 self.input_compact_mode = self.input_compact_placeholder().is_some();
25 slash::update_slash_suggestions(self);
26 }
27
28 pub fn insert_paste_text(&mut self, text: &str) {
34 let sanitized: String = text
35 .chars()
36 .filter(|&ch| ch != '\r' && ch != '\u{7f}')
37 .collect();
38
39 if sanitized.is_empty() {
40 return;
41 }
42
43 self.input_manager.insert_text(&sanitized);
44 self.input_compact_mode = self.input_compact_placeholder().is_some();
45 slash::update_slash_suggestions(self);
46 }
47
48 pub(super) fn remaining_newline_capacity(&self) -> usize {
50 ui::INLINE_INPUT_MAX_LINES
51 .saturating_sub(1)
52 .saturating_sub(self.input_manager.content().matches('\n').count())
53 }
54
55 pub(super) fn can_insert_newline(&self) -> bool {
57 self.remaining_newline_capacity() > 0
58 }
59
60 pub(super) fn delete_char(&mut self) {
62 self.input_manager.backspace();
63 self.input_compact_mode = self.input_compact_placeholder().is_some();
64 slash::update_slash_suggestions(self);
65 }
66
67 pub(super) fn delete_char_forward(&mut self) {
69 self.input_manager.delete();
70 self.input_compact_mode = self.input_compact_placeholder().is_some();
71 slash::update_slash_suggestions(self);
72 }
73
74 pub(super) fn delete_word_backward(&mut self) {
76 if self.input_manager.cursor() == 0 {
77 return;
78 }
79
80 let graphemes: Vec<(usize, &str)> = self
82 .input_manager
83 .content()
84 .grapheme_indices(true)
85 .take_while(|(idx, _)| *idx < self.input_manager.cursor())
86 .collect();
87
88 if graphemes.is_empty() {
89 return;
90 }
91
92 let mut index = graphemes.len();
93
94 while index > 0 {
96 let (_, grapheme) = graphemes[index - 1];
97 if !grapheme.chars().all(char::is_whitespace) {
98 break;
99 }
100 index -= 1;
101 }
102
103 while index > 0 {
105 let (_, grapheme) = graphemes[index - 1];
106 if grapheme.chars().all(char::is_whitespace) {
107 break;
108 }
109 index -= 1;
110 }
111
112 let delete_start = if index < graphemes.len() {
114 graphemes[index].0
115 } else {
116 self.input_manager.cursor()
117 };
118
119 if delete_start < self.input_manager.cursor() {
121 let before = &self.input_manager.content()[..delete_start];
122 let after = &self.input_manager.content()[self.input_manager.cursor()..];
123 let new_content = format!("{}{}", before, after);
124
125 self.input_manager.set_content(new_content);
126 self.input_manager.set_cursor(delete_start);
127 self.input_compact_mode = self.input_compact_placeholder().is_some();
128 slash::update_slash_suggestions(self);
129 }
130 }
131
132 #[allow(dead_code)]
133 pub(super) fn delete_word_forward(&mut self) {
134 self.input_manager.delete_word_forward();
135 self.input_compact_mode = self.input_compact_placeholder().is_some();
136 slash::update_slash_suggestions(self);
137 }
138 pub(super) fn delete_to_start_of_line(&mut self) {
140 let content = self.input_manager.content();
141 let cursor = self.input_manager.cursor();
142
143 let before = &content[..cursor];
145 let delete_start = if let Some(newline_pos) = before.rfind('\n') {
146 newline_pos + 1 } else {
148 0 };
150
151 if delete_start < cursor {
152 let new_content = format!("{}{}", &content[..delete_start], &content[cursor..]);
153 self.input_manager.set_content(new_content);
154 self.input_manager.set_cursor(delete_start);
155 self.input_compact_mode = self.input_compact_placeholder().is_some();
156 slash::update_slash_suggestions(self);
157 }
158 }
159
160 pub(super) fn delete_to_end_of_line(&mut self) {
162 let content = self.input_manager.content();
163 let cursor = self.input_manager.cursor();
164
165 let rest = &content[cursor..];
167 let delete_len = if let Some(newline_pos) = rest.find('\n') {
168 newline_pos
169 } else {
170 rest.len()
171 };
172
173 if delete_len > 0 {
174 let new_content = format!("{}{}", &content[..cursor], &content[cursor + delete_len..]);
175 self.input_manager.set_content(new_content);
176 self.input_compact_mode = self.input_compact_placeholder().is_some();
177 slash::update_slash_suggestions(self);
178 }
179 }
180
181 pub(super) fn move_left(&mut self) {
183 self.input_manager.move_cursor_left();
184 slash::update_slash_suggestions(self);
185 }
186
187 pub(super) fn move_right(&mut self) {
189 self.input_manager.move_cursor_right();
190 slash::update_slash_suggestions(self);
191 }
192
193 pub(super) fn move_left_word(&mut self) {
195 if self.input_manager.cursor() == 0 {
196 return;
197 }
198
199 let graphemes: Vec<(usize, &str)> = self
200 .input_manager
201 .content()
202 .grapheme_indices(true)
203 .take_while(|(idx, _)| *idx < self.input_manager.cursor())
204 .collect();
205
206 if graphemes.is_empty() {
207 return;
208 }
209
210 let mut index = graphemes.len();
211
212 while index > 0 {
214 let (_, grapheme) = graphemes[index - 1];
215 if !grapheme.chars().all(char::is_whitespace) {
216 break;
217 }
218 index -= 1;
219 }
220
221 while index > 0 {
223 let (_, grapheme) = graphemes[index - 1];
224 if grapheme.chars().all(char::is_whitespace) {
225 break;
226 }
227 index -= 1;
228 }
229
230 if index < graphemes.len() {
231 self.input_manager.set_cursor(graphemes[index].0);
232 } else {
233 self.input_manager.set_cursor(0);
234 }
235 }
236 pub(super) fn move_right_word(&mut self) {
238 if self.input_manager.cursor() >= self.input_manager.content().len() {
239 return;
240 }
241
242 let graphemes: Vec<(usize, &str)> = self
243 .input_manager
244 .content()
245 .grapheme_indices(true)
246 .skip_while(|(idx, _)| *idx < self.input_manager.cursor())
247 .collect();
248
249 if graphemes.is_empty() {
250 self.input_manager.move_cursor_to_end();
251 return;
252 }
253
254 let mut index = 0;
255 let mut skipped_whitespace = false;
256
257 while index < graphemes.len() {
259 let (_, grapheme) = graphemes[index];
260 if !grapheme.chars().all(char::is_whitespace) {
261 break;
262 }
263 skipped_whitespace = true;
264 index += 1;
265 }
266
267 if index >= graphemes.len() {
268 self.input_manager.move_cursor_to_end();
269 return;
270 }
271
272 if skipped_whitespace {
274 self.input_manager.set_cursor(graphemes[index].0);
275 return;
276 }
277
278 while index < graphemes.len() {
280 let (_, grapheme) = graphemes[index];
281 if grapheme.chars().all(char::is_whitespace) {
282 break;
283 }
284 index += 1;
285 }
286
287 if index < graphemes.len() {
288 self.input_manager.set_cursor(graphemes[index].0);
289 } else {
290 self.input_manager.move_cursor_to_end();
291 }
292 }
293 pub(super) fn move_to_start(&mut self) {
295 self.input_manager.move_cursor_to_start();
296 }
297
298 pub(super) fn move_to_end(&mut self) {
300 self.input_manager.move_cursor_to_end();
301 }
302
303 pub(super) fn remember_submitted_input(
305 &mut self,
306 submitted: super::input_manager::InputHistoryEntry,
307 ) {
308 self.input_manager.add_to_history(submitted);
309 }
310
311 #[allow(dead_code)]
313 pub(super) fn navigate_history_previous(&mut self) -> bool {
314 if let Some(previous) = self.input_manager.go_to_previous_history() {
315 self.input_manager.apply_history_entry(previous);
316 true
317 } else {
318 false
319 }
320 }
321
322 #[allow(dead_code)]
324 pub(super) fn navigate_history_next(&mut self) -> bool {
325 if let Some(next) = self.input_manager.go_to_next_history() {
326 self.input_manager.apply_history_entry(next);
327 true
328 } else {
329 false
330 }
331 }
332
333 pub fn history_position(&self) -> Option<(usize, usize)> {
336 self.input_manager.history_index().map(|idx| {
337 let total = self.input_manager.history().len();
338 (total - idx, total)
339 })
340 }
341}