From 267ff4b0cfdc17c6c1d8f6f5d3c706f96dcb3207 Mon Sep 17 00:00:00 2001 From: Fernando Herrera Date: Thu, 27 Jan 2022 07:53:23 +0000 Subject: [PATCH] using menu trait (#861) --- Cargo.lock | 2 +- crates/nu-cli/src/prompt.rs | 42 +++++----- src/prompt_update.rs | 31 ++------ src/reedline_config.rs | 149 +++++++++++++++++++++++++----------- src/repl.rs | 25 +++--- 5 files changed, 142 insertions(+), 107 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 472fa919e5..6ddcd3082b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2863,7 +2863,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.2.0" -source = "git+https://github.com/nushell/reedline?branch=main#d7f42e5de4aeee9332dc8eaff07e68923401edcf" +source = "git+https://github.com/nushell/reedline?branch=main#c0ec7dc2fd4181c11065f7e19c59fed2ffc83653" dependencies = [ "chrono", "crossterm", diff --git a/crates/nu-cli/src/prompt.rs b/crates/nu-cli/src/prompt.rs index 7f77598c1f..890a93b83a 100644 --- a/crates/nu-cli/src/prompt.rs +++ b/crates/nu-cli/src/prompt.rs @@ -14,10 +14,8 @@ pub struct NushellPrompt { right_prompt_string: Option, default_prompt_indicator: String, default_vi_insert_prompt_indicator: String, - default_vi_visual_prompt_indicator: String, - default_menu_prompt_indicator: String, + default_vi_normal_prompt_indicator: String, default_multiline_indicator: String, - default_history_prompt_indicator: String, } impl Default for NushellPrompt { @@ -33,10 +31,8 @@ impl NushellPrompt { right_prompt_string: None, default_prompt_indicator: "〉".to_string(), default_vi_insert_prompt_indicator: ": ".to_string(), - default_vi_visual_prompt_indicator: "v ".to_string(), - default_menu_prompt_indicator: "| ".to_string(), + default_vi_normal_prompt_indicator: "〉".to_string(), default_multiline_indicator: "::: ".to_string(), - default_history_prompt_indicator: "? ".to_string(), } } @@ -56,8 +52,8 @@ impl NushellPrompt { self.default_vi_insert_prompt_indicator = prompt_vi_insert_string; } - pub fn update_prompt_vi_visual(&mut self, prompt_vi_visual_string: String) { - self.default_vi_visual_prompt_indicator = prompt_vi_visual_string; + pub fn update_prompt_vi_normal(&mut self, prompt_vi_normal_string: String) { + self.default_vi_normal_prompt_indicator = prompt_vi_normal_string; } pub fn update_prompt_multiline(&mut self, prompt_multiline_indicator_string: String) { @@ -71,20 +67,15 @@ impl NushellPrompt { prompt_indicator_string: String, prompt_multiline_indicator_string: String, prompt_vi: (String, String), - prompt_menus: (String, String), ) { - let (prompt_vi_insert_string, prompt_vi_visual_string) = prompt_vi; - let (prompt_indicator_menu, prompt_history_indicator_menu) = prompt_menus; + let (prompt_vi_insert_string, prompt_vi_normal_string) = prompt_vi; self.left_prompt_string = left_prompt_string; self.right_prompt_string = right_prompt_string; self.default_prompt_indicator = prompt_indicator_string; self.default_vi_insert_prompt_indicator = prompt_vi_insert_string; - self.default_vi_visual_prompt_indicator = prompt_vi_visual_string; + self.default_vi_normal_prompt_indicator = prompt_vi_normal_string; self.default_multiline_indicator = prompt_multiline_indicator_string; - - self.default_menu_prompt_indicator = prompt_indicator_menu; - self.default_history_prompt_indicator = prompt_history_indicator_menu; } fn default_wrapped_custom_string(&self, str: String) -> String { @@ -95,19 +86,27 @@ impl NushellPrompt { impl Prompt for NushellPrompt { fn render_prompt_left(&self) -> Cow { if let Some(prompt_string) = &self.left_prompt_string { - prompt_string.into() + prompt_string.replace("\n", "\r\n").into() } else { let default = DefaultPrompt::new(); - default.render_prompt_left().to_string().into() + default + .render_prompt_left() + .to_string() + .replace("\n", "\r\n") + .into() } } fn render_prompt_right(&self) -> Cow { if let Some(prompt_string) = &self.right_prompt_string { - prompt_string.into() + prompt_string.replace("\n", "\r\n").into() } else { let default = DefaultPrompt::new(); - default.render_prompt_right().to_string().into() + default + .render_prompt_right() + .to_string() + .replace("\n", "\r\n") + .into() } } @@ -116,13 +115,10 @@ impl Prompt for NushellPrompt { PromptEditMode::Default => self.default_prompt_indicator.as_str().into(), PromptEditMode::Emacs => self.default_prompt_indicator.as_str().into(), PromptEditMode::Vi(vi_mode) => match vi_mode { - PromptViMode::Normal => self.default_prompt_indicator.as_str().into(), + PromptViMode::Normal => self.default_vi_normal_prompt_indicator.as_str().into(), PromptViMode::Insert => self.default_vi_insert_prompt_indicator.as_str().into(), - PromptViMode::Visual => self.default_vi_visual_prompt_indicator.as_str().into(), }, PromptEditMode::Custom(str) => self.default_wrapped_custom_string(str).into(), - PromptEditMode::Menu => self.default_menu_prompt_indicator.as_str().into(), - PromptEditMode::HistoryMenu => self.default_history_prompt_indicator.as_str().into(), } } diff --git a/src/prompt_update.rs b/src/prompt_update.rs index 517d65a3b6..3bc212f6b7 100644 --- a/src/prompt_update.rs +++ b/src/prompt_update.rs @@ -12,16 +12,14 @@ pub(crate) const PROMPT_COMMAND: &str = "PROMPT_COMMAND"; pub(crate) const PROMPT_COMMAND_RIGHT: &str = "PROMPT_COMMAND_RIGHT"; pub(crate) const PROMPT_INDICATOR: &str = "PROMPT_INDICATOR"; pub(crate) const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT"; -pub(crate) const PROMPT_INDICATOR_VI_VISUAL: &str = "PROMPT_INDICATOR_VI_VISUAL"; -pub(crate) const PROMPT_INDICATOR_MENU: &str = "PROMPT_INDICATOR_MENU"; +pub(crate) const PROMPT_INDICATOR_VI_NORMAL: &str = "PROMPT_INDICATOR_VI_NORMAL"; pub(crate) const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR"; -pub(crate) const PROMPT_INDICATOR_HISTORY: &str = "PROMPT_INDICATOR_HISTORY"; pub(crate) fn get_prompt_indicators( config: &Config, engine_state: &EngineState, stack: &Stack, -) -> (String, String, String, String, String, String) { +) -> (String, String, String, String) { let prompt_indicator = match stack.get_env_var(engine_state, PROMPT_INDICATOR) { Some(pi) => pi.into_string("", config), None => "〉".to_string(), @@ -32,9 +30,9 @@ pub(crate) fn get_prompt_indicators( None => ": ".to_string(), }; - let prompt_vi_visual = match stack.get_env_var(engine_state, PROMPT_INDICATOR_VI_VISUAL) { + let prompt_vi_normal = match stack.get_env_var(engine_state, PROMPT_INDICATOR_VI_NORMAL) { Some(pviv) => pviv.into_string("", config), - None => "v ".to_string(), + None => "〉".to_string(), }; let prompt_multiline = match stack.get_env_var(engine_state, PROMPT_MULTILINE_INDICATOR) { @@ -42,23 +40,11 @@ pub(crate) fn get_prompt_indicators( None => "::: ".to_string(), }; - let prompt_menu = match stack.get_env_var(engine_state, PROMPT_INDICATOR_MENU) { - Some(pm) => pm.into_string("", config), - None => "| ".to_string(), - }; - - let prompt_history = match stack.get_env_var(engine_state, PROMPT_INDICATOR_HISTORY) { - Some(ph) => ph.into_string("", config), - None => "? ".to_string(), - }; - ( prompt_indicator, prompt_vi_insert, - prompt_vi_visual, + prompt_vi_normal, prompt_multiline, - prompt_menu, - prompt_history, ) } @@ -107,10 +93,8 @@ pub(crate) fn update_prompt<'prompt>( let ( prompt_indicator_string, prompt_vi_insert_string, - prompt_vi_visual_string, + prompt_vi_normal_string, prompt_multiline_string, - prompt_indicator_menu, - prompt_indicator_history, ) = get_prompt_indicators(config, engine_state, stack); let mut stack = stack.clone(); @@ -121,8 +105,7 @@ pub(crate) fn update_prompt<'prompt>( get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, &mut stack), prompt_indicator_string, prompt_multiline_string, - (prompt_vi_insert_string, prompt_vi_visual_string), - (prompt_indicator_menu, prompt_indicator_history), + (prompt_vi_insert_string, prompt_vi_normal_string), ); nu_prompt as &dyn Prompt diff --git a/src/reedline_config.rs b/src/reedline_config.rs index df3cdd77c9..555d6cd0b9 100644 --- a/src/reedline_config.rs +++ b/src/reedline_config.rs @@ -1,26 +1,34 @@ use crossterm::event::{KeyCode, KeyModifiers}; +use nu_cli::NuCompleter; use nu_color_config::lookup_ansi_color_style; -use nu_protocol::{extract_value, Config, ParsedKeybinding, ShellError, Span, Type, Value}; +use nu_protocol::{ + engine::EngineState, extract_value, Config, ParsedKeybinding, ShellError, Span, Type, Value, +}; use reedline::{ default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings, - ContextMenuInput, EditCommand, HistoryMenuInput, Keybindings, ReedlineEvent, + ContextMenu, EditCommand, HistoryMenu, Keybindings, Reedline, ReedlineEvent, }; // Creates an input object for the context menu based on the dictionary // stored in the config variable -pub(crate) fn create_menu_input(config: &Config) -> ContextMenuInput { - let mut input = ContextMenuInput::default(); +pub(crate) fn add_context_menu( + line_editor: Reedline, + engine_state: &EngineState, + config: &Config, +) -> Reedline { + let mut context_menu = ContextMenu::default(); + context_menu = context_menu.with_completer(Box::new(NuCompleter::new(engine_state.clone()))); - input = match config + context_menu = match config .menu_config .get("columns") .and_then(|value| value.as_integer().ok()) { - Some(value) => input.with_columns(value as u16), - None => input, + Some(value) => context_menu.with_columns(value as u16), + None => context_menu, }; - input = input.with_col_width( + context_menu = context_menu.with_column_width( config .menu_config .get("col_width") @@ -28,82 +36,133 @@ pub(crate) fn create_menu_input(config: &Config) -> ContextMenuInput { .map(|value| value as usize), ); - input = match config + context_menu = match config .menu_config .get("col_padding") .and_then(|value| value.as_integer().ok()) { - Some(value) => input.with_col_padding(value as usize), - None => input, + Some(value) => context_menu.with_column_padding(value as usize), + None => context_menu, }; - input = match config + context_menu = match config .menu_config .get("text_style") .and_then(|value| value.as_string().ok()) { - Some(value) => input.with_text_style(lookup_ansi_color_style(&value)), - None => input, + Some(value) => context_menu.with_text_style(lookup_ansi_color_style(&value)), + None => context_menu, }; - input = match config + context_menu = match config .menu_config .get("selected_text_style") .and_then(|value| value.as_string().ok()) { - Some(value) => input.with_selected_text_style(lookup_ansi_color_style(&value)), - None => input, + Some(value) => context_menu.with_selected_text_style(lookup_ansi_color_style(&value)), + None => context_menu, }; - input + context_menu = match config + .menu_config + .get("marker") + .and_then(|value| value.as_string().ok()) + { + Some(value) => context_menu.with_marker(value), + None => context_menu, + }; + + line_editor.with_menu(Box::new(context_menu)) } // Creates an input object for the history menu based on the dictionary // stored in the config variable -pub(crate) fn create_history_input(config: &Config) -> HistoryMenuInput { - let mut input = HistoryMenuInput::default(); +pub(crate) fn add_history_menu(line_editor: Reedline, config: &Config) -> Reedline { + let mut history_menu = HistoryMenu::default(); - input = match config + history_menu = match config .history_config .get("page_size") .and_then(|value| value.as_integer().ok()) { - Some(value) => input.with_page_size(value as usize), - None => input, + Some(value) => history_menu.with_page_size(value as usize), + None => history_menu, }; - input = match config + history_menu = match config .history_config .get("selector") .and_then(|value| value.as_string().ok()) { Some(value) => { let char = value.chars().next().unwrap_or(':'); - input.with_row_char(char) + history_menu.with_row_char(char) } - None => input, + None => history_menu, }; - input = match config + history_menu = match config .history_config .get("text_style") .and_then(|value| value.as_string().ok()) { - Some(value) => input.with_text_style(lookup_ansi_color_style(&value)), - None => input, + Some(value) => history_menu.with_text_style(lookup_ansi_color_style(&value)), + None => history_menu, }; - input = match config + history_menu = match config .history_config .get("selected_text_style") .and_then(|value| value.as_string().ok()) { - Some(value) => input.with_selected_text_style(lookup_ansi_color_style(&value)), - None => input, + Some(value) => history_menu.with_selected_text_style(lookup_ansi_color_style(&value)), + None => history_menu, }; - input + history_menu = match config + .history_config + .get("marker") + .and_then(|value| value.as_string().ok()) + { + Some(value) => history_menu.with_marker(value), + None => history_menu, + }; + + line_editor.with_menu(Box::new(history_menu)) } + +fn add_menu_keybindings(keybindings: &mut Keybindings) { + keybindings.add_binding( + KeyModifiers::CONTROL, + KeyCode::Char('i'), + ReedlineEvent::UntilFound(vec![ + ReedlineEvent::Menu("history_menu".to_string()), + ReedlineEvent::MenuPageNext, + ]), + ); + + keybindings.add_binding( + KeyModifiers::CONTROL | KeyModifiers::SHIFT, + KeyCode::Char('i'), + ReedlineEvent::MenuPagePrevious, + ); + + keybindings.add_binding( + KeyModifiers::NONE, + KeyCode::Tab, + ReedlineEvent::UntilFound(vec![ + ReedlineEvent::Menu("context_menu".to_string()), + ReedlineEvent::MenuNext, + ]), + ); + + keybindings.add_binding( + KeyModifiers::SHIFT, + KeyCode::BackTab, + ReedlineEvent::MenuPrevious, + ); +} + pub enum KeybindingsMode { Emacs(Keybindings), Vi { @@ -117,6 +176,8 @@ pub(crate) fn create_keybindings(config: &Config) -> Result { let mut keybindings = default_emacs_keybindings(); + add_menu_keybindings(&mut keybindings); + // temporal keybinding with multiple events keybindings.add_binding( KeyModifiers::SHIFT, @@ -139,6 +200,9 @@ pub(crate) fn create_keybindings(config: &Config) -> Result Result ReedlineEvent::NextHistory, "previoushistory" => ReedlineEvent::PreviousHistory, "repaint" => ReedlineEvent::Repaint, - "contextmenu" => ReedlineEvent::ContextMenu, "menudown" => ReedlineEvent::MenuDown, "menuup" => ReedlineEvent::MenuUp, "menuleft" => ReedlineEvent::MenuLeft, "menuright" => ReedlineEvent::MenuRight, "menunext" => ReedlineEvent::MenuNext, "menuprevious" => ReedlineEvent::MenuPrevious, - "historymenu" => ReedlineEvent::HistoryMenu, - "historymenunext" => ReedlineEvent::HistoryMenuNext, - "historymenuprevious" => ReedlineEvent::HistoryMenuPrevious, - "historypagenext" => ReedlineEvent::HistoryPageNext, - "historypageprevious" => ReedlineEvent::HistoryPagePrevious, - - // TODO: add ReedlineEvent::Mouse - // TODO: add ReedlineEvent::Resize - // TODO: add ReedlineEvent::Paste + "menupagenext" => ReedlineEvent::MenuPageNext, + "menupageprevious" => ReedlineEvent::MenuPagePrevious, + "menu" => { + let menu = extract_value("name", &cols, &vals, &span)?; + ReedlineEvent::Menu(menu.into_string("", config)) + } "edit" => { let edit = extract_value("edit", &cols, &vals, &span)?; let edit = parse_edit(edit, config)?; ReedlineEvent::Edit(vec![edit]) } + // TODO: add ReedlineEvent::Mouse + // TODO: add ReedlineEvent::Resize + // TODO: add ReedlineEvent::Paste v => { return Err(ShellError::UnsupportedConfigValue( "Reedline event".to_string(), diff --git a/src/repl.rs b/src/repl.rs index ed04b7a95f..e5337bed1b 100644 --- a/src/repl.rs +++ b/src/repl.rs @@ -1,5 +1,6 @@ use std::{sync::atomic::Ordering, time::Instant}; +use crate::reedline_config::{add_context_menu, add_history_menu}; use crate::{config_files, prompt_update, reedline_config}; use crate::{ reedline_config::KeybindingsMode, @@ -7,7 +8,7 @@ use crate::{ }; use log::trace; use miette::{IntoDiagnostic, Result}; -use nu_cli::{NuCompleter, NuHighlighter, NuValidator, NushellPrompt}; +use nu_cli::{NuHighlighter, NuValidator, NushellPrompt}; use nu_color_config::get_color_config; use nu_engine::convert_env_values; use nu_parser::lex; @@ -95,14 +96,8 @@ pub(crate) fn evaluate(engine_state: &mut EngineState) -> Result<()> { ctrlc.store(false, Ordering::SeqCst); } - let line_editor = Reedline::create() + let mut line_editor = Reedline::create() .into_diagnostic()? - // .with_completion_action_handler(Box::new(fuzzy_completion::FuzzyCompletion { - // completer: Box::new(NuCompleter::new(engine_state.clone())), - // })) - // .with_completion_action_handler(Box::new( - // ListCompletionHandler::default().with_completer(Box::new(completer)), - // )) .with_highlighter(Box::new(NuHighlighter { engine_state: engine_state.clone(), config: config.clone(), @@ -111,19 +106,17 @@ pub(crate) fn evaluate(engine_state: &mut EngineState) -> Result<()> { .with_validator(Box::new(NuValidator { engine_state: engine_state.clone(), })) - .with_ansi_colors(config.use_ansi_coloring) - .with_menu_completer( - Box::new(NuCompleter::new(engine_state.clone())), - reedline_config::create_menu_input(&config), - ) - .with_history_menu(reedline_config::create_history_input(&config)); + .with_ansi_colors(config.use_ansi_coloring); + + line_editor = add_context_menu(line_editor, engine_state, &config); + line_editor = add_history_menu(line_editor, &config); //FIXME: if config.use_ansi_coloring is false then we should // turn off the hinter but I don't see any way to do that yet. let color_hm = get_color_config(&config); - let line_editor = if let Some(history_path) = history_path.clone() { + line_editor = if let Some(history_path) = history_path.clone() { let history = std::fs::read_to_string(&history_path); if history.is_ok() { line_editor @@ -146,7 +139,7 @@ pub(crate) fn evaluate(engine_state: &mut EngineState) -> Result<()> { }; // Changing the line editor based on the found keybindings - let mut line_editor = match reedline_config::create_keybindings(&config) { + line_editor = match reedline_config::create_keybindings(&config) { Ok(keybindings) => match keybindings { KeybindingsMode::Emacs(keybindings) => { let edit_mode = Box::new(Emacs::new(keybindings));