From ea1bd9f8f9e8e33b65c7988a856a5adfcf87b767 Mon Sep 17 00:00:00 2001 From: maxomatic458 <104733404+maxomatic458@users.noreply.github.com> Date: Sun, 21 Jan 2024 01:14:02 +0100 Subject: [PATCH] IDE style completion (#11593) # Description Adds an IDE-Style completion menu ![grafik](https://github.com/nushell/nushell/assets/104733404/df7f1039-2bbc-42f7-9501-fe28507b5cfe) # User-Facing Changes # Tests + Formatting # After Submitting --- Cargo.lock | 2 +- crates/nu-cli/src/reedline_config.rs | 208 +++++++++++++++++- .../src/sample_config/default_config.nu | 37 ++++ 3 files changed, 244 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f53b92b679..6a349e8287 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4517,7 +4517,7 @@ dependencies = [ [[package]] name = "reedline" version = "0.28.0" -source = "git+https://github.com/nushell/reedline?branch=main#31eaeeb231664b889c92595ff38ead4ccb570766" +source = "git+https://github.com/nushell/reedline?branch=main#9ca229de321174ccd18bc2d8f96e865b788e65b5" dependencies = [ "arboard", "chrono", diff --git a/crates/nu-cli/src/reedline_config.rs b/crates/nu-cli/src/reedline_config.rs index 9ab6f4a138..2734ef88a2 100644 --- a/crates/nu-cli/src/reedline_config.rs +++ b/crates/nu-cli/src/reedline_config.rs @@ -12,7 +12,8 @@ use nu_protocol::{ }; use reedline::{ default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings, - ColumnarMenu, EditCommand, Keybindings, ListMenu, Reedline, ReedlineEvent, ReedlineMenu, + ColumnarMenu, DescriptionMode, EditCommand, IdeMenu, Keybindings, ListMenu, Reedline, + ReedlineEvent, ReedlineMenu, }; use std::sync::Arc; @@ -138,9 +139,10 @@ fn add_menu( match layout.as_str() { "columnar" => add_columnar_menu(line_editor, menu, engine_state, stack, config), "list" => add_list_menu(line_editor, menu, engine_state, stack, config), + "ide" => add_ide_menu(line_editor, menu, engine_state, stack, config), "description" => add_description_menu(line_editor, menu, engine_state, stack, config), _ => Err(ShellError::UnsupportedConfigValue { - expected: "columnar, list or description".to_string(), + expected: "columnar, list, ide or description".to_string(), value: menu.menu_type.into_abbreviated_string(config), span: menu.menu_type.span(), }), @@ -351,6 +353,208 @@ pub(crate) fn add_list_menu( } } +// Adds an IDE menu to the line editor +pub(crate) fn add_ide_menu( + line_editor: Reedline, + menu: &ParsedMenu, + engine_state: Arc, + stack: &Stack, + config: &Config, +) -> Result { + let span = menu.menu_type.span(); + let name = menu.name.into_string("", config); + let mut ide_menu = IdeMenu::default().with_name(&name); + + if let Value::Record { val, .. } = &menu.menu_type { + ide_menu = match extract_value("min_completion_width", val, span) { + Ok(min_completion_width) => { + let min_completion_width = min_completion_width.as_int()?; + ide_menu.with_min_completion_width(min_completion_width as u16) + } + Err(_) => ide_menu, + }; + + ide_menu = match extract_value("max_completion_width", val, span) { + Ok(max_completion_width) => { + let max_completion_width = max_completion_width.as_int()?; + ide_menu.with_max_completion_width(max_completion_width as u16) + } + Err(_) => ide_menu, + }; + + ide_menu = match extract_value("max_completion_height", val, span) { + Ok(max_completion_height) => { + let max_completion_height = max_completion_height.as_int()?; + ide_menu.with_max_completion_height(max_completion_height as u16) + } + Err(_) => ide_menu, + }; + + ide_menu = match extract_value("padding", val, span) { + Ok(padding) => { + let padding = padding.as_int()?; + ide_menu.with_padding(padding as u16) + } + Err(_) => ide_menu, + }; + + ide_menu = match extract_value("border", val, span) { + Ok(border) => { + if let Ok(border) = border.as_bool() { + if border { + ide_menu.with_default_border() + } else { + ide_menu + } + } else if let Ok(border_chars) = border.as_record() { + let top_right = extract_value("top_right", border_chars, span)?.as_char()?; + let top_left = extract_value("top_left", border_chars, span)?.as_char()?; + let bottom_right = + extract_value("bottom_right", border_chars, span)?.as_char()?; + let bottom_left = + extract_value("bottom_left", border_chars, span)?.as_char()?; + let horizontal = extract_value("horizontal", border_chars, span)?.as_char()?; + let vertical = extract_value("vertical", border_chars, span)?.as_char()?; + + ide_menu.with_border( + top_right, + top_left, + bottom_right, + bottom_left, + horizontal, + vertical, + ) + } else { + return Err(ShellError::UnsupportedConfigValue { + expected: "bool or record".to_string(), + value: border.into_abbreviated_string(config), + span: border.span(), + }); + } + } + Err(_) => ide_menu, + }; + + ide_menu = match extract_value("cursor_offset", val, span) { + Ok(cursor_offset) => { + let cursor_offset = cursor_offset.as_int()?; + ide_menu.with_cursor_offset(cursor_offset as i16) + } + Err(_) => ide_menu, + }; + + ide_menu = match extract_value("description_mode", val, span) { + Ok(description_mode) => { + let description_mode_str = description_mode.as_string()?; + match description_mode_str.as_str() { + "left" => ide_menu.with_description_mode(DescriptionMode::Left), + "right" => ide_menu.with_description_mode(DescriptionMode::Right), + "prefer_right" => ide_menu.with_description_mode(DescriptionMode::PreferRight), + _ => { + return Err(ShellError::UnsupportedConfigValue { + expected: "\"left\", \"right\" or \"prefer_right\"".to_string(), + value: description_mode.into_abbreviated_string(config), + span: description_mode.span(), + }); + } + } + } + Err(_) => ide_menu, + }; + + ide_menu = match extract_value("min_description_width", val, span) { + Ok(min_description_width) => { + let min_description_width = min_description_width.as_int()?; + ide_menu.with_min_description_width(min_description_width as u16) + } + Err(_) => ide_menu, + }; + + ide_menu = match extract_value("max_description_width", val, span) { + Ok(max_description_width) => { + let max_description_width = max_description_width.as_int()?; + ide_menu.with_max_description_width(max_description_width as u16) + } + Err(_) => ide_menu, + }; + + ide_menu = match extract_value("max_description_height", val, span) { + Ok(max_description_height) => { + let max_description_height = max_description_height.as_int()?; + ide_menu.with_max_description_height(max_description_height as u16) + } + Err(_) => ide_menu, + }; + + ide_menu = match extract_value("description_offset", val, span) { + Ok(description_padding) => { + let description_padding = description_padding.as_int()?; + ide_menu.with_description_offset(description_padding as u16) + } + Err(_) => ide_menu, + }; + } + + let span = menu.style.span(); + if let Value::Record { val, .. } = &menu.style { + add_style!( + "text", + val, + span, + config, + ide_menu, + IdeMenu::with_text_style + ); + add_style!( + "selected_text", + val, + span, + config, + ide_menu, + IdeMenu::with_selected_text_style + ); + add_style!( + "description_text", + val, + span, + config, + ide_menu, + IdeMenu::with_description_text_style + ); + } + + let marker = menu.marker.into_string("", config); + ide_menu = ide_menu.with_marker(marker); + + let only_buffer_difference = menu.only_buffer_difference.as_bool()?; + ide_menu = ide_menu.with_only_buffer_difference(only_buffer_difference); + + let span = menu.source.span(); + match &menu.source { + Value::Nothing { .. } => { + Ok(line_editor.with_menu(ReedlineMenu::EngineCompleter(Box::new(ide_menu)))) + } + Value::Closure { val, .. } => { + let menu_completer = NuMenuCompleter::new( + val.block_id, + span, + stack.captures_to_stack(val.captures.clone()), + engine_state, + only_buffer_difference, + ); + Ok(line_editor.with_menu(ReedlineMenu::WithCompleter { + menu: Box::new(ide_menu), + completer: Box::new(menu_completer), + })) + } + _ => Err(ShellError::UnsupportedConfigValue { + expected: "block or omitted value".to_string(), + value: menu.source.into_abbreviated_string(config), + span, + }), + } +} + // Adds a description menu to the line editor pub(crate) fn add_description_menu( line_editor: Reedline, diff --git a/crates/nu-utils/src/sample_config/default_config.nu b/crates/nu-utils/src/sample_config/default_config.nu index 4af77b6d8a..50e8e27c5d 100644 --- a/crates/nu-utils/src/sample_config/default_config.nu +++ b/crates/nu-utils/src/sample_config/default_config.nu @@ -269,6 +269,30 @@ $env.config = { description_text: yellow } } + { + name: ide_completion_menu + only_buffer_difference: false + marker: "| " + type: { + layout: ide + min_completion_width: 0, + max_completion_width: 50, + # max_completion_height: 10, # will be limited by the available lines in the terminal + padding: 0, + border: false, + cursor_offset: 0, + description_mode: "prefer_right" + min_description_width: 0 + max_description_width: 50 + max_description_height: 10 + description_offset: 1 + } + style: { + text: green + selected_text: green_reverse + description_text: yellow + } + } { name: history_menu only_buffer_difference: true @@ -317,6 +341,19 @@ $env.config = { ] } } + { + name: ide_completion_menu + modifier: control + keycode: char_n + mode: [emacs vi_normal vi_insert] + event: { + until: [ + { send: menu name: ide_completion_menu } + { send: menunext } + { edit: complete } + ] + } + } { name: history_menu modifier: control