simple event keybinding (#773)

This commit is contained in:
Fernando Herrera 2022-01-18 19:32:45 +00:00 committed by GitHub
parent 6d554398a7
commit ff9d88887b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 196 additions and 134 deletions

6
Cargo.lock generated
View File

@ -2851,7 +2851,7 @@ dependencies = [
[[package]] [[package]]
name = "reedline" name = "reedline"
version = "0.2.0" 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 = [ dependencies = [
"chrono", "chrono",
"crossterm", "crossterm",
@ -4020,9 +4020,9 @@ dependencies = [
[[package]] [[package]]
name = "zeroize" name = "zeroize"
version = "1.5.0" version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc222aec311c323c717f56060324f32b82da1ce1dd81d9a09aa6a9030bfe08db" checksum = "d68d9dcec5f9b43a30d38c49f91dfedfaac384cb8f085faca366c26207dd1619"
[[package]] [[package]]
name = "zip" name = "zip"

View File

@ -1,4 +1,4 @@
use crate::{BlockId, ShellError, Span, Value}; use crate::{BlockId, ShellError, Span, Spanned, Value};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
@ -41,19 +41,31 @@ impl EnvConversion {
/// Definition of a parsed keybinding from the config object /// Definition of a parsed keybinding from the config object
#[derive(Serialize, Deserialize, Clone, Debug)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub struct ParsedKeybinding { pub struct ParsedKeybinding {
pub modifier: String, pub modifier: Spanned<String>,
pub keycode: String, pub keycode: Spanned<String>,
pub event: EventType, pub event: Spanned<EventType>,
pub mode: EventMode, pub mode: Spanned<EventMode>,
} }
impl Default for ParsedKeybinding { impl Default for ParsedKeybinding {
fn default() -> Self { fn default() -> Self {
Self { Self {
modifier: "".to_string(), modifier: Spanned {
keycode: "".to_string(), item: "".to_string(),
event: EventType::Single("".to_string()), span: Span { start: 0, end: 0 },
mode: EventMode::Emacs, },
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)] #[derive(Serialize, Deserialize, Clone, Debug)]
pub enum EventMode { pub enum EventMode {
Emacs, 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)] #[derive(Serialize, Deserialize, Clone, Debug)]
@ -263,12 +286,32 @@ fn create_keybindings(value: &Value, config: &Config) -> Result<Vec<ParsedKeybin
for (col, val) in cols.iter().zip(vals.iter()) { for (col, val) in cols.iter().zip(vals.iter()) {
match col.as_str() { match col.as_str() {
"modifier" => keybinding.modifier = val.clone().into_string("", config), "modifier" => {
"keycode" => keybinding.keycode = val.clone().into_string("", config), 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" => { "mode" => {
keybinding.mode = match val.clone().into_string("", config).as_str() { keybinding.mode = match val.clone().into_string("", config).as_str() {
"emacs" => EventMode::Emacs, "emacs" => Spanned {
"vi" => EventMode::Vi, 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 => { e => {
return Err(ShellError::UnsupportedConfigValue( return Err(ShellError::UnsupportedConfigValue(
"emacs or vi".to_string(), "emacs or vi".to_string(),
@ -307,7 +350,10 @@ fn create_keybindings(value: &Value, config: &Config) -> Result<Vec<ParsedKeybin
let event_value = let event_value =
event_vals[event_idx].clone().into_string("", config); event_vals[event_idx].clone().into_string("", config);
keybinding.event = EventType::Single(event_value) keybinding.event = Spanned {
item: EventType::Single(event_value),
span: *event_span,
}
} }
e => { e => {
return Err(ShellError::UnsupportedConfigValue( return Err(ShellError::UnsupportedConfigValue(

View File

@ -1,57 +0,0 @@
use dialoguer::{
console::{Style, Term},
theme::ColorfulTheme,
Select,
};
use reedline::{Completer, CompletionActionHandler, LineBuffer};
pub(crate) struct FuzzyCompletion {
pub completer: Box<dyn Completer>,
}
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);
}
}
}
}

View File

@ -6,8 +6,6 @@ mod reedline_config;
mod repl; mod repl;
mod utils; mod utils;
// mod fuzzy_completion;
#[cfg(test)] #[cfg(test)]
mod tests; mod tests;

View File

@ -2,7 +2,8 @@ use crossterm::event::{KeyCode, KeyModifiers};
use nu_color_config::lookup_ansi_color_style; use nu_color_config::lookup_ansi_color_style;
use nu_protocol::{Config, EventType, ParsedKeybinding, ShellError}; use nu_protocol::{Config, EventType, ParsedKeybinding, ShellError};
use reedline::{ 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 // 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 input
} }
pub(crate) fn create_keybindings( pub enum KeybindingsMode {
parsed_keybindings: &[ParsedKeybinding], Emacs(Keybindings),
) -> Result<Keybindings, ShellError> { Vi {
let mut keybindings = default_emacs_keybindings(); insert_keybindings: Keybindings,
normal_keybindings: Keybindings,
// temporal keybinding with multiple events },
keybindings.add_binding( }
KeyModifiers::SHIFT,
KeyCode::BackTab, pub(crate) fn create_keybindings(config: &Config) -> Result<KeybindingsMode, ShellError> {
ReedlineEvent::Multiple(vec![ let parsed_keybindings = &config.keybindings;
ReedlineEvent::Edit(vec![EditCommand::InsertChar('p')]), match config.edit_mode.as_str() {
ReedlineEvent::Enter, "emacs" => {
]), let mut keybindings = default_emacs_keybindings();
); // temporal keybinding with multiple events
keybindings.add_binding(
for keybinding in parsed_keybindings { KeyModifiers::SHIFT,
let modifier = match keybinding.modifier.as_str() { KeyCode::BackTab,
"CONTROL" => KeyModifiers::CONTROL, ReedlineEvent::Multiple(vec![
"SHIFT" => KeyModifiers::CONTROL, ReedlineEvent::Edit(vec![EditCommand::InsertChar('p')]),
_ => unimplemented!(), ReedlineEvent::Enter,
}; ]),
);
let keycode = match keybinding.keycode.as_str() {
c if c.starts_with("Char_") => { for parsed_keybinding in parsed_keybindings {
let char = c.replace("Char_", ""); if parsed_keybinding.mode.item.as_str() == "emacs" {
let char = char.chars().next().expect("correct"); add_keybinding(&mut keybindings, parsed_keybinding)?
KeyCode::Char(char) }
} }
"down" => KeyCode::Down,
_ => unimplemented!(), Ok(KeybindingsMode::Emacs(keybindings))
}; }
_ => {
let event = match &keybinding.event { let mut insert_keybindings = default_vi_insert_keybindings();
EventType::Single(name) => match name.as_str() { let mut normal_keybindings = default_vi_normal_keybindings();
"Complete" => ReedlineEvent::Complete,
"ContextMenu" => ReedlineEvent::ContextMenu, for parsed_keybinding in parsed_keybindings {
_ => unimplemented!(), 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)?
keybindings.add_binding(modifier, keycode, event); }
} }
Ok(keybindings) 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(())
} }

View File

@ -3,8 +3,11 @@ use std::sync::{
Arc, Arc,
}; };
use crate::utils::{eval_source, gather_parent_env_vars, report_error};
use crate::{config_files, prompt_update, reedline_config}; 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 log::trace;
use miette::{IntoDiagnostic, Result}; use miette::{IntoDiagnostic, Result};
use nu_cli::{NuCompleter, NuHighlighter, NuValidator, NushellPrompt}; use nu_cli::{NuCompleter, NuHighlighter, NuValidator, NushellPrompt};
@ -84,9 +87,6 @@ pub(crate) fn evaluate(ctrlc: Arc<AtomicBool>, engine_state: &mut EngineState) -
//Reset the ctrl-c handler //Reset the ctrl-c handler
ctrlc.store(false, Ordering::SeqCst); 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() let line_editor = Reedline::create()
.into_diagnostic()? .into_diagnostic()?
// .with_completion_action_handler(Box::new(fuzzy_completion::FuzzyCompletion { // .with_completion_action_handler(Box::new(fuzzy_completion::FuzzyCompletion {
@ -103,7 +103,6 @@ pub(crate) fn evaluate(ctrlc: Arc<AtomicBool>, engine_state: &mut EngineState) -
.with_validator(Box::new(NuValidator { .with_validator(Box::new(NuValidator {
engine_state: engine_state.clone(), engine_state: engine_state.clone(),
})) }))
.with_edit_mode(edit_mode)
.with_ansi_colors(config.use_ansi_coloring) .with_ansi_colors(config.use_ansi_coloring)
.with_menu_completer( .with_menu_completer(
Box::new(NuCompleter::new(engine_state.clone())), Box::new(NuCompleter::new(engine_state.clone())),
@ -139,13 +138,19 @@ pub(crate) fn evaluate(ctrlc: Arc<AtomicBool>, engine_state: &mut EngineState) -
line_editor line_editor
}; };
// The line editor default mode is emacs mode. For the moment we only // Changing the line editor based on the found keybindings
// need to check for vi mode let mut line_editor = match reedline_config::create_keybindings(&config)? {
let mut line_editor = if config.edit_mode == "vi" { KeybindingsMode::Emacs(keybindings) => {
let edit_mode = Box::new(Vi::default()); let edit_mode = Box::new(Emacs::new(keybindings));
line_editor.with_edit_mode(edit_mode) line_editor.with_edit_mode(edit_mode)
} else { }
line_editor 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); let prompt = prompt_update::update_prompt(&config, engine_state, &stack, &mut nu_prompt);