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]]
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"

View File

@ -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<String>,
pub keycode: Spanned<String>,
pub event: Spanned<EventType>,
pub mode: Spanned<EventMode>,
}
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<Vec<ParsedKeybin
for (col, val) in cols.iter().zip(vals.iter()) {
match col.as_str() {
"modifier" => 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<Vec<ParsedKeybin
let event_value =
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 => {
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 utils;
// mod fuzzy_completion;
#[cfg(test)]
mod tests;

View File

@ -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,11 +58,19 @@ pub(crate) fn create_menu_input(config: &Config) -> ContextMenuInput {
input
}
pub(crate) fn create_keybindings(
parsed_keybindings: &[ParsedKeybinding],
) -> Result<Keybindings, ShellError> {
let mut keybindings = default_emacs_keybindings();
pub enum KeybindingsMode {
Emacs(Keybindings),
Vi {
insert_keybindings: Keybindings,
normal_keybindings: Keybindings,
},
}
pub(crate) fn create_keybindings(config: &Config) -> Result<KeybindingsMode, ShellError> {
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,
@ -72,33 +81,94 @@ pub(crate) fn create_keybindings(
]),
);
for keybinding in parsed_keybindings {
let modifier = match keybinding.modifier.as_str() {
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::CONTROL,
_ => unimplemented!(),
"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 keybinding.keycode.as_str() {
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,
_ => unimplemented!(),
"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 &keybinding.event {
let event = match &parsed_keybinding.event.item {
EventType::Single(name) => match name.as_str() {
"ActionHandler" => ReedlineEvent::ActionHandler,
"Complete" => ReedlineEvent::Complete,
"ContextMenu" => ReedlineEvent::ContextMenu,
_ => unimplemented!(),
"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(keybindings)
Ok(())
}

View File

@ -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<AtomicBool>, 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<AtomicBool>, 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<AtomicBool>, 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());
// 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)
} 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);