mirror of
https://github.com/nushell/nushell.git
synced 2025-06-01 23:55:50 +02:00
211 lines
7.2 KiB
Rust
211 lines
7.2 KiB
Rust
use crate::platform::input::legacy_input::LegacyInput;
|
|
use crate::platform::input::reedline_prompt::ReedlinePrompt;
|
|
use nu_engine::command_prelude::*;
|
|
use nu_protocol::shell_error::io::IoError;
|
|
use reedline::{FileBackedHistory, History, HistoryItem, Reedline, Signal, HISTORY_SIZE};
|
|
|
|
#[derive(Clone)]
|
|
pub struct Input;
|
|
|
|
impl LegacyInput for Input {}
|
|
|
|
impl Command for Input {
|
|
fn name(&self) -> &str {
|
|
"input"
|
|
}
|
|
|
|
fn description(&self) -> &str {
|
|
"Get input from the user."
|
|
}
|
|
|
|
fn search_terms(&self) -> Vec<&str> {
|
|
vec!["prompt", "interactive"]
|
|
}
|
|
|
|
fn signature(&self) -> Signature {
|
|
Signature::build("input")
|
|
.input_output_types(vec![
|
|
(Type::Nothing, Type::Any),
|
|
(Type::List(Box::new(Type::String)), Type::Any)])
|
|
.allow_variants_without_examples(true)
|
|
.optional("prompt", SyntaxShape::String, "Prompt to show the user.")
|
|
.named(
|
|
"bytes-until-any",
|
|
SyntaxShape::String,
|
|
"read bytes (not text) until any of the given stop bytes is seen",
|
|
Some('u'),
|
|
)
|
|
.named(
|
|
"numchar",
|
|
SyntaxShape::Int,
|
|
"number of characters to read; suppresses output",
|
|
Some('n'),
|
|
)
|
|
.named(
|
|
"default",
|
|
SyntaxShape::String,
|
|
"default value if no input is provided",
|
|
Some('d'),
|
|
)
|
|
.named(
|
|
"history-file",
|
|
SyntaxShape::Filepath,
|
|
"path to a file to read/write history",
|
|
None,
|
|
)
|
|
.named(
|
|
"max-history",
|
|
SyntaxShape::Int,
|
|
"The maximum number of entries to keep in the history, defaults to reedline {HISTORY_SIZE}",
|
|
None,
|
|
)
|
|
.switch("suppress-output", "don't print keystroke values", Some('s'))
|
|
.category(Category::Platform)
|
|
}
|
|
|
|
fn run(
|
|
&self,
|
|
engine_state: &EngineState,
|
|
stack: &mut Stack,
|
|
call: &Call,
|
|
input: PipelineData,
|
|
) -> Result<PipelineData, ShellError> {
|
|
// Those options are not supported by reedline, default to the legacy
|
|
// implementation
|
|
let use_legacy = [
|
|
call.get_flag::<String>(engine_state, stack, "bytes-until-any")?
|
|
.is_some(),
|
|
call.has_flag(engine_state, stack, "suppress-output")?,
|
|
call.get_flag::<Spanned<i64>>(engine_state, stack, "numchar")?
|
|
.is_some(),
|
|
]
|
|
.iter()
|
|
.any(|x| *x);
|
|
|
|
if use_legacy {
|
|
return self.legacy_input(engine_state, stack, call, input);
|
|
}
|
|
|
|
let prompt_str: Option<String> = call.opt(engine_state, stack, 0)?;
|
|
let default_val: Option<String> = call.get_flag(engine_state, stack, "default")?;
|
|
let history_file_val: Option<String> = call.get_flag(engine_state, stack, "history-file")?;
|
|
let max_history: usize = call
|
|
.get_flag::<i64>(engine_state, stack, "max-history")?
|
|
.map(|l| if l < 0 { 0 } else { l as usize })
|
|
.unwrap_or(HISTORY_SIZE);
|
|
|
|
let from_io_error = IoError::factory(call.head, None);
|
|
|
|
let default_str = match (&prompt_str, &default_val) {
|
|
(Some(_prompt), Some(val)) => format!("(default: {val}) "),
|
|
_ => "".to_string(),
|
|
};
|
|
|
|
let history_entries = match input {
|
|
PipelineData::Value(Value::List { vals, .. }, ..) => {
|
|
Some(vals)
|
|
}
|
|
_ => None,
|
|
};
|
|
|
|
// If we either have history entries or history file, we create an history
|
|
let history = match (history_entries.is_some(), history_file_val.is_some()) {
|
|
(false, false) => None,
|
|
_ => {
|
|
let mut history = match history_file_val {
|
|
Some(file) => FileBackedHistory::with_file(max_history, file.into()),
|
|
None => FileBackedHistory::new(max_history)
|
|
}.expect("Error creating history file");
|
|
|
|
if let Some(vals) = history_entries {
|
|
vals.iter().for_each(|val| {
|
|
if let Value::String { val, .. } = val {
|
|
let _ = history.save(HistoryItem::from_command_line(val.clone()));
|
|
}
|
|
});
|
|
}
|
|
Some(history)
|
|
},
|
|
};
|
|
|
|
let prompt = ReedlinePrompt {
|
|
indicator: default_str,
|
|
left_prompt: prompt_str.unwrap_or("".to_string()),
|
|
right_prompt: "".to_string(),
|
|
};
|
|
|
|
let mut line_editor = Reedline::create();
|
|
line_editor = line_editor.with_ansi_colors(false);
|
|
line_editor = match history {
|
|
Some(h) => line_editor.with_history(Box::new(h)),
|
|
None => line_editor,
|
|
};
|
|
|
|
let mut buf = String::new();
|
|
|
|
loop {
|
|
match line_editor.read_line(&prompt) {
|
|
Ok(Signal::Success(buffer)) => {
|
|
buf.push_str(&buffer);
|
|
break;
|
|
}
|
|
Ok(Signal::CtrlC) => {
|
|
return Err(
|
|
IoError::new(std::io::ErrorKind::Interrupted, call.head, None).into(),
|
|
);
|
|
}
|
|
Ok(_) => continue,
|
|
Err(event_error) => {
|
|
crossterm::terminal::disable_raw_mode().map_err(&from_io_error)?;
|
|
return Err(from_io_error(event_error).into());
|
|
}
|
|
}
|
|
}
|
|
match default_val {
|
|
Some(val) if buf.is_empty() => Ok(Value::string(val, call.head).into_pipeline_data()),
|
|
_ => Ok(Value::string(buf, call.head).into_pipeline_data()),
|
|
}
|
|
}
|
|
|
|
fn examples(&self) -> Vec<Example> {
|
|
vec![
|
|
Example {
|
|
description: "Get input from the user, and assign to a variable",
|
|
example: "let user_input = (input)",
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "Get two characters from the user, and assign to a variable",
|
|
example: "let user_input = (input --numchar 2)",
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "Get input from the user with default value, and assign to a variable",
|
|
example: "let user_input = (input --default 10)",
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "Get input from the user with history, and assign to a variable",
|
|
example: "let user_input = ([past,command,entries] | input)",
|
|
result: None,
|
|
},
|
|
Example {
|
|
description: "Get input from the user with history backed by a file, and assign to a variable",
|
|
example: "let user_input = (input --history-file ./history.txt)",
|
|
result: None,
|
|
},
|
|
]
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::Input;
|
|
|
|
#[test]
|
|
fn examples_work_as_expected() {
|
|
use crate::test_examples;
|
|
test_examples(Input {})
|
|
}
|
|
}
|