From ff9d88887b09825ea3ceb552271d0b9ce1fb5809 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Tue, 18 Jan 2022 19:32:45 +0000 Subject: [PATCH] simple event keybinding (#773) --- Cargo.lock | 6 +- crates/nu-protocol/src/config.rs | 76 ++++++++++++--- src/fuzzy_completion.rs | 57 ----------- src/main.rs | 2 - src/reedline_config.rs | 160 ++++++++++++++++++++++--------- src/repl.rs | 29 +++--- 6 files changed, 196 insertions(+), 134 deletions(-) delete mode 100644 src/fuzzy_completion.rs diff --git a/Cargo.lock b/Cargo.lock index 9eccec9c3..b38f438ba 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2851,7 +2851,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#b0ff0eb4d1f062a03b76ef0ac28a2ccaada32a52" +source = "git+https://github.com/nushell/reedline?branch=main#caebe19742b61f0f2c075865a3b7fb7354ddb189" dependencies = [ "chrono", "crossterm", @@ -4020,9 +4020,9 @@ dependencies = [ [[package]] name = "zeroize" -version = "1.5.0" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc222aec311c323c717f56060324f32b82da1ce1dd81d9a09aa6a9030bfe08db" +checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619" [[package]] name = "zip" diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index cdb54ef70..5109de43e 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -1,4 +1,4 @@ -use crate::{BlockId, ShellError, Span, Value}; +use crate::{BlockId, ShellError, Span, Spanned, Value}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -41,19 +41,31 @@ impl EnvConversion { /// Definition of a parsed keybinding from the config object #[derive(Serialize, Deserialize, Clone, Debug)] pub struct ParsedKeybinding { - pub modifier: String, - pub keycode: String, - pub event: EventType, - pub mode: EventMode, + pub modifier: Spanned, + pub keycode: Spanned, + pub event: Spanned, + pub mode: Spanned, } impl Default for ParsedKeybinding { fn default() -> Self { Self { - modifier: "".to_string(), - keycode: "".to_string(), - event: EventType::Single("".to_string()), - mode: EventMode::Emacs, + modifier: Spanned { + item: "".to_string(), + span: Span { start: 0, end: 0 }, + }, + keycode: Spanned { + item: "".to_string(), + span: Span { start: 0, end: 0 }, + }, + event: Spanned { + item: EventType::Single("".to_string()), + span: Span { start: 0, end: 0 }, + }, + mode: Spanned { + item: EventMode::Emacs, + span: Span { start: 0, end: 0 }, + }, } } } @@ -66,7 +78,18 @@ pub enum EventType { #[derive(Serialize, Deserialize, Clone, Debug)] pub enum EventMode { Emacs, - Vi, + ViNormal, + ViInsert, +} + +impl EventMode { + pub fn as_str(&self) -> &'static str { + match self { + EventMode::Emacs => "emacs", + EventMode::ViNormal => "vi_normal", + EventMode::ViInsert => "vi_insert", + } + } } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -263,12 +286,32 @@ fn create_keybindings(value: &Value, config: &Config) -> Result keybinding.modifier = val.clone().into_string("", config), - "keycode" => keybinding.keycode = val.clone().into_string("", config), + "modifier" => { + keybinding.modifier = Spanned { + item: val.clone().into_string("", config), + span: val.span()?, + } + } + "keycode" => { + keybinding.keycode = Spanned { + item: val.clone().into_string("", config), + span: val.span()?, + } + } "mode" => { keybinding.mode = match val.clone().into_string("", config).as_str() { - "emacs" => EventMode::Emacs, - "vi" => EventMode::Vi, + "emacs" => Spanned { + item: EventMode::Emacs, + span: val.span()?, + }, + "vi_normal" => Spanned { + item: EventMode::ViNormal, + span: val.span()?, + }, + "vi_insert" => Spanned { + item: EventMode::ViInsert, + span: val.span()?, + }, e => { return Err(ShellError::UnsupportedConfigValue( "emacs or vi".to_string(), @@ -307,7 +350,10 @@ fn create_keybindings(value: &Value, config: &Config) -> Result { return Err(ShellError::UnsupportedConfigValue( diff --git a/src/fuzzy_completion.rs b/src/fuzzy_completion.rs deleted file mode 100644 index 1f8f1cd6e..000000000 --- a/src/fuzzy_completion.rs +++ /dev/null @@ -1,57 +0,0 @@ -use dialoguer::{ - console::{Style, Term}, - theme::ColorfulTheme, - Select, -}; -use reedline::{Completer, CompletionActionHandler, LineBuffer}; - -pub(crate) struct FuzzyCompletion { - pub completer: Box, -} - -impl CompletionActionHandler for FuzzyCompletion { - fn handle(&mut self, present_buffer: &mut LineBuffer) { - let completions = self - .completer - .complete(present_buffer.get_buffer(), present_buffer.offset()); - - if completions.is_empty() { - // do nothing - } else if completions.len() == 1 { - let span = completions[0].0; - - let mut offset = present_buffer.offset(); - offset += completions[0].1.len() - (span.end - span.start); - - // TODO improve the support for multiline replace - present_buffer.replace(span.start..span.end, &completions[0].1); - present_buffer.set_insertion_point(offset); - } else { - let selections: Vec<_> = completions.iter().map(|(_, string)| string).collect(); - - let _ = crossterm::terminal::disable_raw_mode(); - println!(); - let theme = ColorfulTheme { - active_item_style: Style::new().for_stderr().on_green().black(), - ..Default::default() - }; - let result = Select::with_theme(&theme) - .default(0) - .items(&selections[..]) - .interact_on_opt(&Term::stdout()) - .unwrap_or(None); - let _ = crossterm::terminal::enable_raw_mode(); - - if let Some(result) = result { - let span = completions[result].0; - - let mut offset = present_buffer.offset(); - offset += completions[result].1.len() - (span.end - span.start); - - // TODO improve the support for multiline replace - present_buffer.replace(span.start..span.end, &completions[result].1); - present_buffer.set_insertion_point(offset); - } - } - } -} diff --git a/src/main.rs b/src/main.rs index b7de4ca86..4d347ec07 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,8 +6,6 @@ mod reedline_config; mod repl; mod utils; -// mod fuzzy_completion; - #[cfg(test)] mod tests; diff --git a/src/reedline_config.rs b/src/reedline_config.rs index 85dba6409..cb8efea25 100644 --- a/src/reedline_config.rs +++ b/src/reedline_config.rs @@ -2,7 +2,8 @@ use crossterm::event::{KeyCode, KeyModifiers}; use nu_color_config::lookup_ansi_color_style; use nu_protocol::{Config, EventType, ParsedKeybinding, ShellError}; use reedline::{ - default_emacs_keybindings, ContextMenuInput, EditCommand, Keybindings, ReedlineEvent, + default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings, + ContextMenuInput, EditCommand, Keybindings, ReedlineEvent, }; // This creates an input object for the context menu based on the dictionary @@ -57,48 +58,117 @@ pub(crate) fn create_menu_input(config: &Config) -> ContextMenuInput { input } -pub(crate) fn create_keybindings( - parsed_keybindings: &[ParsedKeybinding], -) -> Result { - let mut keybindings = default_emacs_keybindings(); - - // temporal keybinding with multiple events - keybindings.add_binding( - KeyModifiers::SHIFT, - KeyCode::BackTab, - ReedlineEvent::Multiple(vec![ - ReedlineEvent::Edit(vec![EditCommand::InsertChar('p')]), - ReedlineEvent::Enter, - ]), - ); - - for keybinding in parsed_keybindings { - let modifier = match keybinding.modifier.as_str() { - "CONTROL" => KeyModifiers::CONTROL, - "SHIFT" => KeyModifiers::CONTROL, - _ => unimplemented!(), - }; - - let keycode = match keybinding.keycode.as_str() { - c if c.starts_with("Char_") => { - let char = c.replace("Char_", ""); - let char = char.chars().next().expect("correct"); - KeyCode::Char(char) - } - "down" => KeyCode::Down, - _ => unimplemented!(), - }; - - let event = match &keybinding.event { - EventType::Single(name) => match name.as_str() { - "Complete" => ReedlineEvent::Complete, - "ContextMenu" => ReedlineEvent::ContextMenu, - _ => unimplemented!(), - }, - }; - - keybindings.add_binding(modifier, keycode, event); - } - - Ok(keybindings) +pub enum KeybindingsMode { + Emacs(Keybindings), + Vi { + insert_keybindings: Keybindings, + normal_keybindings: Keybindings, + }, +} + +pub(crate) fn create_keybindings(config: &Config) -> Result { + let parsed_keybindings = &config.keybindings; + match config.edit_mode.as_str() { + "emacs" => { + let mut keybindings = default_emacs_keybindings(); + // temporal keybinding with multiple events + keybindings.add_binding( + KeyModifiers::SHIFT, + KeyCode::BackTab, + ReedlineEvent::Multiple(vec![ + ReedlineEvent::Edit(vec![EditCommand::InsertChar('p')]), + ReedlineEvent::Enter, + ]), + ); + + for parsed_keybinding in parsed_keybindings { + if parsed_keybinding.mode.item.as_str() == "emacs" { + add_keybinding(&mut keybindings, parsed_keybinding)? + } + } + + Ok(KeybindingsMode::Emacs(keybindings)) + } + _ => { + let mut insert_keybindings = default_vi_insert_keybindings(); + let mut normal_keybindings = default_vi_normal_keybindings(); + + for parsed_keybinding in parsed_keybindings { + if parsed_keybinding.mode.item.as_str() == "vi_insert" { + add_keybinding(&mut insert_keybindings, parsed_keybinding)? + } else if parsed_keybinding.mode.item.as_str() == "vi_normal" { + add_keybinding(&mut normal_keybindings, parsed_keybinding)? + } + } + + Ok(KeybindingsMode::Vi { + insert_keybindings, + normal_keybindings, + }) + } + } +} + +fn add_keybinding( + keybindings: &mut Keybindings, + parsed_keybinding: &ParsedKeybinding, +) -> Result<(), ShellError> { + let modifier = match parsed_keybinding.modifier.item.as_str() { + "CONTROL" => KeyModifiers::CONTROL, + "SHIFT" => KeyModifiers::SHIFT, + "ALT" => KeyModifiers::ALT, + "NONE" => KeyModifiers::NONE, + "CONTROL | ALT" => KeyModifiers::CONTROL | KeyModifiers::ALT, + _ => { + return Err(ShellError::UnsupportedConfigValue( + "CONTROL, SHIFT, ALT or NONE".to_string(), + parsed_keybinding.modifier.item.clone(), + parsed_keybinding.modifier.span, + )) + } + }; + + let keycode = match parsed_keybinding.keycode.item.as_str() { + c if c.starts_with("Char_") => { + let char = c.replace("Char_", ""); + let char = char.chars().next().expect("correct"); + KeyCode::Char(char) + } + "down" => KeyCode::Down, + "up" => KeyCode::Up, + "left" => KeyCode::Left, + "right" => KeyCode::Right, + "Tab" => KeyCode::Tab, + "BackTab" => KeyCode::BackTab, + _ => { + return Err(ShellError::UnsupportedConfigValue( + "crossterm KeyCode".to_string(), + parsed_keybinding.keycode.item.clone(), + parsed_keybinding.keycode.span, + )) + } + }; + + let event = match &parsed_keybinding.event.item { + EventType::Single(name) => match name.as_str() { + "ActionHandler" => ReedlineEvent::ActionHandler, + "Complete" => ReedlineEvent::Complete, + "ContextMenu" => ReedlineEvent::ContextMenu, + "NextElement" => ReedlineEvent::NextElement, + "NextHistory" => ReedlineEvent::NextHistory, + "PreviousElement" => ReedlineEvent::PreviousElement, + "PreviousHistory" => ReedlineEvent::PreviousHistory, + _ => { + return Err(ShellError::UnsupportedConfigValue( + "crossterm EventType".to_string(), + name.clone(), + parsed_keybinding.event.span, + )) + } + }, + }; + + keybindings.add_binding(modifier, keycode, event); + + Ok(()) } diff --git a/src/repl.rs b/src/repl.rs index 18280065a..e505ab4f9 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -3,8 +3,11 @@ use std::sync::{ Arc, }; -use crate::utils::{eval_source, gather_parent_env_vars, report_error}; use crate::{config_files, prompt_update, reedline_config}; +use crate::{ + reedline_config::KeybindingsMode, + utils::{eval_source, gather_parent_env_vars, report_error}, +}; use log::trace; use miette::{IntoDiagnostic, Result}; use nu_cli::{NuCompleter, NuHighlighter, NuValidator, NushellPrompt}; @@ -84,9 +87,6 @@ pub(crate) fn evaluate(ctrlc: Arc, engine_state: &mut EngineState) - //Reset the ctrl-c handler ctrlc.store(false, Ordering::SeqCst); - let keybindings = reedline_config::create_keybindings(&config.keybindings)?; - let edit_mode = Box::new(Emacs::new(keybindings)); - let line_editor = Reedline::create() .into_diagnostic()? // .with_completion_action_handler(Box::new(fuzzy_completion::FuzzyCompletion { @@ -103,7 +103,6 @@ pub(crate) fn evaluate(ctrlc: Arc, engine_state: &mut EngineState) - .with_validator(Box::new(NuValidator { engine_state: engine_state.clone(), })) - .with_edit_mode(edit_mode) .with_ansi_colors(config.use_ansi_coloring) .with_menu_completer( Box::new(NuCompleter::new(engine_state.clone())), @@ -139,13 +138,19 @@ pub(crate) fn evaluate(ctrlc: Arc, engine_state: &mut EngineState) - line_editor }; - // The line editor default mode is emacs mode. For the moment we only - // need to check for vi mode - let mut line_editor = if config.edit_mode == "vi" { - let edit_mode = Box::new(Vi::default()); - line_editor.with_edit_mode(edit_mode) - } else { - line_editor + // Changing the line editor based on the found keybindings + let mut line_editor = match reedline_config::create_keybindings(&config)? { + KeybindingsMode::Emacs(keybindings) => { + let edit_mode = Box::new(Emacs::new(keybindings)); + line_editor.with_edit_mode(edit_mode) + } + KeybindingsMode::Vi { + insert_keybindings, + normal_keybindings, + } => { + let edit_mode = Box::new(Vi::new(insert_keybindings, normal_keybindings)); + line_editor.with_edit_mode(edit_mode) + } }; let prompt = prompt_update::update_prompt(&config, engine_state, &stack, &mut nu_prompt);