diff --git a/crates/nu-command/src/conversions/into/string.rs b/crates/nu-command/src/conversions/into/string.rs index 0f3a664d3a..22661c0876 100644 --- a/crates/nu-command/src/conversions/into/string.rs +++ b/crates/nu-command/src/conversions/into/string.rs @@ -220,7 +220,7 @@ pub fn action( }, Value::Filesize { val: _, .. } => Value::String { - val: input.clone().into_string(", ", config), + val: input.into_string(", ", config), span, }, Value::Nothing { .. } => Value::String { diff --git a/crates/nu-command/src/formats/to/md.rs b/crates/nu-command/src/formats/to/md.rs index 9f5eca2e25..c12ae90563 100644 --- a/crates/nu-command/src/formats/to/md.rs +++ b/crates/nu-command/src/formats/to/md.rs @@ -207,7 +207,7 @@ pub fn group_by(values: PipelineData, head: Span, config: &Config) -> (PipelineD .or_insert_with(|| vec![val.clone()]); } else { lists - .entry(val.clone().into_string(",", config)) + .entry(val.into_string(",", config)) .and_modify(|v: &mut Vec| v.push(val.clone())) .or_insert_with(|| vec![val.clone()]); } diff --git a/crates/nu-protocol/src/config.rs b/crates/nu-protocol/src/config.rs index 5109de43e8..07be5acf07 100644 --- a/crates/nu-protocol/src/config.rs +++ b/crates/nu-protocol/src/config.rs @@ -1,4 +1,4 @@ -use crate::{BlockId, ShellError, Span, Spanned, Value}; +use crate::{BlockId, ShellError, Span, Value}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -41,55 +41,10 @@ impl EnvConversion { /// Definition of a parsed keybinding from the config object #[derive(Serialize, Deserialize, Clone, Debug)] pub struct ParsedKeybinding { - pub modifier: Spanned, - pub keycode: Spanned, - pub event: Spanned, - pub mode: Spanned, -} - -impl Default for ParsedKeybinding { - fn default() -> Self { - Self { - 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 }, - }, - } - } -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub enum EventType { - Single(String), -} - -#[derive(Serialize, Deserialize, Clone, Debug)] -pub enum EventMode { - Emacs, - ViNormal, - ViInsert, -} - -impl EventMode { - pub fn as_str(&self) -> &'static str { - match self { - EventMode::Emacs => "emacs", - EventMode::ViNormal => "vi_normal", - EventMode::ViInsert => "vi_insert", - } - } + pub modifier: Value, + pub keycode: Value, + pub event: Value, + pub mode: Value, } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -265,10 +220,7 @@ fn color_value_string( let val: String = inner_cols .iter() .zip(inner_vals) - .map(|(x, y)| { - let clony = y.clone(); - format!("{}: \"{}\" ", x, clony.into_string(", ", config)) - }) + .map(|(x, y)| format!("{}: \"{}\" ", x, y.into_string(", ", config))) .collect(); // now insert the braces at the front and the back to fake the json string @@ -281,107 +233,19 @@ fn color_value_string( // Parses the config object to extract the strings that will compose a keybinding for reedline fn create_keybindings(value: &Value, config: &Config) -> Result, ShellError> { match value { - Value::Record { cols, vals, .. } => { - let mut keybinding = ParsedKeybinding::default(); + Value::Record { cols, vals, span } => { + // Finding the modifier value in the record + let modifier = extract_value("modifier", cols, vals, span)?; + let keycode = extract_value("keycode", cols, vals, span)?; + let mode = extract_value("mode", cols, vals, span)?; + let event = extract_value("event", cols, vals, span)?; - for (col, val) in cols.iter().zip(vals.iter()) { - match col.as_str() { - "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" => 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(), - e.to_string(), - val.span()?, - )) - } - }; - } - "event" => match val { - Value::Record { - cols: event_cols, - vals: event_vals, - span: event_span, - } => { - let event_type_idx = event_cols - .iter() - .position(|key| key == "type") - .ok_or_else(|| { - ShellError::MissingConfigValue("type".to_string(), *event_span) - })?; - - let event_idx = event_cols - .iter() - .position(|key| key == "event") - .ok_or_else(|| { - ShellError::MissingConfigValue("event".to_string(), *event_span) - })?; - - let event_type = - event_vals[event_type_idx].clone().into_string("", config); - - // Extracting the event type information from the record based on the type - match event_type.as_str() { - "single" => { - let event_value = - event_vals[event_idx].clone().into_string("", config); - - keybinding.event = Spanned { - item: EventType::Single(event_value), - span: *event_span, - } - } - e => { - return Err(ShellError::UnsupportedConfigValue( - "single".to_string(), - e.to_string(), - *event_span, - )) - } - }; - } - e => { - return Err(ShellError::UnsupportedConfigValue( - "record type".to_string(), - format!("{:?}", e.get_type()), - e.span()?, - )) - } - }, - "name" => {} // don't need to store name - e => { - return Err(ShellError::UnsupportedConfigValue( - "name, mode, modifier, keycode or event".to_string(), - e.to_string(), - val.span()?, - )) - } - } - } + let keybinding = ParsedKeybinding { + modifier: modifier.clone(), + keycode: keycode.clone(), + mode: mode.clone(), + event: event.clone(), + }; Ok(vec![keybinding]) } @@ -401,3 +265,15 @@ fn create_keybindings(value: &Value, config: &Config) -> Result Ok(Vec::new()), } } + +pub fn extract_value<'record>( + name: &str, + cols: &'record [String], + vals: &'record [Value], + span: &Span, +) -> Result<&'record Value, ShellError> { + cols.iter() + .position(|col| col.as_str() == name) + .and_then(|index| vals.get(index)) + .ok_or_else(|| ShellError::MissingConfigValue(name.to_string(), *span)) +} diff --git a/crates/nu-protocol/src/value/mod.rs b/crates/nu-protocol/src/value/mod.rs index c449c39b9e..660c3c3ed2 100644 --- a/crates/nu-protocol/src/value/mod.rs +++ b/crates/nu-protocol/src/value/mod.rs @@ -373,14 +373,14 @@ impl Value { } /// Convert Value into string. Note that Streams will be consumed. - pub fn into_string(self, separator: &str, config: &Config) -> String { + pub fn into_string(&self, separator: &str, config: &Config) -> String { match self { Value::Bool { val, .. } => val.to_string(), Value::Int { val, .. } => val.to_string(), Value::Float { val, .. } => val.to_string(), - Value::Filesize { val, .. } => format_filesize(val, config), - Value::Duration { val, .. } => format_duration(val), - Value::Date { val, .. } => HumanTime::from(val).to_string(), + Value::Filesize { val, .. } => format_filesize(*val, config), + Value::Duration { val, .. } => format_duration(*val), + Value::Date { val, .. } => HumanTime::from(*val).to_string(), Value::Range { val, .. } => { format!( "{}..{}", @@ -388,10 +388,10 @@ impl Value { val.to.into_string(", ", config) ) } - Value::String { val, .. } => val, + Value::String { val, .. } => val.clone(), Value::List { vals: val, .. } => format!( "[{}]", - val.into_iter() + val.iter() .map(|x| x.into_string(", ", config)) .collect::>() .join(separator) @@ -400,7 +400,7 @@ impl Value { "{{{}}}", cols.iter() .zip(vals.iter()) - .map(|(x, y)| format!("{}: {}", x, y.clone().into_string(", ", config))) + .map(|(x, y)| format!("{}: {}", x, y.into_string(", ", config))) .collect::>() .join(separator) ), @@ -425,8 +425,8 @@ impl Value { Value::Range { val, .. } => { format!( "{}..{}", - val.from.clone().into_string(", ", config), - val.to.clone().into_string(", ", config) + val.from.into_string(", ", config), + val.to.into_string(", ", config) ) } Value::String { val, .. } => val.to_string(), @@ -457,18 +457,18 @@ impl Value { } /// Convert Value into a debug string - pub fn debug_value(self) -> String { + pub fn debug_value(&self) -> String { format!("{:#?}", self) } /// Convert Value into string. Note that Streams will be consumed. - pub fn debug_string(self, separator: &str, config: &Config) -> String { + pub fn debug_string(&self, separator: &str, config: &Config) -> String { match self { Value::Bool { val, .. } => val.to_string(), Value::Int { val, .. } => val.to_string(), Value::Float { val, .. } => val.to_string(), - Value::Filesize { val, .. } => format_filesize(val, config), - Value::Duration { val, .. } => format_duration(val), + Value::Filesize { val, .. } => format_filesize(*val, config), + Value::Duration { val, .. } => format_duration(*val), Value::Date { val, .. } => format!("{:?}", val), Value::Range { val, .. } => { format!( @@ -477,10 +477,10 @@ impl Value { val.to.into_string(", ", config) ) } - Value::String { val, .. } => val, + Value::String { val, .. } => val.clone(), Value::List { vals: val, .. } => format!( "[{}]", - val.into_iter() + val.iter() .map(|x| x.into_string(", ", config)) .collect::>() .join(separator) @@ -489,7 +489,7 @@ impl Value { "{{{}}}", cols.iter() .zip(vals.iter()) - .map(|(x, y)| format!("{}: {}", x, y.clone().into_string(", ", config))) + .map(|(x, y)| format!("{}: {}", x, y.into_string(", ", config))) .collect::>() .join(separator) ), @@ -503,7 +503,7 @@ impl Value { } /// Check if the content is empty - pub fn is_empty(self) -> bool { + pub fn is_empty(&self) -> bool { match self { Value::String { val, .. } => val.is_empty(), Value::List { vals, .. } => { diff --git a/src/reedline_config.rs b/src/reedline_config.rs index cb8efea252..d42cd67b80 100644 --- a/src/reedline_config.rs +++ b/src/reedline_config.rs @@ -1,6 +1,6 @@ use crossterm::event::{KeyCode, KeyModifiers}; use nu_color_config::lookup_ansi_color_style; -use nu_protocol::{Config, EventType, ParsedKeybinding, ShellError}; +use nu_protocol::{extract_value, Config, ParsedKeybinding, ShellError, Span, Value}; use reedline::{ default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings, ContextMenuInput, EditCommand, Keybindings, ReedlineEvent, @@ -82,8 +82,8 @@ pub(crate) fn create_keybindings(config: &Config) -> Result Result Result Result<(), ShellError> { - let modifier = match parsed_keybinding.modifier.item.as_str() { + let modifier = match keybinding.modifier.into_string("", config).as_str() { "CONTROL" => KeyModifiers::CONTROL, "SHIFT" => KeyModifiers::SHIFT, "ALT" => KeyModifiers::ALT, "NONE" => KeyModifiers::NONE, "CONTROL | ALT" => KeyModifiers::CONTROL | KeyModifiers::ALT, + "CONTROL | ALT | SHIFT" => KeyModifiers::CONTROL | KeyModifiers::ALT | KeyModifiers::SHIFT, _ => { return Err(ShellError::UnsupportedConfigValue( + keybinding.modifier.into_abbreviated_string(config), "CONTROL, SHIFT, ALT or NONE".to_string(), - parsed_keybinding.modifier.item.clone(), - parsed_keybinding.modifier.span, + keybinding.modifier.span()?, )) } }; - let keycode = match parsed_keybinding.keycode.item.as_str() { + let keycode = match keybinding.keycode.into_string("", config).as_str() { c if c.starts_with("Char_") => { let char = c.replace("Char_", ""); - let char = char.chars().next().expect("correct"); + let char = char.chars().next().ok_or({ + ShellError::UnsupportedConfigValue( + c.to_string(), + "Char_ plus char".to_string(), + keybinding.keycode.span()?, + ) + })?; KeyCode::Char(char) } "down" => KeyCode::Down, @@ -142,33 +150,202 @@ fn add_keybinding( "BackTab" => KeyCode::BackTab, _ => { return Err(ShellError::UnsupportedConfigValue( + keybinding.keycode.into_abbreviated_string(config), "crossterm KeyCode".to_string(), - parsed_keybinding.keycode.item.clone(), - parsed_keybinding.keycode.span, + 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, - )) - } - }, - }; + let event = parse_event(keybinding.event.clone(), config)?; keybindings.add_binding(modifier, keycode, event); Ok(()) } + +fn parse_event(value: Value, config: &Config) -> Result { + match value { + Value::Record { cols, vals, span } => { + let event = match extract_value("send", &cols, &vals, &span) { + Ok(event) => match event.into_string("", config).as_str() { + "ActionHandler" => ReedlineEvent::ActionHandler, + "ClearScreen" => ReedlineEvent::ClearScreen, + "ContextMenu" => ReedlineEvent::ContextMenu, + "Complete" => ReedlineEvent::Complete, + "Enter" => ReedlineEvent::Enter, + "Esc" => ReedlineEvent::Esc, + "Up" => ReedlineEvent::Up, + "Down" => ReedlineEvent::Down, + "Right" => ReedlineEvent::Right, + "Left" => ReedlineEvent::Left, + "NextElement" => ReedlineEvent::NextElement, + "NextHistory" => ReedlineEvent::NextHistory, + "PreviousElement" => ReedlineEvent::PreviousElement, + "PreviousHistory" => ReedlineEvent::PreviousHistory, + "SearchHistory" => ReedlineEvent::SearchHistory, + "Repaint" => ReedlineEvent::Repaint, + "Edit" => { + let edit = extract_value("edit", &cols, &vals, &span)?; + let edit = parse_edit(edit, config)?; + + ReedlineEvent::Edit(vec![edit]) + } + v => { + return Err(ShellError::UnsupportedConfigValue( + v.to_string(), + "Reedline event".to_string(), + span, + )) + } + }, + Err(_) => { + let edit = extract_value("edit", &cols, &vals, &span); + let edit = match edit { + Ok(edit_value) => parse_edit(edit_value, config)?, + Err(_) => { + return Err(ShellError::MissingConfigValue( + "send or edit".to_string(), + span, + )) + } + }; + + ReedlineEvent::Edit(vec![edit]) + } + }; + + Ok(event) + } + Value::List { vals, .. } => { + let events = vals + .into_iter() + .map(|value| parse_event(value, config)) + .collect::, ShellError>>()?; + + Ok(ReedlineEvent::Multiple(events)) + } + v => Err(ShellError::UnsupportedConfigValue( + v.into_abbreviated_string(config), + "record or list of records".to_string(), + v.span()?, + )), + } +} + +fn parse_edit(edit: &Value, config: &Config) -> Result { + let edit = match edit { + Value::Record { + cols: edit_cols, + vals: edit_vals, + span: edit_span, + } => { + let cmd = extract_value("cmd", edit_cols, edit_vals, edit_span)?; + + match cmd.into_string("", config).as_str() { + "MoveToStart" => EditCommand::MoveToStart, + "MoveToLineStart" => EditCommand::MoveToLineStart, + "MoveToEnd" => EditCommand::MoveToEnd, + "MoveToLineEnd" => EditCommand::MoveToLineEnd, + "MoveLeft" => EditCommand::MoveLeft, + "MoveRight" => EditCommand::MoveRight, + "MoveWordLeft" => EditCommand::MoveWordLeft, + "MoveWordRight" => EditCommand::MoveWordRight, + "InsertChar" => { + let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; + EditCommand::InsertChar(char) + } + "InsertString" => { + let value = extract_value("value", edit_cols, edit_vals, edit_span)?; + EditCommand::InsertString(value.into_string("", config)) + } + "Backspace" => EditCommand::Backspace, + "Delete" => EditCommand::Delete, + "BackspaceWord" => EditCommand::BackspaceWord, + "DeleteWord" => EditCommand::DeleteWord, + "Clear" => EditCommand::Clear, + "ClearToLineEnd" => EditCommand::ClearToLineEnd, + "CutCurrentLine" => EditCommand::CutCurrentLine, + "CutFromStart" => EditCommand::CutFromStart, + "CutFromLineStart" => EditCommand::CutFromLineStart, + "CutToEnd" => EditCommand::CutToEnd, + "CutToLineEnd" => EditCommand::CutToLineEnd, + "CutWordLeft" => EditCommand::CutWordLeft, + "CutWordRight" => EditCommand::CutWordRight, + "PasteCutBufferBefore" => EditCommand::PasteCutBufferBefore, + "PasteCutBufferAfter" => EditCommand::PasteCutBufferAfter, + "UppercaseWord" => EditCommand::UppercaseWord, + "LowercaseWord" => EditCommand::LowercaseWord, + "CapitalizeChar" => EditCommand::CapitalizeChar, + "SwapWords" => EditCommand::SwapWords, + "SwapGraphemes" => EditCommand::SwapGraphemes, + "Undo" => EditCommand::Undo, + "Redo" => EditCommand::Redo, + "CutRightUntil" => { + let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; + EditCommand::CutRightUntil(char) + } + "CutRightBefore" => { + let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; + EditCommand::CutRightBefore(char) + } + "MoveRightUntil" => { + let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; + EditCommand::MoveRightUntil(char) + } + "MoveRightBefore" => { + let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; + EditCommand::MoveRightBefore(char) + } + "CutLeftUntil" => { + let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; + EditCommand::CutLeftUntil(char) + } + "CutLeftBefore" => { + let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; + EditCommand::CutLeftBefore(char) + } + "MoveLeftUntil" => { + let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; + EditCommand::MoveLeftUntil(char) + } + "MoveLeftBefore" => { + let char = extract_char("value", edit_cols, edit_vals, config, edit_span)?; + EditCommand::MoveLeftBefore(char) + } + e => { + return Err(ShellError::UnsupportedConfigValue( + e.to_string(), + "reedline EditCommand".to_string(), + edit.span()?, + )) + } + } + } + e => { + return Err(ShellError::UnsupportedConfigValue( + e.into_abbreviated_string(config), + "record with EditCommand".to_string(), + edit.span()?, + )) + } + }; + + Ok(edit) +} + +fn extract_char<'record>( + name: &str, + cols: &'record [String], + vals: &'record [Value], + config: &Config, + span: &Span, +) -> Result { + let value = extract_value(name, cols, vals, span)?; + + value + .into_string("", config) + .chars() + .next() + .ok_or_else(|| ShellError::MissingConfigValue("char to insert".to_string(), *span)) +} diff --git a/src/repl.rs b/src/repl.rs index e505ab4f93..9b8772cef8 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -139,17 +139,24 @@ pub(crate) fn evaluate(ctrlc: Arc, engine_state: &mut EngineState) - }; // 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 mut line_editor = match reedline_config::create_keybindings(&config) { + Ok(keybindings) => match keybindings { + 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) + } + }, + Err(e) => { + let working_set = StateWorkingSet::new(engine_state); + report_error(&working_set, &e); + line_editor } };