mirror of
https://github.com/nushell/nushell.git
synced 2024-11-07 09:04:18 +01:00
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]]
|
[[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"
|
||||||
|
@ -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(
|
||||||
|
@ -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 repl;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
// mod fuzzy_completion;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
|
@ -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(())
|
||||||
}
|
}
|
||||||
|
29
src/repl.rs
29
src/repl.rs
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user