1use crate::{Action, EditResult, Key, KeyCode, LineEditor, TextEdit};
8use std::ops::Range;
9
10mod edits;
11mod motions;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
15pub(crate) enum Mode {
16 #[default]
17 Normal,
18 Insert,
19 OperatorPending(Operator),
20 Visual,
21 ReplaceChar,
23}
24
25#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub(crate) enum Operator {
28 Delete,
29 Change,
30 Yank,
31}
32
33#[derive(Debug, Clone)]
38pub struct VimLineEditor {
39 pub(in crate::vim) cursor: usize,
40 pub(in crate::vim) mode: Mode,
41 pub(in crate::vim) visual_anchor: Option<usize>,
43 pub(in crate::vim) yank_buffer: String,
45}
46
47impl Default for VimLineEditor {
48 fn default() -> Self {
49 Self::new()
50 }
51}
52
53impl VimLineEditor {
54 pub fn new() -> Self {
56 Self {
57 cursor: 0,
58 mode: Mode::Normal,
59 visual_anchor: None,
60 yank_buffer: String::new(),
61 }
62 }
63
64 #[cfg(test)]
66 fn mode(&self) -> Mode {
67 self.mode
68 }
69
70 fn clamp_cursor(&mut self, text: &str) {
72 self.cursor = self.cursor.min(text.len());
73 }
74
75 fn move_left(&mut self, text: &str) {
77 self.cursor = motions::move_left(self.cursor, text);
78 }
79
80 fn move_right(&mut self, text: &str) {
82 self.cursor = motions::move_right(self.cursor, text);
83 }
84
85 fn move_line_start(&mut self, text: &str) {
87 self.cursor = motions::move_line_start(self.cursor, text);
88 }
89
90 fn move_first_non_blank(&mut self, text: &str) {
92 self.cursor = motions::move_first_non_blank(self.cursor, text);
93 }
94
95 fn move_line_end(&mut self, text: &str) {
97 self.cursor = motions::move_line_end(self.cursor, text);
98 }
99
100 fn move_line_end_insert(&mut self, text: &str) {
102 self.cursor = motions::move_line_end_insert(self.cursor, text);
103 }
104
105 fn move_word_forward(&mut self, text: &str) {
107 self.cursor = motions::move_word_forward(self.cursor, text);
108 }
109
110 fn move_word_backward(&mut self, text: &str) {
112 self.cursor = motions::move_word_backward(self.cursor, text);
113 }
114
115 fn move_word_end(&mut self, text: &str) {
117 self.cursor = motions::move_word_end(self.cursor, text);
118 }
119
120 fn move_up(&mut self, text: &str) {
122 self.cursor = motions::move_up(self.cursor, text);
123 }
124
125 fn move_down(&mut self, text: &str) {
127 self.cursor = motions::move_down(self.cursor, text);
128 }
129
130 fn move_to_matching_bracket(&mut self, text: &str) {
133 self.cursor = motions::move_to_matching_bracket(self.cursor, text);
134 }
135
136 fn dispatch_motion(&mut self, code: KeyCode, text: &str) -> bool {
148 match code {
149 KeyCode::Char('h') | KeyCode::Left => self.move_left(text),
150 KeyCode::Char('l') | KeyCode::Right => self.move_right(text),
151 KeyCode::Char('j') => self.move_down(text),
152 KeyCode::Char('k') => self.move_up(text),
153 KeyCode::Char('0') | KeyCode::Home => self.move_line_start(text),
154 KeyCode::Char('^') => self.move_first_non_blank(text),
155 KeyCode::Char('$') | KeyCode::End => self.move_line_end(text),
156 KeyCode::Char('w') => self.move_word_forward(text),
157 KeyCode::Char('b') => self.move_word_backward(text),
158 KeyCode::Char('e') => self.move_word_end(text),
159 KeyCode::Char('%') => self.move_to_matching_bracket(text),
160 _ => return false,
161 }
162 true
163 }
164
165 fn handle_normal(&mut self, key: Key, text: &str) -> EditResult {
167 if self.dispatch_motion(key.code, text) {
170 return EditResult::cursor_only();
171 }
172
173 match key.code {
174 KeyCode::Char('i') => {
176 self.mode = Mode::Insert;
177 EditResult::none()
178 }
179 KeyCode::Char('a') => {
180 self.mode = Mode::Insert;
181 self.move_right(text);
182 EditResult::none()
183 }
184 KeyCode::Char('A') => {
185 self.mode = Mode::Insert;
186 self.move_line_end_insert(text);
187 EditResult::none()
188 }
189 KeyCode::Char('I') => {
190 self.mode = Mode::Insert;
191 self.move_first_non_blank(text);
192 EditResult::none()
193 }
194 KeyCode::Char('o') => {
195 self.mode = Mode::Insert;
196 self.move_line_end(text);
197 let pos = self.cursor;
198 self.cursor = pos + 1;
199 EditResult::edit(TextEdit::Insert {
200 at: pos,
201 text: "\n".to_string(),
202 })
203 }
204 KeyCode::Char('O') => {
205 self.mode = Mode::Insert;
206 self.move_line_start(text);
207 let pos = self.cursor;
208 EditResult::edit(TextEdit::Insert {
209 at: pos,
210 text: "\n".to_string(),
211 })
212 }
213
214 KeyCode::Char('v') => {
216 self.mode = Mode::Visual;
217 self.visual_anchor = Some(self.cursor);
218 EditResult::none()
219 }
220
221 KeyCode::Char('c') if key.ctrl => EditResult::action(Action::Cancel),
223
224 KeyCode::Char('d') => {
226 self.mode = Mode::OperatorPending(Operator::Delete);
227 EditResult::none()
228 }
229 KeyCode::Char('c') => {
230 self.mode = Mode::OperatorPending(Operator::Change);
231 EditResult::none()
232 }
233 KeyCode::Char('y') => {
234 self.mode = Mode::OperatorPending(Operator::Yank);
235 EditResult::none()
236 }
237
238 KeyCode::Char('x') => self.delete_char(text),
240 KeyCode::Char('D') => self.delete_to_end(text),
241 KeyCode::Char('C') => {
242 self.mode = Mode::Insert;
243 self.delete_to_end(text)
244 }
245
246 KeyCode::Char('r') => {
248 self.mode = Mode::ReplaceChar;
249 EditResult::none()
250 }
251
252 KeyCode::Char('p') => self.paste_after(text),
254 KeyCode::Char('P') => self.paste_before(text),
255
256 KeyCode::Up => EditResult::action(Action::HistoryPrev),
258 KeyCode::Down => EditResult::action(Action::HistoryNext),
259
260 KeyCode::Enter if !key.shift => EditResult::action(Action::Submit),
262
263 KeyCode::Enter if key.shift => {
265 self.mode = Mode::Insert;
266 let pos = self.cursor;
267 self.cursor = pos + 1;
268 EditResult::edit(TextEdit::Insert {
269 at: pos,
270 text: "\n".to_string(),
271 })
272 }
273
274 KeyCode::Escape => EditResult::none(),
277
278 _ => EditResult::none(),
279 }
280 }
281
282 fn handle_insert(&mut self, key: Key, text: &str) -> EditResult {
284 match key.code {
285 KeyCode::Escape => {
286 self.mode = Mode::Normal;
287 if self.cursor > 0 {
289 self.move_left(text);
290 }
291 EditResult::none()
292 }
293
294 KeyCode::Char('c') if key.ctrl => {
296 self.mode = Mode::Normal;
297 EditResult::none()
298 }
299
300 KeyCode::Char(c) if !key.ctrl && !key.alt => {
301 let pos = self.cursor;
302 self.cursor = pos + c.len_utf8();
303 EditResult::edit(TextEdit::Insert {
304 at: pos,
305 text: c.to_string(),
306 })
307 }
308
309 KeyCode::Backspace => {
310 if self.cursor == 0 {
311 return EditResult::none();
312 }
313 let mut start = self.cursor - 1;
314 while start > 0 && !text.is_char_boundary(start) {
315 start -= 1;
316 }
317 let end = self.cursor; self.cursor = start;
319 EditResult::edit(TextEdit::Delete { start, end })
320 }
321
322 KeyCode::Delete => self.delete_char(text),
323
324 KeyCode::Left => {
325 self.move_left(text);
326 EditResult::cursor_only()
327 }
328 KeyCode::Right => {
329 self.move_right(text);
330 EditResult::cursor_only()
331 }
332 KeyCode::Up => {
333 self.move_up(text);
334 EditResult::cursor_only()
335 }
336 KeyCode::Down => {
337 self.move_down(text);
338 EditResult::cursor_only()
339 }
340 KeyCode::Home => {
341 self.move_line_start(text);
342 EditResult::cursor_only()
343 }
344 KeyCode::End => {
345 self.move_line_end_insert(text);
347 EditResult::cursor_only()
348 }
349
350 KeyCode::Enter => {
352 let pos = self.cursor;
353 self.cursor = pos + 1;
354 EditResult::edit(TextEdit::Insert {
355 at: pos,
356 text: "\n".to_string(),
357 })
358 }
359
360 _ => EditResult::none(),
361 }
362 }
363
364 fn handle_operator_pending(&mut self, op: Operator, key: Key, text: &str) -> EditResult {
366 if key.code == KeyCode::Escape {
368 self.mode = Mode::Normal;
369 return EditResult::none();
370 }
371
372 let is_line_op = matches!(
374 (op, key.code),
375 (Operator::Delete, KeyCode::Char('d'))
376 | (Operator::Change, KeyCode::Char('c'))
377 | (Operator::Yank, KeyCode::Char('y'))
378 );
379
380 if is_line_op {
381 self.mode = Mode::Normal;
382 return self.apply_operator_line(op, text);
383 }
384
385 let start = self.cursor;
387 match key.code {
388 KeyCode::Char('w') => {
389 if op == Operator::Change {
392 self.move_word_end(text);
393 if self.cursor < text.len() {
395 self.cursor += 1;
396 }
397 } else {
398 self.move_word_forward(text);
399 }
400 }
401 KeyCode::Char('b') => self.move_word_backward(text),
402 KeyCode::Char('e') => {
403 self.move_word_end(text);
404 if self.cursor < text.len() {
406 self.cursor += 1;
407 }
408 }
409 KeyCode::Char('0') | KeyCode::Home => self.move_line_start(text),
410 KeyCode::Char('$') | KeyCode::End => self.move_line_end(text),
411 KeyCode::Char('^') => self.move_first_non_blank(text),
412 KeyCode::Char('h') | KeyCode::Left => self.move_left(text),
413 KeyCode::Char('l') | KeyCode::Right => self.move_right(text),
414 KeyCode::Char('j') => self.move_down(text),
415 KeyCode::Char('k') => self.move_up(text),
416 _ => {
417 self.mode = Mode::Normal;
419 return EditResult::none();
420 }
421 }
422
423 let end = self.cursor;
424 self.mode = Mode::Normal;
425
426 if start == end {
427 return EditResult::none();
428 }
429
430 let (range_start, range_end) = if start < end {
431 (start, end)
432 } else {
433 (end, start)
434 };
435
436 self.apply_operator(op, range_start, range_end, text)
437 }
438
439 fn handle_visual(&mut self, key: Key, text: &str) -> EditResult {
441 match key.code {
442 KeyCode::Escape => {
443 self.mode = Mode::Normal;
444 self.visual_anchor = None;
445 EditResult::none()
446 }
447
448 KeyCode::Char('h') | KeyCode::Left => {
452 self.move_left(text);
453 EditResult::cursor_only()
454 }
455 KeyCode::Char('l') | KeyCode::Right => {
456 self.move_right(text);
457 EditResult::cursor_only()
458 }
459 KeyCode::Char('j') => {
460 self.move_down(text);
461 EditResult::cursor_only()
462 }
463 KeyCode::Char('k') => {
464 self.move_up(text);
465 EditResult::cursor_only()
466 }
467 KeyCode::Char('w') => {
468 self.move_word_forward(text);
469 EditResult::cursor_only()
470 }
471 KeyCode::Char('b') => {
472 self.move_word_backward(text);
473 EditResult::cursor_only()
474 }
475 KeyCode::Char('e') => {
476 self.move_word_end(text);
477 EditResult::cursor_only()
478 }
479 KeyCode::Char('0') | KeyCode::Home => {
480 self.move_line_start(text);
481 EditResult::cursor_only()
482 }
483 KeyCode::Char('$') | KeyCode::End => {
484 self.move_line_end(text);
485 EditResult::cursor_only()
486 }
487
488 KeyCode::Char('d') | KeyCode::Char('x') => {
490 let (start, end) = self.selection_range();
491 self.mode = Mode::Normal;
492 self.visual_anchor = None;
493 self.apply_operator(Operator::Delete, start, end, text)
494 }
495 KeyCode::Char('c') => {
496 let (start, end) = self.selection_range();
497 self.mode = Mode::Normal;
498 self.visual_anchor = None;
499 self.apply_operator(Operator::Change, start, end, text)
500 }
501 KeyCode::Char('y') => {
502 let (start, end) = self.selection_range();
503 self.mode = Mode::Normal;
504 self.visual_anchor = None;
505 self.apply_operator(Operator::Yank, start, end, text)
506 }
507
508 _ => EditResult::none(),
509 }
510 }
511
512 fn handle_replace_char(&mut self, key: Key, text: &str) -> EditResult {
514 self.mode = Mode::Normal;
515
516 match key.code {
517 KeyCode::Escape => EditResult::none(),
518 KeyCode::Char(c) if !key.ctrl && !key.alt => {
519 if self.cursor >= text.len() {
521 return EditResult::none();
522 }
523
524 let mut end = self.cursor + 1;
526 while end < text.len() && !text.is_char_boundary(end) {
527 end += 1;
528 }
529
530 EditResult {
533 edits: vec![
534 TextEdit::Insert {
535 at: self.cursor,
536 text: c.to_string(),
537 },
538 TextEdit::Delete {
539 start: self.cursor,
540 end,
541 },
542 ],
543 ..Default::default()
544 }
545 }
546 _ => EditResult::none(),
547 }
548 }
549
550 fn selection_range(&self) -> (usize, usize) {
552 let anchor = self.visual_anchor.unwrap_or(self.cursor);
553 if self.cursor < anchor {
554 (self.cursor, anchor)
555 } else {
556 (anchor, self.cursor + 1) }
558 }
559}
560
561impl LineEditor for VimLineEditor {
562 fn handle_key(&mut self, key: Key, text: &str) -> EditResult {
563 self.clamp_cursor(text);
564
565 let result = match self.mode {
566 Mode::Normal => self.handle_normal(key, text),
567 Mode::Insert => self.handle_insert(key, text),
568 Mode::OperatorPending(op) => self.handle_operator_pending(op, key, text),
569 Mode::Visual => self.handle_visual(key, text),
570 Mode::ReplaceChar => self.handle_replace_char(key, text),
571 };
572
573 if let Some(ref yanked) = result.yanked {
575 self.yank_buffer = yanked.clone();
576 }
577
578 result
579 }
580
581 fn cursor(&self) -> usize {
582 self.cursor
583 }
584
585 fn status(&self) -> &str {
586 match self.mode {
587 Mode::Normal => "NORMAL",
588 Mode::Insert => "INSERT",
589 Mode::OperatorPending(Operator::Delete) => "d...",
590 Mode::OperatorPending(Operator::Change) => "c...",
591 Mode::OperatorPending(Operator::Yank) => "y...",
592 Mode::Visual => "VISUAL",
593 Mode::ReplaceChar => "r...",
594 }
595 }
596
597 fn selection(&self) -> Option<Range<usize>> {
598 if self.mode == Mode::Visual {
599 let (start, end) = self.selection_range();
600 Some(start..end)
601 } else {
602 None
603 }
604 }
605
606 fn reset(&mut self) {
607 self.cursor = 0;
608 self.mode = Mode::Normal;
609 self.visual_anchor = None;
610 }
612
613 fn set_cursor(&mut self, pos: usize, text: &str) {
614 let pos = pos.min(text.len());
616 self.cursor = if text.is_char_boundary(pos) {
617 pos
618 } else {
619 let mut p = pos;
621 while p > 0 && !text.is_char_boundary(p) {
622 p -= 1;
623 }
624 p
625 };
626 }
627}
628
629#[cfg(test)]
630mod tests;