mirror of
https://github.com/nushell/nushell.git
synced 2025-03-30 10:37:29 +02:00
# 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
223 lines
6.8 KiB
Rust
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 {})
|
|
}
|
|
}
|