use dialoguer::{console::Term, Select}; use dialoguer::{FuzzySelect, MultiSelect}; use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, SyntaxShape, Type, Value, }; use std::fmt::{Display, Formatter}; enum InteractMode { Single(Option), Multi(Option>), } #[derive(Clone)] struct Options { name: String, value: Value, } impl Display for Options { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.name) } } #[derive(Clone)] pub struct InputList; const INTERACT_ERROR: &str = "Interact error, could not process options"; impl Command for InputList { fn name(&self) -> &str { "input list" } fn signature(&self) -> Signature { Signature::build("input list") .input_output_types(vec![ (Type::List(Box::new(Type::Any)), Type::Any), (Type::Range, Type::Int), ]) .optional("prompt", SyntaxShape::String, "The prompt to display.") .switch( "multi", "Use multiple results, you can press a to toggle all options on/off", Some('m'), ) .switch("fuzzy", "Use a fuzzy select.", Some('f')) .allow_variants_without_examples(true) .category(Category::Platform) } fn usage(&self) -> &str { "Interactive list selection." } fn extra_usage(&self) -> &str { "Abort with esc or q." } fn search_terms(&self) -> Vec<&str> { vec!["prompt", "ask", "menu"] } fn run( &self, engine_state: &EngineState, stack: &mut Stack, call: &Call, input: PipelineData, ) -> Result { let head = call.head; let prompt: Option = call.opt(engine_state, stack, 0)?; let multi = call.has_flag(engine_state, stack, "multi")?; let fuzzy = call.has_flag(engine_state, stack, "fuzzy")?; let options: Vec = match input { PipelineData::Value(Value::Range { .. }, ..) | PipelineData::Value(Value::List { .. }, ..) | PipelineData::ListStream { .. } => input .into_iter() .map(move |val| Options { name: val.into_string(", ", engine_state.get_config()), value: val, }) .collect(), _ => { return Err(ShellError::TypeMismatch { err_message: "expected a list, a table, or a range".to_string(), span: head, }) } }; if options.is_empty() { return Err(ShellError::TypeMismatch { err_message: "expected a list or table, it can also be a problem with the an inner type of your list.".to_string(), span: head, }); } if multi && fuzzy { return Err(ShellError::TypeMismatch { err_message: "Fuzzy search is not supported for multi select".to_string(), span: head, }); } // could potentially be used to map the use theme colors at some point // let theme = dialoguer::theme::ColorfulTheme { // active_item_style: Style::new().fg(Color::Cyan).bold(), // ..Default::default() // }; let ans: InteractMode = if multi { let multi_select = MultiSelect::new(); //::with_theme(&theme); InteractMode::Multi( if let Some(prompt) = prompt { multi_select.with_prompt(&prompt) } else { multi_select } .items(&options) .report(false) .interact_on_opt(&Term::stderr()) .map_err(|err| ShellError::IOError { msg: format!("{}: {}", INTERACT_ERROR, err), })?, ) } else if fuzzy { let fuzzy_select = FuzzySelect::new(); //::with_theme(&theme); InteractMode::Single( if let Some(prompt) = prompt { fuzzy_select.with_prompt(&prompt) } else { fuzzy_select } .items(&options) .default(0) .report(false) .interact_on_opt(&Term::stderr()) .map_err(|err| ShellError::IOError { msg: format!("{}: {}", INTERACT_ERROR, err), })?, ) } else { let select = Select::new(); //::with_theme(&theme); InteractMode::Single( if let Some(prompt) = prompt { select.with_prompt(&prompt) } else { select } .items(&options) .default(0) .report(false) .interact_on_opt(&Term::stderr()) .map_err(|err| ShellError::IOError { msg: format!("{}: {}", INTERACT_ERROR, err), })?, ) }; Ok(match ans { InteractMode::Multi(res) => match res { Some(opts) => Value::list( opts.iter().map(|s| options[*s].value.clone()).collect(), head, ), None => Value::nothing(head), }, InteractMode::Single(res) => match res { Some(opt) => options[opt].value.clone(), None => Value::nothing(head), }, } .into_pipeline_data()) } fn examples(&self) -> Vec { vec![ Example { description: "Return a single value from a list", example: r#"[1 2 3 4 5] | input list 'Rate it'"#, result: None, }, Example { description: "Return multiple values from a list", example: r#"[Banana Kiwi Pear Peach Strawberry] | input list --multi 'Add fruits to the basket'"#, result: None, }, Example { description: "Return a single record from a table with fuzzy search", example: r#"ls | input list --fuzzy 'Select the target'"#, result: None, }, Example { description: "Choose an item from a range", example: r#"1..10 | input list"#, result: None, }, ] } } #[cfg(test)] mod test { use super::*; #[test] fn test_examples() { use crate::test_examples; test_examples(InputList {}) } }