From 72f6513d2a9632288952578b39fd816d20daebbe Mon Sep 17 00:00:00 2001 From: Jonathan Turner Date: Wed, 15 Jul 2020 19:51:59 +1200 Subject: [PATCH] Keybindings and invocation fix (#2186) --- crates/nu-cli/src/cli.rs | 4 + crates/nu-cli/src/evaluate/evaluator.rs | 2 +- crates/nu-cli/src/evaluate/variables.rs | 6 + crates/nu-cli/src/keybinding.rs | 430 ++++++++++++++++++++++++ crates/nu-cli/src/lib.rs | 1 + 5 files changed, 442 insertions(+), 1 deletion(-) create mode 100644 crates/nu-cli/src/keybinding.rs diff --git a/crates/nu-cli/src/cli.rs b/crates/nu-cli/src/cli.rs index 56c2eda450..8762b07611 100644 --- a/crates/nu-cli/src/cli.rs +++ b/crates/nu-cli/src/cli.rs @@ -545,6 +545,10 @@ pub async fn cli( Cmd::Move(Movement::ForwardWord(1, At::AfterEnd, Word::Vi)), ); + if let Err(e) = crate::keybinding::load_keybindings(&mut rl) { + println!("Error loading keybindings: {:?}", e); + } + #[cfg(windows)] { let _ = ansi_term::enable_ansi_support(); diff --git a/crates/nu-cli/src/evaluate/evaluator.rs b/crates/nu-cli/src/evaluate/evaluator.rs index 3ae34a6fcf..1e3cd77608 100644 --- a/crates/nu-cli/src/evaluate/evaluator.rs +++ b/crates/nu-cli/src/evaluate/evaluator.rs @@ -197,7 +197,7 @@ async fn evaluate_invocation( let input = InputStream::empty(); let mut block = block.clone(); - block.set_is_last(true); + block.set_is_last(false); let result = run_block(&block, &mut context, input, it, vars, env).await?; diff --git a/crates/nu-cli/src/evaluate/variables.rs b/crates/nu-cli/src/evaluate/variables.rs index 7aae9e21f4..2eabb5de3d 100644 --- a/crates/nu-cli/src/evaluate/variables.rs +++ b/crates/nu-cli/src/evaluate/variables.rs @@ -42,6 +42,12 @@ pub fn nu(env: &IndexMap, tag: impl Into) -> Result rustyline::KeyPress { + match keypress { + KeyPress::UnknownEscSeq => rustyline::KeyPress::UnknownEscSeq, + KeyPress::Backspace => rustyline::KeyPress::Backspace, + KeyPress::BackTab => rustyline::KeyPress::BackTab, + KeyPress::BracketedPasteStart => rustyline::KeyPress::BracketedPasteStart, + KeyPress::BracketedPasteEnd => rustyline::KeyPress::BracketedPasteEnd, + KeyPress::Char(c) => rustyline::KeyPress::Char(c), + KeyPress::ControlDown => rustyline::KeyPress::ControlDown, + KeyPress::ControlLeft => rustyline::KeyPress::ControlLeft, + KeyPress::ControlRight => rustyline::KeyPress::ControlRight, + KeyPress::ControlUp => rustyline::KeyPress::ControlUp, + KeyPress::Ctrl(c) => rustyline::KeyPress::Ctrl(c), + KeyPress::Delete => rustyline::KeyPress::Delete, + KeyPress::Down => rustyline::KeyPress::Down, + KeyPress::End => rustyline::KeyPress::End, + KeyPress::Enter => rustyline::KeyPress::Enter, + KeyPress::Esc => rustyline::KeyPress::Esc, + KeyPress::F(u) => rustyline::KeyPress::F(u), + KeyPress::Home => rustyline::KeyPress::Home, + KeyPress::Insert => rustyline::KeyPress::Insert, + KeyPress::Left => rustyline::KeyPress::Left, + KeyPress::Meta(c) => rustyline::KeyPress::Meta(c), + KeyPress::Null => rustyline::KeyPress::Null, + KeyPress::PageDown => rustyline::KeyPress::PageDown, + KeyPress::PageUp => rustyline::KeyPress::PageUp, + KeyPress::Right => rustyline::KeyPress::Right, + KeyPress::ShiftDown => rustyline::KeyPress::ShiftDown, + KeyPress::ShiftLeft => rustyline::KeyPress::ShiftLeft, + KeyPress::ShiftRight => rustyline::KeyPress::ShiftRight, + KeyPress::ShiftUp => rustyline::KeyPress::ShiftUp, + KeyPress::Tab => rustyline::KeyPress::Tab, + KeyPress::Up => rustyline::KeyPress::Up, + } +} + +fn convert_word(word: Word) -> rustyline::Word { + match word { + Word::Big => rustyline::Word::Big, + Word::Emacs => rustyline::Word::Emacs, + Word::Vi => rustyline::Word::Vi, + } +} + +fn convert_at(at: At) -> rustyline::At { + match at { + At::AfterEnd => rustyline::At::AfterEnd, + At::BeforeEnd => rustyline::At::BeforeEnd, + At::Start => rustyline::At::Start, + } +} + +fn convert_char_search(search: CharSearch) -> rustyline::CharSearch { + match search { + CharSearch::Backward(c) => rustyline::CharSearch::Backward(c), + CharSearch::BackwardAfter(c) => rustyline::CharSearch::BackwardAfter(c), + CharSearch::Forward(c) => rustyline::CharSearch::Forward(c), + CharSearch::ForwardBefore(c) => rustyline::CharSearch::ForwardBefore(c), + } +} + +fn convert_movement(movement: Movement) -> rustyline::Movement { + match movement { + Movement::BackwardChar(u) => rustyline::Movement::BackwardChar(u), + Movement::BackwardWord { repeat, word } => { + rustyline::Movement::BackwardWord(repeat, convert_word(word)) + } + Movement::BeginningOfBuffer => rustyline::Movement::BeginningOfBuffer, + Movement::BeginningOfLine => rustyline::Movement::BeginningOfLine, + Movement::EndOfBuffer => rustyline::Movement::EndOfBuffer, + Movement::EndOfLine => rustyline::Movement::EndOfLine, + Movement::ForwardChar(u) => rustyline::Movement::ForwardChar(u), + Movement::ForwardWord { repeat, at, word } => { + rustyline::Movement::ForwardWord(repeat, convert_at(at), convert_word(word)) + } + Movement::LineDown(u) => rustyline::Movement::LineDown(u), + Movement::LineUp(u) => rustyline::Movement::LineUp(u), + Movement::ViCharSearch { repeat, search } => { + rustyline::Movement::ViCharSearch(repeat, convert_char_search(search)) + } + Movement::ViFirstPrint => rustyline::Movement::ViFirstPrint, + Movement::WholeBuffer => rustyline::Movement::WholeBuffer, + Movement::WholeLine => rustyline::Movement::WholeLine, + } +} + +fn convert_anchor(anchor: Anchor) -> rustyline::Anchor { + match anchor { + Anchor::After => rustyline::Anchor::After, + Anchor::Before => rustyline::Anchor::Before, + } +} + +fn convert_cmd(cmd: Cmd) -> rustyline::Cmd { + match cmd { + Cmd::Abort => rustyline::Cmd::Abort, + Cmd::AcceptLine => rustyline::Cmd::AcceptLine, + Cmd::AcceptOrInsertLine => rustyline::Cmd::AcceptOrInsertLine, + Cmd::BeginningOfHistory => rustyline::Cmd::BeginningOfHistory, + Cmd::CapitalizeWord => rustyline::Cmd::CapitalizeWord, + Cmd::ClearScreen => rustyline::Cmd::ClearScreen, + Cmd::Complete => rustyline::Cmd::Complete, + Cmd::CompleteBackward => rustyline::Cmd::CompleteBackward, + Cmd::CompleteHint => rustyline::Cmd::CompleteHint, + Cmd::DowncaseWord => rustyline::Cmd::DowncaseWord, + Cmd::EndOfFile => rustyline::Cmd::EndOfFile, + Cmd::EndOfHistory => rustyline::Cmd::EndOfHistory, + Cmd::ForwardSearchHistory => rustyline::Cmd::ForwardSearchHistory, + Cmd::HistorySearchBackward => rustyline::Cmd::HistorySearchBackward, + Cmd::HistorySearchForward => rustyline::Cmd::HistorySearchForward, + Cmd::Insert { repeat, string } => rustyline::Cmd::Insert(repeat, string), + Cmd::Interrupt => rustyline::Cmd::Interrupt, + Cmd::Kill(movement) => rustyline::Cmd::Kill(convert_movement(movement)), + Cmd::LineDownOrNextHistory(u) => rustyline::Cmd::LineDownOrNextHistory(u), + Cmd::LineUpOrPreviousHistory(u) => rustyline::Cmd::LineUpOrPreviousHistory(u), + Cmd::Move(movement) => rustyline::Cmd::Move(convert_movement(movement)), + Cmd::NextHistory => rustyline::Cmd::NextHistory, + Cmd::Noop => rustyline::Cmd::Noop, + Cmd::Overwrite(c) => rustyline::Cmd::Overwrite(c), + Cmd::PreviousHistory => rustyline::Cmd::PreviousHistory, + Cmd::QuotedInsert => rustyline::Cmd::QuotedInsert, + Cmd::Replace { + movement, + replacement, + } => rustyline::Cmd::Replace(convert_movement(movement), replacement), + Cmd::ReplaceChar { repeat, ch } => rustyline::Cmd::ReplaceChar(repeat, ch), + Cmd::ReverseSearchHistory => rustyline::Cmd::ReverseSearchHistory, + Cmd::SelfInsert { repeat, ch } => rustyline::Cmd::SelfInsert(repeat, ch), + Cmd::Suspend => rustyline::Cmd::Suspend, + Cmd::TransposeChars => rustyline::Cmd::TransposeChars, + Cmd::TransposeWords(u) => rustyline::Cmd::TransposeWords(u), + Cmd::Undo(u) => rustyline::Cmd::Undo(u), + Cmd::Unknown => rustyline::Cmd::Unknown, + Cmd::UpcaseWord => rustyline::Cmd::UpcaseWord, + Cmd::ViYankTo(movement) => rustyline::Cmd::ViYankTo(convert_movement(movement)), + Cmd::Yank { repeat, anchor } => rustyline::Cmd::Yank(repeat, convert_anchor(anchor)), + Cmd::YankPop => rustyline::Cmd::YankPop, + } +} + +fn convert_keybinding(keybinding: Keybinding) -> (rustyline::KeyPress, rustyline::Cmd) { + ( + convert_keypress(keybinding.key), + convert_cmd(keybinding.binding), + ) +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum KeyPress { + /// Unsupported escape sequence (on unix platform) + UnknownEscSeq, + /// ⌫ or `KeyPress::Ctrl('H')` + Backspace, + /// ⇤ (usually Shift-Tab) + BackTab, + /// Paste (on unix platform) + BracketedPasteStart, + /// Paste (on unix platform) + BracketedPasteEnd, + /// Single char + Char(char), + /// Ctrl-↓ + ControlDown, + /// Ctrl-← + ControlLeft, + /// Ctrl-→ + ControlRight, + /// Ctrl-↑ + ControlUp, + /// Ctrl-char + Ctrl(char), + /// ⌦ + Delete, + /// ↓ arrow key + Down, + /// ⇲ + End, + /// ↵ or `KeyPress::Ctrl('M')` + Enter, + /// Escape or `KeyPress::Ctrl('[')` + Esc, + /// Function key + F(u8), + /// ⇱ + Home, + /// Insert key + Insert, + /// ← arrow key + Left, + /// Escape-char or Alt-char + Meta(char), + /// `KeyPress::Char('\0')` + Null, + /// ⇟ + PageDown, + /// ⇞ + PageUp, + /// → arrow key + Right, + /// Shift-↓ + ShiftDown, + /// Shift-← + ShiftLeft, + /// Shift-→ + ShiftRight, + /// Shift-↑ + ShiftUp, + /// ⇥ or `KeyPress::Ctrl('I')` + Tab, + /// ↑ arrow key + Up, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum Cmd { + /// abort + Abort, // Miscellaneous Command + /// accept-line + AcceptLine, + /// beginning-of-history + BeginningOfHistory, + /// capitalize-word + CapitalizeWord, + /// clear-screen + ClearScreen, + /// complete + Complete, + /// complete-backward + CompleteBackward, + /// complete-hint + CompleteHint, + /// downcase-word + DowncaseWord, + /// vi-eof-maybe + EndOfFile, + /// end-of-history + EndOfHistory, + /// forward-search-history + ForwardSearchHistory, + /// history-search-backward + HistorySearchBackward, + /// history-search-forward + HistorySearchForward, + /// Insert text + Insert { repeat: RepeatCount, string: String }, + /// Interrupt signal (Ctrl-C) + Interrupt, + /// backward-delete-char, backward-kill-line, backward-kill-word + /// delete-char, kill-line, kill-word, unix-line-discard, unix-word-rubout, + /// vi-delete, vi-delete-to, vi-rubout + Kill(Movement), + /// backward-char, backward-word, beginning-of-line, end-of-line, + /// forward-char, forward-word, vi-char-search, vi-end-word, vi-next-word, + /// vi-prev-word + Move(Movement), + /// next-history + NextHistory, + /// No action + Noop, + /// vi-replace + Overwrite(char), + /// previous-history + PreviousHistory, + /// quoted-insert + QuotedInsert, + /// vi-change-char + ReplaceChar { repeat: RepeatCount, ch: char }, + /// vi-change-to, vi-substitute + Replace { + movement: Movement, + replacement: Option, + }, + /// reverse-search-history + ReverseSearchHistory, + /// self-insert + SelfInsert { repeat: RepeatCount, ch: char }, + /// Suspend signal (Ctrl-Z on unix platform) + Suspend, + /// transpose-chars + TransposeChars, + /// transpose-words + TransposeWords(RepeatCount), + /// undo + Undo(RepeatCount), + /// Unsupported / unexpected + Unknown, + /// upcase-word + UpcaseWord, + /// vi-yank-to + ViYankTo(Movement), + /// yank, vi-put + Yank { repeat: RepeatCount, anchor: Anchor }, + /// yank-pop + YankPop, + /// moves cursor to the line above or switches to prev history entry if + /// the cursor is already on the first line + LineUpOrPreviousHistory(RepeatCount), + /// moves cursor to the line below or switches to next history entry if + /// the cursor is already on the last line + LineDownOrNextHistory(RepeatCount), + /// accepts the line when cursor is at the end of the text (non including + /// trailing whitespace), inserts newline character otherwise + AcceptOrInsertLine, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum Movement { + /// Whole current line (not really a movement but a range) + WholeLine, + /// beginning-of-line + BeginningOfLine, + /// end-of-line + EndOfLine, + /// backward-word, vi-prev-word + BackwardWord { repeat: RepeatCount, word: Word }, // Backward until start of word + /// forward-word, vi-end-word, vi-next-word + ForwardWord { + repeat: RepeatCount, + at: At, + word: Word, + }, // Forward until start/end of word + /// vi-char-search + ViCharSearch { + repeat: RepeatCount, + search: CharSearch, + }, + /// vi-first-print + ViFirstPrint, + /// backward-char + BackwardChar(RepeatCount), + /// forward-char + ForwardChar(RepeatCount), + /// move to the same column on the previous line + LineUp(RepeatCount), + /// move to the same column on the next line + LineDown(RepeatCount), + /// Whole user input (not really a movement but a range) + WholeBuffer, + /// beginning-of-buffer + BeginningOfBuffer, + /// end-of-buffer + EndOfBuffer, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +enum InputMode { + /// Vi Command/Alternate + Command, + /// Insert/Input mode + Insert, + /// Overwrite mode + Replace, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum Word { + /// non-blanks characters + Big, + /// alphanumeric characters + Emacs, + /// alphanumeric (and '_') characters + Vi, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum At { + /// Start of word. + Start, + /// Before end of word. + BeforeEnd, + /// After end of word. + AfterEnd, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum Anchor { + /// After cursor + After, + /// Before cursor + Before, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum CharSearch { + /// Forward search + Forward(char), + /// Forward search until + ForwardBefore(char), + /// Backward search + Backward(char), + /// Backward search until + BackwardAfter(char), +} + +/// The number of times one command should be repeated. +pub type RepeatCount = usize; + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Keybinding { + key: KeyPress, + binding: Cmd, +} + +type Keybindings = Vec; + +pub(crate) fn keybinding_path() -> Result { + crate::data::config::default_path_for(&Some(std::path::PathBuf::from("keybindings.yml"))) +} + +pub(crate) fn load_keybindings( + rl: &mut rustyline::Editor, +) -> Result<(), nu_errors::ShellError> { + let filename = keybinding_path()?; + let contents = std::fs::read_to_string(filename); + + // Silently fail if there is no file there + if let Ok(contents) = contents { + let keybindings: Keybindings = serde_yaml::from_str(&contents)?; + + for keybinding in keybindings.into_iter() { + let (k, b) = convert_keybinding(keybinding); + + rl.bind_sequence(k, b); + } + } + + Ok(()) +} diff --git a/crates/nu-cli/src/lib.rs b/crates/nu-cli/src/lib.rs index a7bb6c7027..8fba0911d6 100644 --- a/crates/nu-cli/src/lib.rs +++ b/crates/nu-cli/src/lib.rs @@ -23,6 +23,7 @@ mod evaluate; mod format; mod futures; mod git; +mod keybinding; mod path; mod shell; mod stream;