using menu trait (#861)

This commit is contained in:
Fernando Herrera 2022-01-27 07:53:23 +00:00 committed by GitHub
parent 04395ee05c
commit 267ff4b0cf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 142 additions and 107 deletions

2
Cargo.lock generated
View file

@ -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",

View file

@ -14,10 +14,8 @@ pub struct NushellPrompt {
right_prompt_string: Option<String>,
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<str> {
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<str> {
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(),
}
}

View file

@ -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

View file

@ -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<KeybindingsMode, She
match config.edit_mode.as_str() {
"emacs" => {
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<KeybindingsMode, She
let mut insert_keybindings = default_vi_insert_keybindings();
let mut normal_keybindings = default_vi_normal_keybindings();
add_menu_keybindings(&mut insert_keybindings);
add_menu_keybindings(&mut normal_keybindings);
for parsed_keybinding in parsed_keybindings {
if parsed_keybinding.mode.into_string("", config).as_str() == "vi_insert" {
add_keybinding(&mut insert_keybindings, parsed_keybinding, config)?
@ -254,28 +318,27 @@ fn parse_event(value: Value, config: &Config) -> Result<ReedlineEvent, ShellErro
"nexthistory" => 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(),

View file

@ -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));