1use log::debug;
3
4use super::Result;
5use crate::highlight::CmdKind;
6use crate::keys::{KeyCode as K, KeyEvent, KeyEvent as E, Modifiers as M};
7use crate::tty::{self, RawReader, Term, Terminal};
8use crate::{Config, EditMode};
9#[cfg(feature = "custom-bindings")]
10use crate::{Event, EventContext, EventHandler};
11
12pub type RepeatCount = usize;
14
15#[derive(Debug, Clone, Eq, PartialEq)]
17#[non_exhaustive]
18pub enum Cmd {
19 Abort, AcceptLine,
25 BeginningOfHistory,
27 CapitalizeWord,
29 ClearScreen,
31 #[cfg(windows)]
33 PasteFromClipboard,
34 Complete,
36 CompleteBackward,
38 CompleteHint,
40 Dedent(Movement),
42 DowncaseWord,
44 EndOfFile,
46 EndOfHistory,
48 ForwardSearchHistory,
50 HistorySearchBackward,
52 HistorySearchForward,
54 Indent(Movement),
56 Insert(RepeatCount, String),
58 Interrupt,
60 Kill(Movement),
64 Move(Movement),
68 NextHistory,
70 Noop,
72 Repaint,
74 Overwrite(char),
76 PreviousHistory,
78 QuotedInsert,
80 ReplaceChar(RepeatCount, char),
82 Replace(Movement, Option<String>),
84 ReverseSearchHistory,
86 SelfInsert(RepeatCount, char),
88 Suspend,
90 TransposeChars,
92 TransposeWords(RepeatCount),
94 Undo(RepeatCount),
96 Unknown,
98 UpcaseWord,
100 ViYankTo(Movement),
102 Yank(RepeatCount, Anchor),
104 YankPop,
106 LineUpOrPreviousHistory(RepeatCount),
109 LineDownOrNextHistory(RepeatCount),
112 Newline,
114 AcceptOrInsertLine {
125 accept_in_the_middle: bool,
128 },
129}
130
131impl Cmd {
132 #[must_use]
134 pub const fn should_reset_kill_ring(&self) -> bool {
135 match *self {
136 Self::Kill(Movement::BackwardChar(_) | Movement::ForwardChar(_)) => true,
137 Self::ClearScreen
138 | Self::Kill(_)
139 | Self::Replace(..)
140 | Self::Noop
141 | Self::Suspend
142 | Self::Yank(..)
143 | Self::YankPop => false,
144 _ => true,
145 }
146 }
147
148 const fn is_repeatable_change(&self) -> bool {
149 matches!(
150 *self,
151 Self::Dedent(..)
152 | Self::Indent(..)
153 | Self::Insert(..)
154 | Self::Kill(_)
155 | Self::ReplaceChar(..)
156 | Self::Replace(..)
157 | Self::SelfInsert(..)
158 | Self::ViYankTo(_)
159 | Self::Yank(..) )
161 }
162
163 const fn is_repeatable(&self) -> bool {
164 match *self {
165 Self::Move(_) => true,
166 _ => self.is_repeatable_change(),
167 }
168 }
169
170 fn redo(&self, new: Option<RepeatCount>, wrt: &dyn Refresher) -> Self {
172 match *self {
173 Self::Dedent(ref mvt) => Self::Dedent(mvt.redo(new)),
174 Self::Indent(ref mvt) => Self::Indent(mvt.redo(new)),
175 Self::Insert(previous, ref text) => {
176 Self::Insert(repeat_count(previous, new), text.clone())
177 }
178 Self::Kill(ref mvt) => Self::Kill(mvt.redo(new)),
179 Self::Move(ref mvt) => Self::Move(mvt.redo(new)),
180 Self::ReplaceChar(previous, c) => Self::ReplaceChar(repeat_count(previous, new), c),
181 Self::Replace(ref mvt, ref text) => {
182 if text.is_none() {
183 let last_insert = wrt.last_insert();
184 if let Movement::ForwardChar(0) = mvt {
185 Self::Replace(
186 Movement::ForwardChar(last_insert.as_ref().map_or(0, String::len)),
187 last_insert,
188 )
189 } else {
190 Self::Replace(mvt.redo(new), last_insert)
191 }
192 } else {
193 Self::Replace(mvt.redo(new), text.clone())
194 }
195 }
196 Self::SelfInsert(previous, c) => {
197 if let Some(text) = wrt.last_insert() {
199 Self::Insert(repeat_count(previous, new), text)
200 } else {
201 Self::SelfInsert(repeat_count(previous, new), c)
202 }
203 }
204 Self::ViYankTo(ref mvt) => Self::ViYankTo(mvt.redo(new)),
206 Self::Yank(previous, anchor) => Self::Yank(repeat_count(previous, new), anchor),
207 _ => unreachable!(),
208 }
209 }
210}
211
212const fn repeat_count(previous: RepeatCount, new: Option<RepeatCount>) -> RepeatCount {
213 match new {
214 Some(n) => n,
215 None => previous,
216 }
217}
218
219#[derive(Debug, Clone, Eq, PartialEq, Copy)]
221pub enum Word {
222 Big,
224 Emacs,
226 Vi,
228}
229
230#[derive(Debug, Clone, Eq, PartialEq, Copy)]
232pub enum At {
233 Start,
235 BeforeEnd,
237 AfterEnd,
239}
240
241#[derive(Debug, Clone, Eq, PartialEq, Copy)]
243pub enum Anchor {
244 After,
246 Before,
248}
249
250#[derive(Debug, Clone, Eq, PartialEq, Copy)]
252pub enum CharSearch {
253 Forward(char),
255 ForwardBefore(char),
257 Backward(char),
259 BackwardAfter(char),
261}
262
263impl CharSearch {
264 const fn opposite(self) -> Self {
265 match self {
266 Self::Forward(c) => Self::Backward(c),
267 Self::ForwardBefore(c) => Self::BackwardAfter(c),
268 Self::Backward(c) => Self::Forward(c),
269 Self::BackwardAfter(c) => Self::ForwardBefore(c),
270 }
271 }
272}
273
274#[derive(Debug, Clone, Eq, PartialEq)]
276#[non_exhaustive]
277pub enum Movement {
278 WholeLine,
280 BeginningOfLine,
282 EndOfLine,
284 BackwardWord(RepeatCount, Word), ForwardWord(RepeatCount, At, Word), ViCharSearch(RepeatCount, CharSearch),
290 ViFirstPrint,
292 BackwardChar(RepeatCount),
294 ForwardChar(RepeatCount),
296 LineUp(RepeatCount),
298 LineDown(RepeatCount),
300 WholeBuffer,
302 BeginningOfBuffer,
304 EndOfBuffer,
306}
307
308impl Movement {
309 const fn redo(&self, new: Option<RepeatCount>) -> Self {
311 match *self {
312 Self::WholeLine => Self::WholeLine,
313 Self::BeginningOfLine => Self::BeginningOfLine,
314 Self::ViFirstPrint => Self::ViFirstPrint,
315 Self::EndOfLine => Self::EndOfLine,
316 Self::BackwardWord(previous, word) => {
317 Self::BackwardWord(repeat_count(previous, new), word)
318 }
319 Self::ForwardWord(previous, at, word) => {
320 Self::ForwardWord(repeat_count(previous, new), at, word)
321 }
322 Self::ViCharSearch(previous, char_search) => {
323 Self::ViCharSearch(repeat_count(previous, new), char_search)
324 }
325 Self::BackwardChar(previous) => Self::BackwardChar(repeat_count(previous, new)),
326 Self::ForwardChar(previous) => Self::ForwardChar(repeat_count(previous, new)),
327 Self::LineUp(previous) => Self::LineUp(repeat_count(previous, new)),
328 Self::LineDown(previous) => Self::LineDown(repeat_count(previous, new)),
329 Self::WholeBuffer => Self::WholeBuffer,
330 Self::BeginningOfBuffer => Self::BeginningOfBuffer,
331 Self::EndOfBuffer => Self::EndOfBuffer,
332 }
333 }
334}
335
336#[derive(Clone, Copy, Eq, PartialEq)]
338pub enum InputMode {
339 Command,
341 Insert,
343 Replace,
345}
346
347pub struct InputState<'b> {
349 pub(crate) mode: EditMode,
350 #[cfg_attr(not(feature = "custom-bindings"), expect(dead_code))]
351 custom_bindings: &'b Bindings,
352 pub(crate) input_mode: InputMode, num_args: i16,
355 last_cmd: Cmd, last_char_search: Option<CharSearch>, }
358
359pub trait Invoke {
361 fn input(&self) -> &str;
363 }
366
367impl Invoke for &str {
368 fn input(&self) -> &str {
369 self
370 }
371}
372
373pub trait Refresher {
374 fn refresh_line(&mut self) -> Result<()>;
377 fn refresh_line_with_msg(&mut self, msg: Option<&str>, kind: CmdKind) -> Result<()>;
379 fn refresh_prompt_and_line(&mut self, prompt: &str) -> Result<()>;
381 fn doing_insert(&mut self);
383 fn done_inserting(&mut self);
385 fn last_insert(&self) -> Option<String>;
387 fn is_cursor_at_end(&self) -> bool;
389 fn has_hint(&self) -> bool;
391 #[cfg_attr(not(feature = "custom-bindings"), expect(dead_code))]
393 fn hint_text(&self) -> Option<&str>;
394 fn line(&self) -> &str;
396 #[cfg_attr(not(feature = "custom-bindings"), expect(dead_code))]
398 fn pos(&self) -> usize;
399 fn external_print(&mut self, msg: String) -> Result<()>;
401}
402
403impl<'b> InputState<'b> {
404 pub fn new(config: &Config, custom_bindings: &'b Bindings) -> Self {
405 Self {
406 mode: config.edit_mode(),
407 custom_bindings,
408 input_mode: InputMode::Insert,
409 num_args: 0,
410 last_cmd: Cmd::Noop,
411 last_char_search: None,
412 }
413 }
414
415 pub fn is_emacs_mode(&self) -> bool {
416 self.mode == EditMode::Emacs
417 }
418
419 pub fn next_cmd(
423 &mut self,
424 rdr: &mut <Terminal as Term>::Reader,
425 wrt: &mut dyn Refresher,
426 single_esc_abort: bool,
427 ignore_external_print: bool,
428 ) -> Result<Cmd> {
429 let single_esc_abort = self.single_esc_abort(single_esc_abort);
430 let key;
431 if ignore_external_print {
432 key = rdr.next_key(single_esc_abort)?;
433 } else {
434 loop {
435 let event = rdr.wait_for_input(single_esc_abort)?;
436 match event {
437 tty::Event::KeyPress(k) => {
438 key = k;
439 break;
440 }
441 tty::Event::ExternalPrint(msg) => {
442 wrt.external_print(msg)?;
443 }
444 }
445 }
446 }
447 match self.mode {
448 EditMode::Emacs => self.emacs(rdr, wrt, key),
449 EditMode::Vi if self.input_mode != InputMode::Command => self.vi_insert(rdr, wrt, key),
450 EditMode::Vi => self.vi_command(rdr, wrt, key),
451 }
452 }
453
454 fn single_esc_abort(&self, single_esc_abort: bool) -> bool {
455 match self.mode {
456 EditMode::Emacs => single_esc_abort,
457 EditMode::Vi => false,
458 }
459 }
460
461 fn term_binding<R: RawReader>(rdr: &R, wrt: &dyn Refresher, key: &KeyEvent) -> Option<Cmd> {
463 let cmd = rdr.find_binding(key);
464 if cmd == Some(Cmd::EndOfFile) && !wrt.line().is_empty() {
465 None } else {
467 cmd
468 }
469 }
470
471 fn emacs_digit_argument<R: RawReader>(
472 &mut self,
473 rdr: &mut R,
474 wrt: &mut dyn Refresher,
475 digit: char,
476 ) -> Result<KeyEvent> {
477 #[expect(clippy::cast_possible_truncation)]
478 match digit {
479 '0'..='9' => {
480 self.num_args = digit.to_digit(10).unwrap() as i16;
481 }
482 '-' => {
483 self.num_args = -1;
484 }
485 _ => unreachable!(),
486 }
487 loop {
488 wrt.refresh_prompt_and_line(&format!("(arg: {}) ", self.num_args))?;
489 let key = rdr.next_key(true)?;
490 #[expect(clippy::cast_possible_truncation)]
491 match key {
492 E(K::Char(digit @ '0'..='9'), m) if m == M::NONE || m == M::ALT => {
493 if self.num_args == -1 {
494 self.num_args *= digit.to_digit(10).unwrap() as i16;
495 } else if self.num_args.abs() < 1000 {
496 self.num_args = self
498 .num_args
499 .saturating_mul(10)
500 .saturating_add(digit.to_digit(10).unwrap() as i16);
501 }
502 }
503 E(K::Char('-'), m) if m == M::NONE || m == M::ALT => {}
504 _ => {
505 wrt.refresh_line()?;
506 return Ok(key);
507 }
508 };
509 }
510 }
511
512 fn emacs<R: RawReader>(
513 &mut self,
514 rdr: &mut R,
515 wrt: &mut dyn Refresher,
516 mut key: KeyEvent,
517 ) -> Result<Cmd> {
518 if let E(K::Char(digit @ '-'), M::ALT) = key {
519 key = self.emacs_digit_argument(rdr, wrt, digit)?;
520 } else if let E(K::Char(digit @ '0'..='9'), M::ALT) = key {
521 key = self.emacs_digit_argument(rdr, wrt, digit)?;
522 }
523 let (n, positive) = self.emacs_num_args(); let mut evt = key.into();
526 if let Some(cmd) = self.custom_binding(wrt, &evt, n, positive) {
527 return Ok(if cmd.is_repeatable() {
528 cmd.redo(Some(n), wrt)
529 } else {
530 cmd
531 });
532 } else if let Some(cmd) = InputState::term_binding(rdr, wrt, &key) {
533 return Ok(cmd);
534 }
535 let cmd = match key {
536 E(K::Char(c), M::NONE) => {
537 if positive {
538 Cmd::SelfInsert(n, c)
539 } else {
540 Cmd::Unknown
541 }
542 }
543 E(K::Char('A'), M::CTRL) => Cmd::Move(Movement::BeginningOfLine),
544 E(K::Char('B'), M::CTRL) => Cmd::Move(if positive {
545 Movement::BackwardChar(n)
546 } else {
547 Movement::ForwardChar(n)
548 }),
549 E(K::Char('E'), M::CTRL) => Cmd::Move(Movement::EndOfLine),
550 E(K::Char('F'), M::CTRL) => Cmd::Move(if positive {
551 Movement::ForwardChar(n)
552 } else {
553 Movement::BackwardChar(n)
554 }),
555 E(K::Char('G'), M::CTRL | M::CTRL_ALT) | E::ESC => Cmd::Abort,
556 E(K::Char('H'), M::CTRL) | E::BACKSPACE => Cmd::Kill(if positive {
557 Movement::BackwardChar(n)
558 } else {
559 Movement::ForwardChar(n)
560 }),
561 E(K::BackTab, M::NONE) => Cmd::CompleteBackward,
562 E(K::Char('I'), M::CTRL) | E(K::Tab, M::NONE) => {
563 if positive {
564 Cmd::Complete
565 } else {
566 Cmd::CompleteBackward
567 }
568 }
569 E(K::Right, M::NONE) if wrt.has_hint() && wrt.is_cursor_at_end() => Cmd::CompleteHint,
571 E(K::Char('K'), M::CTRL) => Cmd::Kill(if positive {
572 Movement::EndOfLine
573 } else {
574 Movement::BeginningOfLine
575 }),
576 E(K::Char('L'), M::CTRL) => Cmd::ClearScreen,
577 E(K::Char('N'), M::CTRL) => Cmd::NextHistory,
578 E(K::Char('P'), M::CTRL) => Cmd::PreviousHistory,
579 E(K::Char('X'), M::CTRL) => {
580 if let Some(cmd) = self.custom_seq_binding(rdr, wrt, &mut evt, n, positive)? {
581 cmd
582 } else {
583 let snd_key = match evt {
584 #[allow(clippy::out_of_bounds_indexing)]
586 Event::KeySeq(ref key_seq) if key_seq.len() > 1 => key_seq[1],
587 _ => rdr.next_key(true)?,
588 };
589 match snd_key {
590 E(K::Char('G'), M::CTRL) | E::ESC => Cmd::Abort,
591 E(K::Char('U'), M::CTRL) => Cmd::Undo(n),
592 E(K::Backspace, M::NONE) => Cmd::Kill(if positive {
593 Movement::BeginningOfLine
594 } else {
595 Movement::EndOfLine
596 }),
597 _ => Cmd::Unknown,
598 }
599 }
600 }
601 E(K::Char(']'), m @ (M::CTRL | M::CTRL_ALT)) => {
603 let ch = rdr.next_key(false)?;
604 match ch {
605 E(K::Char(ch), M::NONE) => Cmd::Move(Movement::ViCharSearch(
606 n,
607 if positive {
608 if m.contains(M::ALT) {
609 CharSearch::Backward(ch)
610 } else {
611 CharSearch::ForwardBefore(ch)
612 }
613 } else if m.contains(M::ALT) {
614 CharSearch::ForwardBefore(ch)
615 } else {
616 CharSearch::Backward(ch)
617 },
618 )),
619 _ => Cmd::Unknown,
620 }
621 }
622 E(K::Backspace, M::ALT) => Cmd::Kill(if positive {
623 Movement::BackwardWord(n, Word::Emacs)
624 } else {
625 Movement::ForwardWord(n, At::AfterEnd, Word::Emacs)
626 }),
627 E(K::Char('<'), M::ALT) => Cmd::BeginningOfHistory,
628 E(K::Char('>'), M::ALT) => Cmd::EndOfHistory,
629 E(K::Char('B' | 'b') | K::Left, M::ALT) | E(K::Left, M::CTRL) => {
630 Cmd::Move(if positive {
631 Movement::BackwardWord(n, Word::Emacs)
632 } else {
633 Movement::ForwardWord(n, At::AfterEnd, Word::Emacs)
634 })
635 }
636 E(K::Char('C' | 'c'), M::ALT) => Cmd::CapitalizeWord,
637 E(K::Char('D' | 'd'), M::ALT) => Cmd::Kill(if positive {
638 Movement::ForwardWord(n, At::AfterEnd, Word::Emacs)
639 } else {
640 Movement::BackwardWord(n, Word::Emacs)
641 }),
642 E(K::Char('F' | 'f') | K::Right, M::ALT) | E(K::Right, M::CTRL) => {
643 Cmd::Move(if positive {
644 Movement::ForwardWord(n, At::AfterEnd, Word::Emacs)
645 } else {
646 Movement::BackwardWord(n, Word::Emacs)
647 })
648 }
649 E(K::Char('L' | 'l'), M::ALT) => Cmd::DowncaseWord,
650 E(K::Char('T' | 't'), M::ALT) => Cmd::TransposeWords(n),
651 E(K::Char('U' | 'u'), M::ALT) => Cmd::UpcaseWord,
653 E(K::Char('Y' | 'y'), M::ALT) => Cmd::YankPop,
654 _ => self.common(rdr, wrt, evt, key, n, positive)?,
655 };
656 debug!(target: "rustyline", "Emacs command: {:?}", cmd);
657 Ok(cmd)
658 }
659
660 #[expect(clippy::cast_possible_truncation)]
661 fn vi_arg_digit<R: RawReader>(
662 &mut self,
663 rdr: &mut R,
664 wrt: &mut dyn Refresher,
665 digit: char,
666 ) -> Result<KeyEvent> {
667 self.num_args = digit.to_digit(10).unwrap() as i16;
668 loop {
669 wrt.refresh_prompt_and_line(&format!("(arg: {}) ", self.num_args))?;
670 let key = rdr.next_key(false)?;
671 if let E(K::Char(digit @ '0'..='9'), M::NONE) = key {
672 if self.num_args.abs() < 1000 {
673 self.num_args = self
675 .num_args
676 .saturating_mul(10)
677 .saturating_add(digit.to_digit(10).unwrap() as i16);
678 }
679 } else {
680 wrt.refresh_line()?;
681 return Ok(key);
682 };
683 }
684 }
685
686 fn vi_command<R: RawReader>(
687 &mut self,
688 rdr: &mut R,
689 wrt: &mut dyn Refresher,
690 mut key: KeyEvent,
691 ) -> Result<Cmd> {
692 if let E(K::Char(digit @ '1'..='9'), M::NONE) = key {
693 key = self.vi_arg_digit(rdr, wrt, digit)?;
694 }
695 let no_num_args = self.num_args == 0;
696 let n = self.vi_num_args(); let evt = key.into();
698 if let Some(cmd) = self.custom_binding(wrt, &evt, n, true) {
699 return Ok(if cmd.is_repeatable() {
700 if no_num_args {
701 cmd.redo(None, wrt)
702 } else {
703 cmd.redo(Some(n), wrt)
704 }
705 } else {
706 cmd
707 });
708 } else if let Some(cmd) = InputState::term_binding(rdr, wrt, &key) {
709 return Ok(cmd);
710 }
711 let cmd = match key {
712 E(K::Char('$') | K::End, M::NONE) => Cmd::Move(Movement::EndOfLine),
713 E(K::Char('.'), M::NONE) => {
714 if !self.last_cmd.is_repeatable() {
716 Cmd::Noop
717 } else if no_num_args {
718 self.last_cmd.redo(None, wrt)
719 } else {
720 self.last_cmd.redo(Some(n), wrt)
721 }
722 }
723 E(K::Char('0'), M::NONE) => Cmd::Move(Movement::BeginningOfLine),
726 E(K::Char('^'), M::NONE) => Cmd::Move(Movement::ViFirstPrint),
727 E(K::Char('a'), M::NONE) => {
728 self.input_mode = InputMode::Insert;
730 wrt.doing_insert();
731 Cmd::Move(Movement::ForwardChar(n))
732 }
733 E(K::Char('A'), M::NONE) => {
734 self.input_mode = InputMode::Insert;
736 wrt.doing_insert();
737 Cmd::Move(Movement::EndOfLine)
738 }
739 E(K::Char('b'), M::NONE) => Cmd::Move(Movement::BackwardWord(n, Word::Vi)), E(K::Char('B'), M::NONE) => Cmd::Move(Movement::BackwardWord(n, Word::Big)),
741 E(K::Char('c'), M::NONE) => {
742 self.input_mode = InputMode::Insert;
743 match self.vi_cmd_motion(rdr, wrt, key, n)? {
744 Some(mvt) => Cmd::Replace(mvt, None),
745 None => Cmd::Unknown,
746 }
747 }
748 E(K::Char('C'), M::NONE) => {
749 self.input_mode = InputMode::Insert;
750 Cmd::Replace(Movement::EndOfLine, None)
751 }
752 E(K::Char('d'), M::NONE) => match self.vi_cmd_motion(rdr, wrt, key, n)? {
753 Some(mvt) => Cmd::Kill(mvt),
754 None => Cmd::Unknown,
755 },
756 E(K::Char('D'), M::NONE) | E(K::Char('K'), M::CTRL) => Cmd::Kill(Movement::EndOfLine),
757 E(K::Char('e'), M::NONE) => {
758 Cmd::Move(Movement::ForwardWord(n, At::BeforeEnd, Word::Vi))
759 }
760 E(K::Char('E'), M::NONE) => {
761 Cmd::Move(Movement::ForwardWord(n, At::BeforeEnd, Word::Big))
762 }
763 E(K::Char('i'), M::NONE) => {
764 self.input_mode = InputMode::Insert;
766 wrt.doing_insert();
767 Cmd::Noop
768 }
769 E(K::Char('I'), M::NONE) => {
770 self.input_mode = InputMode::Insert;
772 wrt.doing_insert();
773 Cmd::Move(Movement::BeginningOfLine)
774 }
775 E(K::Char(c), M::NONE) if c == 'f' || c == 'F' || c == 't' || c == 'T' => {
776 let cs = self.vi_char_search(rdr, c)?;
778 match cs {
779 Some(cs) => Cmd::Move(Movement::ViCharSearch(n, cs)),
780 None => Cmd::Unknown,
781 }
782 }
783 E(K::Char(';'), M::NONE) => match self.last_char_search {
784 Some(cs) => Cmd::Move(Movement::ViCharSearch(n, cs)),
785 None => Cmd::Noop,
786 },
787 E(K::Char(','), M::NONE) => match self.last_char_search {
788 Some(ref cs) => Cmd::Move(Movement::ViCharSearch(n, cs.opposite())),
789 None => Cmd::Noop,
790 },
791 E(K::Char('p'), M::NONE) => Cmd::Yank(n, Anchor::After), E(K::Char('P'), M::NONE) => Cmd::Yank(n, Anchor::Before), E(K::Char('r'), M::NONE) => {
795 let ch = rdr.next_key(false)?;
797 match ch {
798 E(K::Char(c), M::NONE) => Cmd::ReplaceChar(n, c),
799 E::ESC => Cmd::Noop,
800 _ => Cmd::Unknown,
801 }
802 }
803 E(K::Char('R'), M::NONE) => {
804 self.input_mode = InputMode::Replace;
806 Cmd::Replace(Movement::ForwardChar(0), None)
807 }
808 E(K::Char('s'), M::NONE) => {
809 self.input_mode = InputMode::Insert;
811 Cmd::Replace(Movement::ForwardChar(n), None)
812 }
813 E(K::Char('S'), M::NONE) => {
814 self.input_mode = InputMode::Insert;
816 Cmd::Replace(Movement::WholeLine, None)
817 }
818 E(K::Char('u'), M::NONE) => Cmd::Undo(n),
819 E(K::Char('w'), M::NONE) => Cmd::Move(Movement::ForwardWord(n, At::Start, Word::Vi)), E(K::Char('W'), M::NONE) => Cmd::Move(Movement::ForwardWord(n, At::Start, Word::Big)), E(K::Char('x'), M::NONE) => Cmd::Kill(Movement::ForwardChar(n)), E(K::Char('X'), M::NONE) => Cmd::Kill(Movement::BackwardChar(n)), E(K::Char('y'), M::NONE) => match self.vi_cmd_motion(rdr, wrt, key, n)? {
826 Some(mvt) => Cmd::ViYankTo(mvt),
827 None => Cmd::Unknown,
828 },
829 E(K::Char('h'), M::NONE) | E(K::Char('H'), M::CTRL) | E::BACKSPACE => {
831 Cmd::Move(Movement::BackwardChar(n))
832 }
833 E(K::Char('G'), M::CTRL) => Cmd::Abort,
834 E(K::Char('l' | ' '), M::NONE) => Cmd::Move(Movement::ForwardChar(n)),
835 E(K::Char('L'), M::CTRL) => Cmd::ClearScreen,
836 E(K::Char('+' | 'j'), M::NONE) => Cmd::LineDownOrNextHistory(n),
837 E(K::Char('N'), M::CTRL) => Cmd::NextHistory,
839 E(K::Char('-' | 'k'), M::NONE) => Cmd::LineUpOrPreviousHistory(n),
840 E(K::Char('P'), M::CTRL) => Cmd::PreviousHistory,
842 E(K::Char('R'), M::CTRL) => {
843 self.input_mode = InputMode::Insert; Cmd::ReverseSearchHistory
845 }
846 E(K::Char('S'), M::CTRL) => {
847 self.input_mode = InputMode::Insert; Cmd::ForwardSearchHistory
849 }
850 E(K::Char('<'), M::NONE) => match self.vi_cmd_motion(rdr, wrt, key, n)? {
851 Some(mvt) => Cmd::Dedent(mvt),
852 None => Cmd::Unknown,
853 },
854 E(K::Char('>'), M::NONE) => match self.vi_cmd_motion(rdr, wrt, key, n)? {
855 Some(mvt) => Cmd::Indent(mvt),
856 None => Cmd::Unknown,
857 },
858 E::ESC => Cmd::Noop,
859 _ => self.common(rdr, wrt, evt, key, n, true)?,
860 };
861 debug!(target: "rustyline", "Vi command: {:?}", cmd);
862 if cmd.is_repeatable_change() {
863 self.last_cmd = cmd.clone();
864 }
865 Ok(cmd)
866 }
867
868 fn vi_insert<R: RawReader>(
869 &mut self,
870 rdr: &mut R,
871 wrt: &mut dyn Refresher,
872 key: KeyEvent,
873 ) -> Result<Cmd> {
874 let evt = key.into();
875 if let Some(cmd) = self.custom_binding(wrt, &evt, 0, true) {
876 return Ok(if cmd.is_repeatable() {
877 cmd.redo(None, wrt)
878 } else {
879 cmd
880 });
881 } else if let Some(cmd) = InputState::term_binding(rdr, wrt, &key) {
882 return Ok(cmd);
883 }
884 let cmd = match key {
885 E(K::Char(c), M::NONE) => {
886 if self.input_mode == InputMode::Replace {
887 Cmd::Overwrite(c)
888 } else {
889 Cmd::SelfInsert(1, c)
890 }
891 }
892 E(K::Char('H'), M::CTRL) | E::BACKSPACE => Cmd::Kill(Movement::BackwardChar(1)),
893 E(K::BackTab, M::NONE) => Cmd::CompleteBackward,
894 E(K::Char('I'), M::CTRL) | E(K::Tab, M::NONE) => Cmd::Complete,
895 E(K::Right, M::NONE) if wrt.has_hint() && wrt.is_cursor_at_end() => Cmd::CompleteHint,
897 E(K::Char(k), M::ALT) => {
898 debug!(target: "rustyline", "Vi fast command mode: {}", k);
899 self.input_mode = InputMode::Command;
900 wrt.done_inserting();
901
902 self.vi_command(rdr, wrt, E(K::Char(k), M::NONE))?
903 }
904 E::ESC => {
905 self.input_mode = InputMode::Command;
907 wrt.done_inserting();
908 Cmd::Move(Movement::BackwardChar(1))
909 }
910 _ => self.common(rdr, wrt, evt, key, 1, true)?,
911 };
912 debug!(target: "rustyline", "Vi insert: {:?}", cmd);
913 if cmd.is_repeatable_change() {
914 if let (Cmd::Replace(..), Cmd::SelfInsert(..)) = (&self.last_cmd, &cmd) {
915 } else if let (Cmd::SelfInsert(..), Cmd::SelfInsert(..)) = (&self.last_cmd, &cmd) {
917 } else {
919 self.last_cmd = cmd.clone();
920 }
921 }
922 Ok(cmd)
923 }
924
925 fn vi_cmd_motion<R: RawReader>(
926 &mut self,
927 rdr: &mut R,
928 wrt: &mut dyn Refresher,
929 key: KeyEvent,
930 n: RepeatCount,
931 ) -> Result<Option<Movement>> {
932 let mut mvt = rdr.next_key(false)?;
933 if mvt == key {
934 return Ok(Some(Movement::WholeLine));
935 }
936 let mut n = n;
937 if let E(K::Char(digit @ '1'..='9'), M::NONE) = mvt {
938 mvt = self.vi_arg_digit(rdr, wrt, digit)?;
940 n = self.vi_num_args().saturating_mul(n);
941 }
942 Ok(match mvt {
943 E(K::Char('$'), M::NONE) => Some(Movement::EndOfLine),
944 E(K::Char('0'), M::NONE) => Some(Movement::BeginningOfLine),
945 E(K::Char('^'), M::NONE) => Some(Movement::ViFirstPrint),
946 E(K::Char('b'), M::NONE) => Some(Movement::BackwardWord(n, Word::Vi)),
947 E(K::Char('B'), M::NONE) => Some(Movement::BackwardWord(n, Word::Big)),
948 E(K::Char('e'), M::NONE) => Some(Movement::ForwardWord(n, At::AfterEnd, Word::Vi)),
949 E(K::Char('E'), M::NONE) => Some(Movement::ForwardWord(n, At::AfterEnd, Word::Big)),
950 E(K::Char(c), M::NONE) if c == 'f' || c == 'F' || c == 't' || c == 'T' => {
951 let cs = self.vi_char_search(rdr, c)?;
952 cs.map(|cs| Movement::ViCharSearch(n, cs))
953 }
954 E(K::Char(';'), M::NONE) => self
955 .last_char_search
956 .map(|cs| Movement::ViCharSearch(n, cs)),
957 E(K::Char(','), M::NONE) => self
958 .last_char_search
959 .map(|cs| Movement::ViCharSearch(n, cs.opposite())),
960 E(K::Char('h'), M::NONE) | E(K::Char('H'), M::CTRL) | E::BACKSPACE => {
961 Some(Movement::BackwardChar(n))
962 }
963 E(K::Char('l' | ' '), M::NONE) => Some(Movement::ForwardChar(n)),
964 E(K::Char('j' | '+'), M::NONE) => Some(Movement::LineDown(n)),
965 E(K::Char('k' | '-'), M::NONE) => Some(Movement::LineUp(n)),
966 E(K::Char('w'), M::NONE) => {
967 if key == E(K::Char('c'), M::NONE) {
969 Some(Movement::ForwardWord(n, At::AfterEnd, Word::Vi))
970 } else {
971 Some(Movement::ForwardWord(n, At::Start, Word::Vi))
972 }
973 }
974 E(K::Char('W'), M::NONE) => {
975 if key == E(K::Char('c'), M::NONE) {
977 Some(Movement::ForwardWord(n, At::AfterEnd, Word::Big))
978 } else {
979 Some(Movement::ForwardWord(n, At::Start, Word::Big))
980 }
981 }
982 _ => None,
983 })
984 }
985
986 fn vi_char_search<R: RawReader>(
987 &mut self,
988 rdr: &mut R,
989 cmd: char,
990 ) -> Result<Option<CharSearch>> {
991 let ch = rdr.next_key(false)?;
992 Ok(match ch {
993 E(K::Char(ch), M::NONE) => {
994 let cs = match cmd {
995 'f' => CharSearch::Forward(ch),
996 't' => CharSearch::ForwardBefore(ch),
997 'F' => CharSearch::Backward(ch),
998 'T' => CharSearch::BackwardAfter(ch),
999 _ => unreachable!(),
1000 };
1001 self.last_char_search = Some(cs);
1002 Some(cs)
1003 }
1004 _ => None,
1005 })
1006 }
1007
1008 fn common<R: RawReader>(
1009 &mut self,
1010 rdr: &mut R,
1011 wrt: &dyn Refresher,
1012 mut evt: Event,
1013 key: KeyEvent,
1014 n: RepeatCount,
1015 positive: bool,
1016 ) -> Result<Cmd> {
1017 Ok(match key {
1018 E(K::Home, M::NONE) => Cmd::Move(Movement::BeginningOfLine),
1019 E(K::Left, M::NONE) => Cmd::Move(if positive {
1020 Movement::BackwardChar(n)
1021 } else {
1022 Movement::ForwardChar(n)
1023 }),
1024 #[cfg(any(windows, test))]
1025 E(K::Char('C'), M::CTRL) => Cmd::Interrupt,
1026 E(K::Char('D'), M::CTRL) => {
1027 if self.is_emacs_mode() && !wrt.line().is_empty() {
1028 Cmd::Kill(if positive {
1029 Movement::ForwardChar(n)
1030 } else {
1031 Movement::BackwardChar(n)
1032 })
1033 } else if cfg!(windows) || cfg!(test) || !wrt.line().is_empty() {
1034 Cmd::EndOfFile
1035 } else {
1036 Cmd::Unknown
1037 }
1038 }
1039 E(K::Delete, M::NONE) => Cmd::Kill(if positive {
1040 Movement::ForwardChar(n)
1041 } else {
1042 Movement::BackwardChar(n)
1043 }),
1044 E(K::End, M::NONE) => Cmd::Move(Movement::EndOfLine),
1045 E(K::Right, M::NONE) => Cmd::Move(if positive {
1046 Movement::ForwardChar(n)
1047 } else {
1048 Movement::BackwardChar(n)
1049 }),
1050 E(K::Char('J' | 'M'), M::CTRL) | E::ENTER => Cmd::AcceptOrInsertLine {
1051 accept_in_the_middle: true,
1052 },
1053 E(K::Down, M::NONE) => Cmd::LineDownOrNextHistory(1),
1054 E(K::Up, M::NONE) => Cmd::LineUpOrPreviousHistory(1),
1055 E(K::Char('R'), M::CTRL) => Cmd::ReverseSearchHistory,
1056 E(K::Char('S'), M::CTRL) => Cmd::ForwardSearchHistory,
1058 E(K::Char('T'), M::CTRL) => Cmd::TransposeChars,
1059 E(K::Char('U'), M::CTRL) => Cmd::Kill(if positive {
1060 Movement::BeginningOfLine
1061 } else {
1062 Movement::EndOfLine
1063 }),
1064 E(K::Char('Q'), M::CTRL) => Cmd::QuotedInsert,
1066 #[cfg(not(windows))]
1067 E(K::Char('V'), M::CTRL) => Cmd::QuotedInsert,
1068 #[cfg(windows)]
1069 E(K::Char('V'), M::CTRL) => Cmd::PasteFromClipboard,
1070 E(K::Char('W'), M::CTRL) => Cmd::Kill(if positive {
1071 Movement::BackwardWord(n, Word::Big)
1072 } else {
1073 Movement::ForwardWord(n, At::AfterEnd, Word::Big)
1074 }),
1075 E(K::Char('Y'), M::CTRL) => {
1076 if positive {
1077 Cmd::Yank(n, Anchor::Before)
1078 } else {
1079 Cmd::Unknown }
1081 }
1082 E(K::Char('_'), M::CTRL) => Cmd::Undo(n),
1083 E(K::UnknownEscSeq, M::NONE) => Cmd::Noop,
1084 E(K::BracketedPasteStart, M::NONE) => {
1085 let paste = rdr.read_pasted_text()?;
1086 Cmd::Insert(1, paste)
1087 }
1088 _ => self
1089 .custom_seq_binding(rdr, wrt, &mut evt, n, positive)?
1090 .unwrap_or(Cmd::Unknown),
1091 })
1092 }
1093
1094 fn num_args(&mut self) -> i16 {
1095 let num_args = match self.num_args {
1096 0 => 1,
1097 _ => self.num_args,
1098 };
1099 self.num_args = 0;
1100 num_args
1101 }
1102
1103 #[expect(clippy::cast_sign_loss)]
1104 fn emacs_num_args(&mut self) -> (RepeatCount, bool) {
1105 let num_args = self.num_args();
1106 if num_args < 0 {
1107 if let (n, false) = num_args.overflowing_abs() {
1108 (n as RepeatCount, false)
1109 } else {
1110 (RepeatCount::MAX, false)
1111 }
1112 } else {
1113 (num_args as RepeatCount, true)
1114 }
1115 }
1116
1117 fn vi_num_args(&mut self) -> RepeatCount {
1118 let num_args = self.num_args();
1119 if num_args < 0 {
1120 unreachable!()
1121 } else {
1122 num_args.unsigned_abs() as RepeatCount
1123 }
1124 }
1125}
1126
1127#[cfg(feature = "custom-bindings")]
1128impl InputState<'_> {
1129 fn custom_binding(
1131 &self,
1132 wrt: &dyn Refresher,
1133 evt: &Event,
1134 n: RepeatCount,
1135 positive: bool,
1136 ) -> Option<Cmd> {
1137 let bindings = self.custom_bindings;
1138 let handler = bindings.get(evt).or_else(|| bindings.get(&Event::Any));
1139 if let Some(handler) = handler {
1140 match handler {
1141 EventHandler::Simple(cmd) => Some(cmd.clone()),
1142 EventHandler::Conditional(handler) => {
1143 let ctx = EventContext::new(self, wrt);
1144 handler.handle(evt, n, positive, &ctx)
1145 }
1146 }
1147 } else {
1148 None
1149 }
1150 }
1151
1152 fn custom_seq_binding<R: RawReader>(
1153 &self,
1154 rdr: &mut R,
1155 wrt: &dyn Refresher,
1156 evt: &mut Event,
1157 n: RepeatCount,
1158 positive: bool,
1159 ) -> Result<Option<Cmd>> {
1160 while let Some(subtrie) = self.custom_bindings.get_raw_descendant(evt) {
1161 let snd_key = rdr.next_key(true)?;
1162 if let Event::KeySeq(ref mut key_seq) = evt {
1163 key_seq.push(snd_key);
1164 } else {
1165 break;
1166 }
1167 let handler = subtrie.get(evt).unwrap();
1168 if let Some(handler) = handler {
1169 let cmd = match handler {
1170 EventHandler::Simple(cmd) => Some(cmd.clone()),
1171 EventHandler::Conditional(handler) => {
1172 let ctx = EventContext::new(self, wrt);
1173 handler.handle(evt, n, positive, &ctx)
1174 }
1175 };
1176 if cmd.is_some() {
1177 return Ok(cmd);
1178 }
1179 }
1180 }
1181 Ok(None)
1182 }
1183}
1184
1185#[cfg(not(feature = "custom-bindings"))]
1186impl<'b> InputState<'b> {
1187 fn custom_binding(&self, _: &dyn Refresher, _: &Event, _: RepeatCount, _: bool) -> Option<Cmd> {
1188 None
1189 }
1190
1191 fn custom_seq_binding<R: RawReader>(
1192 &self,
1193 _: &mut R,
1194 _: &dyn Refresher,
1195 _: &mut Event,
1196 _: RepeatCount,
1197 _: bool,
1198 ) -> Result<Option<Cmd>> {
1199 Ok(None)
1200 }
1201}
1202
1203cfg_if::cfg_if! {
1204 if #[cfg(feature = "custom-bindings")] {
1205pub type Bindings = radix_trie::Trie<Event, EventHandler>;
1206 } else {
1207enum Event {
1208 KeySeq([KeyEvent; 1]),
1209}
1210impl From<KeyEvent> for Event {
1211 fn from(k: KeyEvent) -> Self {
1212 Self::KeySeq([k])
1213 }
1214}
1215pub struct Bindings {}
1216impl Bindings {
1217 pub fn new() -> Self {
1218 Self {}
1219 }
1220}
1221 }
1222}