forked from extern/nushell
simple event keybinding (#773)
This commit is contained in:
parent
6d554398a7
commit
ff9d88887b
6
Cargo.lock
generated
6
Cargo.lock
generated
@ -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"
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,8 +6,6 @@ mod reedline_config;
|
||||
mod repl;
|
||||
mod utils;
|
||||
|
||||
// mod fuzzy_completion;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
|
@ -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<Keybindings, ShellError> {
|
||||
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<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,
|
||||
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(())
|
||||
}
|
||||
|
29
src/repl.rs
29
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<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());
|
||||
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);
|
||||
|
Loading…
Reference in New Issue
Block a user