modit/
vi.rs

1use alloc::{string::String, vec::Vec};
2use core::{fmt, mem};
3
4use crate::{Event, Key, Motion, Operator, Parser, TextObject, Word};
5
6pub const VI_DEFAULT_REGISTER: char = '"';
7
8#[derive(Debug)]
9pub struct ViContext<F: FnMut(Event)> {
10    callback: F,
11    selection: bool,
12    pending_change: Option<Vec<Event>>,
13    change: Option<Vec<Event>>,
14    set_mode: Option<ViMode>,
15}
16
17impl<F: FnMut(Event)> ViContext<F> {
18    fn start_change(&mut self) {
19        if self.pending_change.is_none() {
20            self.pending_change = Some(Vec::new());
21        }
22        (self.callback)(Event::ChangeStart);
23    }
24
25    fn finish_change(&mut self) {
26        self.change = self.pending_change.take();
27        (self.callback)(Event::ChangeFinish);
28    }
29
30    fn e(&mut self, event: Event) {
31        match &mut self.pending_change {
32            Some(change) => change.push(event.clone()),
33            None => {}
34        }
35        (self.callback)(event);
36    }
37}
38
39#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
40pub struct ViCmd {
41    register: Option<char>,
42    count: Option<usize>,
43    operator: Option<Operator>,
44    motion: Option<Motion>,
45    text_object: Option<TextObject>,
46}
47
48impl fmt::Display for ViCmd {
49    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50        if let Some(register) = self.register {
51            write!(f, "\"{register}")?;
52        }
53        if let Some(count) = self.count {
54            write!(f, "{count}")?;
55        }
56        if let Some(operator) = self.operator {
57            write!(f, "{operator:?}")?;
58        }
59        if let Some(motion) = self.motion {
60            write!(f, "{motion:?}")?;
61        }
62        if let Some(text_object) = self.text_object {
63            write!(f, "{text_object:?}")?;
64        }
65        Ok(())
66    }
67}
68
69impl ViCmd {
70    /// Repeat the provided function count times, resetting count after
71    pub fn repeat<F: FnMut(usize)>(&mut self, mut f: F) {
72        for i in 0..self.count.take().unwrap_or(1) {
73            f(i);
74        }
75    }
76
77    /// Set motion
78    pub fn motion<F: FnMut(Event)>(&mut self, motion: Motion, ctx: &mut ViContext<F>) {
79        self.motion = Some(motion);
80        self.run(ctx);
81    }
82
83    /// Set operator, may set motion if operator is doubled like `dd`
84    pub fn operator<F: FnMut(Event)>(&mut self, operator: Operator, ctx: &mut ViContext<F>) {
85        if self.operator == Some(operator) {
86            self.motion = Some(Motion::Line);
87        } else {
88            self.operator = Some(operator);
89        }
90        self.run(ctx);
91    }
92
93    /// Set text object and return true if supported by the motion
94    pub fn text_object<F: FnMut(Event)>(
95        &mut self,
96        text_object: TextObject,
97        ctx: &mut ViContext<F>,
98    ) -> bool {
99        if !self.motion.map_or(false, |motion| motion.text_object()) {
100            // Did not need text object
101            return false;
102        }
103
104        // Needed text object
105        self.text_object = Some(text_object);
106        self.run(ctx);
107        true
108    }
109
110    /// Run operation, resetting it to defaults if it runs
111    pub fn run<F: FnMut(Event)>(&mut self, ctx: &mut ViContext<F>) -> bool {
112        match self.motion {
113            Some(motion) => {
114                if motion.text_object() && self.text_object.is_none() {
115                    // After or inside requires a text object
116                    return false;
117                }
118            }
119            None => {
120                if !ctx.selection {
121                    // No motion requires a selection
122                    return false;
123                }
124            }
125        }
126
127        let register = self.register.take().unwrap_or(VI_DEFAULT_REGISTER);
128        let count = self.count.take().unwrap_or(1);
129        let motion = self.motion.take().unwrap_or(Motion::Selection);
130        let text_object = self.text_object.take();
131
132        //TODO: clean up logic of Motion, such that actual motions and references to
133        // text objects and selections are not in the same enum
134        match self.operator.take() {
135            Some(operator) => {
136                ctx.start_change();
137
138                match motion {
139                    Motion::Around => ctx.e(Event::SelectTextObject(
140                        text_object.expect("no text object"),
141                        true,
142                    )),
143                    Motion::Inside => ctx.e(Event::SelectTextObject(
144                        text_object.expect("no text object"),
145                        false,
146                    )),
147                    Motion::Line => {
148                        ctx.e(Event::SelectLineStart);
149                    }
150                    Motion::Selection => {}
151                    _ => {
152                        ctx.e(Event::SelectStart);
153                        for _ in 0..count {
154                            ctx.e(Event::Motion(motion));
155                        }
156                    }
157                }
158
159                let mut enter_insert_mode = false;
160                match operator {
161                    Operator::AutoIndent => {
162                        ctx.e(Event::AutoIndent);
163                    }
164                    Operator::Change => {
165                        ctx.e(Event::Yank { register });
166                        ctx.e(Event::Delete);
167                        enter_insert_mode = true;
168                    }
169                    Operator::Delete => {
170                        ctx.e(Event::Yank { register });
171                        ctx.e(Event::Delete);
172                    }
173                    Operator::ShiftLeft => {
174                        ctx.e(Event::ShiftLeft);
175                    }
176                    Operator::ShiftRight => {
177                        ctx.e(Event::ShiftRight);
178                    }
179                    Operator::SwapCase => {
180                        ctx.e(Event::SwapCase);
181                    }
182                    Operator::Yank => {
183                        ctx.e(Event::Yank { register });
184                    }
185                }
186
187                ctx.e(Event::SelectClear);
188                if enter_insert_mode {
189                    ctx.set_mode = Some(ViMode::Insert);
190                } else {
191                    ctx.finish_change();
192                    ctx.set_mode = Some(ViMode::Normal);
193                }
194            }
195            None => match motion {
196                Motion::Around => ctx.e(Event::SelectTextObject(
197                    text_object.expect("no text object"),
198                    true,
199                )),
200                Motion::Inside => ctx.e(Event::SelectTextObject(
201                    text_object.expect("no text object"),
202                    false,
203                )),
204                _ => {
205                    for _ in 0..count {
206                        ctx.e(Event::Motion(motion));
207                    }
208                }
209            },
210        }
211
212        true
213    }
214}
215
216#[derive(Clone, Debug, Eq, PartialEq)]
217pub enum ViMode {
218    /// Normal mode
219    Normal,
220    /// Waiting for another character to complete command
221    Extra(char),
222    /// Insert mode
223    Insert,
224    /// Replace mode
225    Replace,
226    /// Visual mode
227    Visual,
228    /// Visual line mode
229    VisualLine,
230    /// Command mode
231    Command { value: String },
232    /// Search mode
233    Search { value: String, forwards: bool },
234}
235
236#[derive(Debug)]
237pub struct ViParser {
238    pub mode: ViMode,
239    pub cmd: ViCmd,
240    pub register_mode: ViMode,
241    pub semicolon_motion: Option<Motion>,
242    pub pending_change: Option<Vec<Event>>,
243    pub last_change: Option<Vec<Event>>,
244}
245
246impl ViParser {
247    pub fn new() -> Self {
248        Self {
249            mode: ViMode::Normal,
250            cmd: ViCmd::default(),
251            register_mode: ViMode::Normal,
252            semicolon_motion: None,
253            pending_change: None,
254            last_change: None,
255        }
256    }
257}
258
259impl Parser for ViParser {
260    fn reset(&mut self) {
261        self.mode = ViMode::Normal;
262        self.cmd = ViCmd::default();
263    }
264
265    fn parse<F: FnMut(Event)>(&mut self, key: Key, selection: bool, callback: F) {
266        // Makes composing commands easier
267        let cmd = &mut self.cmd;
268        // Normalize key, so we don't deal with control characters below
269        let key = key.normalize();
270        // Makes managing callbacks easier
271        let mut ctx = ViContext {
272            selection,
273            callback,
274            pending_change: self.pending_change.take(),
275            change: None,
276            set_mode: None,
277        };
278        let ctx = &mut ctx;
279        match self.mode {
280            ViMode::Normal | ViMode::Visual | ViMode::VisualLine => match key {
281                Key::Backspace => cmd.motion(Motion::Left, ctx),
282                //TODO: what should backtab do?
283                Key::Backtab => (),
284                Key::Delete => {
285                    ctx.start_change();
286                    cmd.repeat(|_| ctx.e(Event::DeleteInLine));
287                    ctx.finish_change();
288                }
289                Key::Down => cmd.motion(Motion::Down, ctx),
290                Key::End => cmd.motion(Motion::End, ctx),
291                Key::Enter => {
292                    cmd.motion(Motion::Down, ctx);
293                    cmd.motion(Motion::SoftHome, ctx);
294                }
295                Key::Escape => {
296                    self.reset();
297                    ctx.e(Event::Escape);
298                }
299                Key::Home => cmd.motion(Motion::Home, ctx),
300                Key::Left => cmd.motion(Motion::LeftInLine, ctx),
301                Key::PageDown => cmd.motion(Motion::PageDown, ctx),
302                Key::PageUp => cmd.motion(Motion::PageUp, ctx),
303                Key::Right => cmd.motion(Motion::RightInLine, ctx),
304                //TODO: what should tab do?
305                Key::Tab => (),
306                Key::Up => cmd.motion(Motion::Up, ctx),
307                Key::Char(c) => match c {
308                    // Enter insert mode after cursor (if not awaiting text object)
309                    'a' => {
310                        if cmd.operator.is_some() || self.mode != ViMode::Normal {
311                            cmd.motion(Motion::Around, ctx);
312                        } else {
313                            ctx.start_change();
314                            ViCmd::default().motion(Motion::Right, ctx);
315                            self.mode = ViMode::Insert;
316                        }
317                    }
318                    // Enter insert mode at end of line
319                    'A' => {
320                        ctx.start_change();
321                        ViCmd::default().motion(Motion::End, ctx);
322                        self.mode = ViMode::Insert;
323                    }
324                    // Previous word (if not text object)
325                    'b' => {
326                        if !cmd.text_object(TextObject::Block, ctx) {
327                            cmd.motion(Motion::PreviousWordStart(Word::Lower), ctx);
328                        }
329                    }
330                    // Previous WORD (if not text object)
331                    //TODO: should this TextObject be different?
332                    'B' => {
333                        if !cmd.text_object(TextObject::Block, ctx) {
334                            cmd.motion(Motion::PreviousWordStart(Word::Upper), ctx);
335                        }
336                    }
337                    // Change mode
338                    'c' => {
339                        cmd.operator(Operator::Change, ctx);
340                    }
341                    // Change to end of line
342                    'C' => {
343                        cmd.operator(Operator::Change, ctx);
344                        cmd.motion(Motion::End, ctx);
345                    }
346                    // Delete mode
347                    'd' => {
348                        cmd.operator(Operator::Delete, ctx);
349                    }
350                    // Delete to end of line
351                    'D' => {
352                        cmd.operator(Operator::Delete, ctx);
353                        cmd.motion(Motion::End, ctx);
354                    }
355                    // End of word
356                    'e' => cmd.motion(Motion::NextWordEnd(Word::Lower), ctx),
357                    // End of WORD
358                    'E' => cmd.motion(Motion::NextWordEnd(Word::Upper), ctx),
359                    // Find char forwards
360                    'f' => {
361                        self.mode = ViMode::Extra(c);
362                    }
363                    // Find char backwords
364                    'F' => {
365                        self.mode = ViMode::Extra(c);
366                    }
367                    // g commands
368                    'g' => {
369                        self.mode = ViMode::Extra(c);
370                    }
371                    // Goto line (or end of file)
372                    'G' => match cmd.count.take() {
373                        Some(line) => cmd.motion(Motion::GotoLine(line), ctx),
374                        None => cmd.motion(Motion::GotoEof, ctx),
375                    },
376                    // Left (in line)
377                    'h' => cmd.motion(Motion::LeftInLine, ctx),
378                    // Top of screen
379                    'H' => cmd.motion(Motion::ScreenHigh, ctx),
380                    // Enter insert mode at cursor (if not awaiting text object)
381                    'i' => {
382                        if cmd.operator.is_some() || self.mode != ViMode::Normal {
383                            cmd.motion(Motion::Inside, ctx);
384                        } else {
385                            ctx.start_change();
386                            self.mode = ViMode::Insert;
387                        }
388                    }
389                    // Enter insert mode at start of line
390                    'I' => {
391                        ctx.start_change();
392                        ViCmd::default().motion(Motion::SoftHome, ctx);
393                        self.mode = ViMode::Insert;
394                    }
395                    // Down
396                    'j' => cmd.motion(Motion::Down, ctx),
397                    //TODO: Join lines
398                    'J' => {}
399                    // Up
400                    'k' => cmd.motion(Motion::Up, ctx),
401                    //TODO: Look up keyword (vim looks up word under cursor in man pages)
402                    'K' => {}
403                    // Right (in line)
404                    'l' => cmd.motion(Motion::RightInLine, ctx),
405                    // Bottom of screen
406                    'L' => cmd.motion(Motion::ScreenLow, ctx),
407                    //TODO: Set mark
408                    'm' => {}
409                    // Middle of screen
410                    'M' => cmd.motion(Motion::ScreenMiddle, ctx),
411                    // Next search item
412                    'n' => cmd.motion(Motion::NextSearch, ctx),
413                    // Previous search item
414                    'N' => cmd.motion(Motion::PreviousSearch, ctx),
415                    // Create line after and enter insert mode
416                    'o' => {
417                        ctx.start_change();
418                        ViCmd::default().motion(Motion::End, ctx);
419                        ctx.e(Event::NewLine);
420                        self.mode = ViMode::Insert;
421                    }
422                    // Create line before and enter insert mode
423                    'O' => {
424                        ctx.start_change();
425                        ViCmd::default().motion(Motion::Home, ctx);
426                        ctx.e(Event::NewLine);
427                        ViCmd::default().motion(Motion::Up, ctx);
428                        self.mode = ViMode::Insert;
429                    }
430                    // Paste after (if not text object)
431                    'p' => {
432                        if !cmd.text_object(TextObject::Paragraph, ctx) {
433                            let register = cmd.register.unwrap_or(VI_DEFAULT_REGISTER);
434                            ctx.e(Event::Put {
435                                register,
436                                after: true,
437                            });
438                        }
439                    }
440                    // Paste before
441                    'P' => {
442                        let register = cmd.register.unwrap_or(VI_DEFAULT_REGISTER);
443                        ctx.e(Event::Put {
444                            register,
445                            after: false,
446                        });
447                    }
448                    //TODO: q, Q
449                    // Replace char
450                    'r' => {
451                        self.mode = ViMode::Extra(c);
452                    }
453                    // Replace mode
454                    'R' => {
455                        ctx.start_change();
456                        self.mode = ViMode::Replace;
457                    }
458                    // Substitute char (if not text object)
459                    's' => {
460                        if !cmd.text_object(TextObject::Sentence, ctx) {
461                            ctx.start_change();
462                            cmd.repeat(|_| ctx.e(Event::DeleteInLine));
463                            self.mode = ViMode::Insert;
464                        }
465                    }
466                    // Substitute line
467                    'S' => {
468                        cmd.operator(Operator::Change, ctx);
469                        cmd.motion(Motion::Line, ctx);
470                    }
471                    // Until character forwards (if not text object)
472                    't' => {
473                        if !cmd.text_object(TextObject::Tag, ctx) {
474                            self.mode = ViMode::Extra(c);
475                        }
476                    }
477                    // Until character backwards
478                    'T' => {
479                        self.mode = ViMode::Extra(c);
480                    }
481                    // Undo
482                    'u' => {
483                        ctx.e(Event::Undo);
484                    }
485                    //TODO: U
486                    // Enter visual mode
487                    'v' => {
488                        //TODO: this is very hacky and has bugs
489                        if self.mode == ViMode::Visual {
490                            ctx.e(Event::SelectClear);
491                            self.mode = ViMode::Normal;
492                        } else {
493                            ctx.e(Event::SelectStart);
494                            self.mode = ViMode::Visual;
495                        }
496                    }
497                    // Enter line visual mode
498                    'V' => {
499                        if self.mode == ViMode::VisualLine {
500                            ctx.e(Event::SelectClear);
501                            self.mode = ViMode::Normal;
502                        } else {
503                            ctx.e(Event::SelectLineStart);
504                            self.mode = ViMode::VisualLine;
505                        }
506                    }
507                    // Next word (if not text object)
508                    'w' => {
509                        if !cmd.text_object(TextObject::Word(Word::Lower), ctx) {
510                            cmd.motion(Motion::NextWordStart(Word::Lower), ctx);
511                        }
512                    }
513                    // Next WORD (if not text object)
514                    'W' => {
515                        if !cmd.text_object(TextObject::Word(Word::Upper), ctx) {
516                            cmd.motion(Motion::NextWordStart(Word::Upper), ctx);
517                        }
518                    }
519                    // Remove character at cursor
520                    'x' => {
521                        ctx.start_change();
522                        cmd.repeat(|_| ctx.e(Event::DeleteInLine));
523                        ctx.finish_change();
524                    }
525                    // Remove character before cursor
526                    'X' => {
527                        ctx.start_change();
528                        cmd.repeat(|_| ctx.e(Event::BackspaceInLine));
529                        ctx.finish_change();
530                    }
531                    // Yank
532                    'y' => cmd.operator(Operator::Yank, ctx),
533                    // Yank line
534                    'Y' => {
535                        cmd.operator(Operator::Yank, ctx);
536                        cmd.motion(Motion::Line, ctx);
537                    }
538                    // z commands
539                    'z' => {
540                        self.mode = ViMode::Extra(c);
541                    }
542                    // Z commands
543                    'Z' => {
544                        self.mode = ViMode::Extra(c);
545                    }
546                    // Go to start of line
547                    '0' => match cmd.count {
548                        Some(ref mut count) => {
549                            *count = count.saturating_mul(10);
550                        }
551                        None => {
552                            cmd.motion(Motion::Home, ctx);
553                        }
554                    },
555                    // Count of next action
556                    '1'..='9' => {
557                        let number = (c as u32).saturating_sub('0' as u32) as usize;
558                        cmd.count = Some(match cmd.count.take() {
559                            Some(count) => count.saturating_mul(10).saturating_add(number),
560                            None => number,
561                        });
562                    }
563                    // TODO (if not text object)
564                    '`' => if !cmd.text_object(TextObject::Ticks, ctx) {},
565                    // Swap case
566                    '~' => cmd.operator(Operator::SwapCase, ctx),
567                    // TODO: !, @, #
568                    // Go to end of line
569                    '$' => cmd.motion(Motion::End, ctx),
570                    //TODO: %
571                    // Go to start of line after whitespace
572                    '^' => cmd.motion(Motion::SoftHome, ctx),
573                    //TODO &, *
574                    // TODO (if not text object)
575                    '(' => if !cmd.text_object(TextObject::Parentheses, ctx) {},
576                    // TODO (if not text object)
577                    ')' => if !cmd.text_object(TextObject::Parentheses, ctx) {},
578                    // Move up and soft home
579                    '-' => {
580                        cmd.motion(Motion::Up, ctx);
581                        cmd.motion(Motion::SoftHome, ctx);
582                    }
583                    // Move down and soft home
584                    '+' => {
585                        cmd.motion(Motion::Down, ctx);
586                        cmd.motion(Motion::SoftHome, ctx);
587                    }
588                    // Auto indent
589                    '=' => cmd.operator(Operator::AutoIndent, ctx),
590                    // TODO (if not text object)
591                    '[' => if !cmd.text_object(TextObject::SquareBrackets, ctx) {},
592                    // TODO (if not text object)
593                    '{' => if !cmd.text_object(TextObject::CurlyBrackets, ctx) {},
594                    // TODO (if not text object)
595                    ']' => if !cmd.text_object(TextObject::SquareBrackets, ctx) {},
596                    // TODO (if not text object)
597                    '}' => if !cmd.text_object(TextObject::CurlyBrackets, ctx) {},
598                    // Repeat f/F/t/T
599                    ';' => {
600                        if let Some(motion) = self.semicolon_motion {
601                            cmd.motion(motion, ctx);
602                        }
603                    }
604                    // Enter command mode
605                    ':' => {
606                        self.mode = ViMode::Command {
607                            value: String::new(),
608                        };
609                    }
610                    //TODO (if not text object)
611                    '\'' => if !cmd.text_object(TextObject::SingleQuotes, ctx) {},
612                    // Select register (if not text object)
613                    '"' => {
614                        if !cmd.text_object(TextObject::DoubleQuotes, ctx) {
615                            self.register_mode = self.mode.clone();
616                            self.mode = ViMode::Extra(c);
617                        }
618                    }
619                    // Reverse f/F/t/T
620                    ',' => {
621                        if let Some(motion) = self.semicolon_motion {
622                            if let Some(reverse) = motion.reverse() {
623                                cmd.motion(reverse, ctx);
624                            }
625                        }
626                    }
627                    // Unindent (if not text object)
628                    '<' => {
629                        if !cmd.text_object(TextObject::AngleBrackets, ctx) {
630                            cmd.operator(Operator::ShiftLeft, ctx);
631                        }
632                    }
633                    // Repeat change
634                    '.' => {
635                        if let Some(change) = &self.last_change {
636                            ctx.start_change();
637                            for event in change.iter() {
638                                ctx.e(event.clone());
639                            }
640                            ctx.finish_change();
641                        }
642                    }
643                    // Indent (if not text object)
644                    '>' => {
645                        if !cmd.text_object(TextObject::AngleBrackets, ctx) {
646                            cmd.operator(Operator::ShiftRight, ctx);
647                        }
648                    }
649                    // Enter search mode
650                    '/' => {
651                        self.mode = ViMode::Search {
652                            value: String::new(),
653                            forwards: true,
654                        };
655                    }
656                    // Enter search backwards mode
657                    '?' => {
658                        self.mode = ViMode::Search {
659                            value: String::new(),
660                            forwards: false,
661                        };
662                    }
663                    // Right
664                    ' ' => cmd.motion(Motion::Right, ctx),
665                    _ => {}
666                },
667                Key::Ctrl(c) => {
668                    //TODO: Ctrl characters
669                }
670            },
671            ViMode::Extra(extra) => match extra {
672                // Find/till character
673                'f' | 'F' | 't' | 'T' => {
674                    match key {
675                        Key::Char(c) => {
676                            let motion = match extra {
677                                'f' => Motion::NextChar(c),
678                                'F' => Motion::PreviousChar(c),
679                                't' => Motion::NextCharTill(c),
680                                'T' => Motion::PreviousCharTill(c),
681                                _ => unreachable!(),
682                            };
683                            cmd.motion(motion, ctx);
684                            self.semicolon_motion = Some(motion);
685                        }
686                        _ => {}
687                    }
688                    self.reset();
689                }
690                // Extra commands
691                'g' => {
692                    match key {
693                        Key::Char(c) => match c {
694                            // Previous word end
695                            'e' => cmd.motion(Motion::PreviousWordEnd(Word::Lower), ctx),
696                            // Prevous WORD end
697                            'E' => cmd.motion(Motion::PreviousWordEnd(Word::Upper), ctx),
698                            'g' => match cmd.count.take() {
699                                Some(line) => cmd.motion(Motion::GotoLine(line), ctx),
700                                None => cmd.motion(Motion::GotoLine(1), ctx),
701                            },
702                            'n' => {
703                                cmd.motion(Motion::Inside, ctx);
704                                cmd.text_object(TextObject::Search { forwards: true }, ctx);
705                            }
706                            'N' => {
707                                cmd.motion(Motion::Inside, ctx);
708                                cmd.text_object(TextObject::Search { forwards: false }, ctx);
709                            }
710                            //TODO: more g commands
711                            _ => {}
712                        },
713                        //TODO: what do control keys do in this mode?
714                        _ => {}
715                    }
716                    self.reset();
717                }
718                // Replace character
719                'r' => {
720                    match key {
721                        Key::Char(c) => {
722                            //TODO: a visual selection allows replacing all characters
723                            ctx.start_change();
724                            ctx.e(Event::Delete);
725                            ctx.e(Event::Insert(c));
726                            ViCmd::default().motion(Motion::LeftInLine, ctx);
727                            ctx.finish_change();
728                        }
729                        _ => {}
730                    }
731                    self.reset();
732                }
733                // Select register
734                '"' => {
735                    match key {
736                        Key::Char(c) => {
737                            cmd.register = Some(c);
738                        }
739                        _ => {}
740                    }
741                    self.mode = self.register_mode.clone();
742                    self.register_mode = ViMode::Normal;
743                }
744                _ => {
745                    //TODO
746                    log::info!("TODO: extra command {:?}{:?}", extra, key);
747                    self.reset();
748                }
749            },
750            ViMode::Insert | ViMode::Replace => match key {
751                //TODO: FINISH CHANGE ON MOTION?
752                Key::Backspace => ctx.e(Event::Backspace),
753                Key::Backtab => ctx.e(Event::ShiftLeft),
754                Key::Char(c) => {
755                    if self.mode == ViMode::Replace {
756                        ctx.e(Event::Delete);
757                    }
758                    ctx.e(Event::Insert(c));
759                }
760                Key::Ctrl(c) => {
761                    //TODO: control characters
762                }
763                Key::Down => ViCmd::default().motion(Motion::Down, ctx),
764                Key::Delete => ctx.e(Event::Delete),
765                Key::End => ViCmd::default().motion(Motion::End, ctx),
766                Key::Enter => ctx.e(Event::NewLine),
767                Key::Escape => {
768                    ViCmd::default().motion(Motion::LeftInLine, ctx);
769                    ctx.finish_change();
770                    self.reset();
771                }
772                Key::Home => ViCmd::default().motion(Motion::Home, ctx),
773                Key::Left => ViCmd::default().motion(Motion::LeftInLine, ctx),
774                Key::PageDown => ViCmd::default().motion(Motion::PageDown, ctx),
775                Key::PageUp => ViCmd::default().motion(Motion::PageUp, ctx),
776                Key::Right => ViCmd::default().motion(Motion::RightInLine, ctx),
777                Key::Tab => ctx.e(Event::ShiftRight),
778                Key::Up => ViCmd::default().motion(Motion::Up, ctx),
779            },
780            ViMode::Command { ref mut value } => match key {
781                Key::Escape => {
782                    self.reset();
783                }
784                Key::Enter => {
785                    //TODO: run command
786                    self.reset();
787                }
788                Key::Backspace => {
789                    if value.pop().is_none() {
790                        self.reset();
791                    }
792                }
793                Key::Char(c) => {
794                    value.push(c);
795                }
796                _ => {
797                    //TODO: more keys
798                }
799            },
800            ViMode::Search {
801                ref mut value,
802                forwards,
803            } => match key {
804                Key::Escape => {
805                    self.reset();
806                }
807                Key::Enter => {
808                    // Swap search value to avoid allocations
809                    let mut tmp = String::new();
810                    mem::swap(value, &mut tmp);
811                    ctx.e(Event::SetSearch(tmp, forwards));
812                    self.reset();
813                    ViCmd::default().motion(Motion::NextSearch, ctx);
814                }
815                Key::Backspace => {
816                    if value.pop().is_none() {
817                        self.reset();
818                    }
819                }
820                Key::Char(c) => {
821                    value.push(c);
822                }
823                _ => {
824                    //TODO: more keys
825                }
826            },
827        }
828
829        // Reset mode after operators
830        if let Some(mode) = ctx.set_mode.take() {
831            self.mode = mode;
832        }
833
834        // Save change state
835        self.pending_change = ctx.pending_change.take();
836        if let Some(change) = ctx.change.take() {
837            self.last_change = Some(change);
838        }
839
840        //TODO: optimize redraw
841        ctx.e(Event::Redraw);
842    }
843}