nushell/crates/nu-command/src/platform/input/list.rs
WindSoilder e72a4116ec
adjust some commansd input_output type (#11436)
# Description
1. Make table to be a subtype of `list<any>`, so some input_output_types
of filter commands are unnecessary
2. Change some commands which accept an input type, but generates
different output types. In this case, delete duplicate entry, and change
relative output type to `<any>`

Yeah it makes some commands more permissive, but I think it's better to
run into strange issue that why my script runs to failed during parse
time.

Fixes  #11193

# User-Facing Changes
NaN

# Tests + Formatting
NaN

# After Submitting
NaN
2024-01-15 16:58:26 +08:00

223 lines
6.8 KiB
Rust

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<usize>),
Multi(Option<Vec<usize>>),
}
#[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<PipelineData, ShellError> {
let head = call.head;
let prompt: Option<String> = 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<Options> = 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<Example> {
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 {})
}
}