use crate::{menus::NuMenuCompleter, NuHelpCompleter}; use crossterm::event::{KeyCode, KeyModifiers}; use nu_ansi_term::Style; use nu_color_config::{color_record_to_nustyle, lookup_ansi_color_style}; use nu_engine::eval_block; use nu_parser::parse; use nu_protocol::{ debugger::WithoutDebug, engine::{EngineState, Stack, StateWorkingSet}, extract_value, Config, EditBindings, FromValue, ParsedKeybinding, ParsedMenu, PipelineData, Record, ShellError, Span, Type, Value, }; use reedline::{ default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings, ColumnarMenu, DescriptionMenu, DescriptionMode, EditCommand, IdeMenu, Keybindings, ListMenu, MenuBuilder, Reedline, ReedlineEvent, ReedlineMenu, }; use std::sync::Arc; const DEFAULT_COMPLETION_MENU: &str = r#" { name: completion_menu only_buffer_difference: false marker: "| " type: { layout: columnar columns: 4 col_width: 20 col_padding: 2 } style: { text: green, selected_text: green_reverse description_text: yellow } }"#; const DEFAULT_IDE_COMPLETION_MENU: &str = r#" { 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: true, cursor_offset: 0, description_mode: "prefer_right" min_description_width: 0 max_description_width: 50 max_description_height: 10 description_offset: 1 # If true, the cursor pos will be corrected, so the suggestions match up with the typed text # # C:\> str # str join # str trim # str split correct_cursor_pos: false } style: { text: green selected_text: { attr: r } description_text: yellow match_text: { attr: u } selected_match_text: { attr: ur } } }"#; const DEFAULT_HISTORY_MENU: &str = r#" { name: history_menu only_buffer_difference: true marker: "? " type: { layout: list page_size: 10 } style: { text: green, selected_text: green_reverse description_text: yellow } }"#; const DEFAULT_HELP_MENU: &str = r#" { name: help_menu only_buffer_difference: true marker: "? " type: { layout: description columns: 4 col_width: 20 col_padding: 2 selection_rows: 4 description_rows: 10 } style: { text: green, selected_text: green_reverse description_text: yellow } }"#; // Adds all menus to line editor pub(crate) fn add_menus( mut line_editor: Reedline, engine_state_ref: Arc, stack: &Stack, config: Arc, ) -> Result { //log::trace!("add_menus: config: {:#?}", &config); line_editor = line_editor.clear_menus(); for menu in &config.menus { line_editor = add_menu( line_editor, menu, engine_state_ref.clone(), stack, config.clone(), )? } // Checking if the default menus have been added from the config file let default_menus = [ ("completion_menu", DEFAULT_COMPLETION_MENU), ("ide_completion_menu", DEFAULT_IDE_COMPLETION_MENU), ("history_menu", DEFAULT_HISTORY_MENU), ("help_menu", DEFAULT_HELP_MENU), ]; let mut engine_state = (*engine_state_ref).clone(); let mut menu_eval_results = vec![]; for (name, definition) in default_menus { if !config .menus .iter() .any(|menu| menu.name.to_expanded_string("", &config) == name) { let (block, delta) = { let mut working_set = StateWorkingSet::new(&engine_state); let output = parse( &mut working_set, Some(name), // format!("entry #{}", entry_num) definition.as_bytes(), true, ); (output, working_set.render()) }; engine_state.merge_delta(delta)?; let mut temp_stack = Stack::new().collect_value(); let input = PipelineData::Empty; menu_eval_results.push(eval_block::( &engine_state, &mut temp_stack, &block, input, )?); } } let new_engine_state_ref = Arc::new(engine_state); for res in menu_eval_results.into_iter() { if let PipelineData::Value(value, None) = res { line_editor = add_menu( line_editor, &ParsedMenu::from_value(value)?, new_engine_state_ref.clone(), stack, config.clone(), )?; } } Ok(line_editor) } fn add_menu( line_editor: Reedline, menu: &ParsedMenu, engine_state: Arc, stack: &Stack, config: Arc, ) -> Result { let span = menu.r#type.span(); if let Value::Record { val, .. } = &menu.r#type { let layout = extract_value("layout", val, span)?.to_expanded_string("", &config); 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), str => Err(ShellError::InvalidValue { valid: "'columnar', 'list', 'ide', or 'description'".into(), actual: format!("'{str}'"), span, }), } } else { Err(ShellError::RuntimeTypeMismatch { expected: Type::record(), actual: menu.r#type.get_type(), span, }) } } fn get_style(record: &Record, name: &'static str, span: Span) -> Option