forked from extern/nushell
Configurable built-in prompts (#2064)
* Add ansi, do, and prompt customization * Fix test * Cleanups
This commit is contained in:
parent
6daec399e6
commit
05781607f4
@ -269,6 +269,7 @@ pub fn create_default_context(
|
||||
whole_stream_command(Debug),
|
||||
whole_stream_command(Alias),
|
||||
whole_stream_command(WithEnv),
|
||||
whole_stream_command(Do),
|
||||
// Statistics
|
||||
whole_stream_command(Size),
|
||||
whole_stream_command(Count),
|
||||
@ -303,6 +304,7 @@ pub fn create_default_context(
|
||||
whole_stream_command(StrToDatetime),
|
||||
whole_stream_command(StrTrim),
|
||||
whole_stream_command(BuildString),
|
||||
whole_stream_command(Ansi),
|
||||
// Column manipulation
|
||||
whole_stream_command(Reject),
|
||||
whole_stream_command(Select),
|
||||
@ -588,6 +590,7 @@ pub async fn cli(
|
||||
rl.set_helper(Some(crate::shell::Helper::new(context.clone())));
|
||||
|
||||
let config = config::config(Tag::unknown())?;
|
||||
|
||||
let use_starship = match config.get("use_starship") {
|
||||
Some(b) => match b.as_bool() {
|
||||
Ok(b) => b,
|
||||
@ -596,7 +599,7 @@ pub async fn cli(
|
||||
_ => false,
|
||||
};
|
||||
|
||||
let edit_mode = config::config(Tag::unknown())?
|
||||
let edit_mode = config
|
||||
.get("edit_mode")
|
||||
.map(|s| match s.value.expect_string() {
|
||||
"vi" => EditMode::Vi,
|
||||
@ -607,21 +610,21 @@ pub async fn cli(
|
||||
|
||||
rl.set_edit_mode(edit_mode);
|
||||
|
||||
let max_history_size = config::config(Tag::unknown())?
|
||||
let max_history_size = config
|
||||
.get("history_size")
|
||||
.map(|i| i.value.expect_int())
|
||||
.unwrap_or(100_000);
|
||||
|
||||
rl.set_max_history_size(max_history_size as usize);
|
||||
|
||||
let key_timeout = config::config(Tag::unknown())?
|
||||
let key_timeout = config
|
||||
.get("key_timeout")
|
||||
.map(|s| s.value.expect_int())
|
||||
.unwrap_or(1);
|
||||
|
||||
rl.set_keyseq_timeout(key_timeout as i32);
|
||||
|
||||
let completion_mode = config::config(Tag::unknown())?
|
||||
let completion_mode = config
|
||||
.get("completion_mode")
|
||||
.map(|s| match s.value.expect_string() {
|
||||
"list" => CompletionType::List,
|
||||
@ -649,6 +652,48 @@ pub async fn cli(
|
||||
_ => {}
|
||||
};
|
||||
starship::print::get_prompt(starship_context)
|
||||
} else if let Some(prompt) = config.get("prompt") {
|
||||
let prompt_line = prompt.as_string()?;
|
||||
|
||||
if let Ok(result) = nu_parser::lite_parse(&prompt_line, 0).map_err(ShellError::from)
|
||||
{
|
||||
let prompt_block = nu_parser::classify_block(&result, context.registry());
|
||||
|
||||
let env = context.get_env();
|
||||
|
||||
match run_block(
|
||||
&prompt_block.block,
|
||||
&mut context,
|
||||
InputStream::empty(),
|
||||
&Value::nothing(),
|
||||
&IndexMap::new(),
|
||||
&env,
|
||||
)
|
||||
.await
|
||||
{
|
||||
Ok(result) => {
|
||||
context.clear_errors();
|
||||
|
||||
match result.collect_string(Tag::unknown()).await {
|
||||
Ok(string_result) => string_result.item,
|
||||
Err(_) => {
|
||||
context.maybe_print_errors(Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"Error running prompt> ".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(_) => {
|
||||
context.maybe_print_errors(Text::from(prompt_line));
|
||||
context.clear_errors();
|
||||
|
||||
"Error running prompt> ".to_string()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
"Error parsing prompt> ".to_string()
|
||||
}
|
||||
} else {
|
||||
format!(
|
||||
"\x1b[32m{}{}\x1b[m> ",
|
||||
|
@ -5,6 +5,7 @@ mod from_delimited_data;
|
||||
mod to_delimited_data;
|
||||
|
||||
pub(crate) mod alias;
|
||||
pub(crate) mod ansi;
|
||||
pub(crate) mod append;
|
||||
pub(crate) mod args;
|
||||
pub(crate) mod autoview;
|
||||
@ -23,6 +24,7 @@ pub(crate) mod cp;
|
||||
pub(crate) mod date;
|
||||
pub(crate) mod debug;
|
||||
pub(crate) mod default;
|
||||
pub(crate) mod do_;
|
||||
pub(crate) mod drop;
|
||||
pub(crate) mod du;
|
||||
pub(crate) mod each;
|
||||
@ -135,6 +137,7 @@ pub(crate) use command::{
|
||||
};
|
||||
|
||||
pub(crate) use alias::Alias;
|
||||
pub(crate) use ansi::Ansi;
|
||||
pub(crate) use append::Append;
|
||||
pub(crate) use build_string::BuildString;
|
||||
pub(crate) use cal::Cal;
|
||||
@ -146,6 +149,7 @@ pub(crate) use cp::Cpy;
|
||||
pub(crate) use date::Date;
|
||||
pub(crate) use debug::Debug;
|
||||
pub(crate) use default::Default;
|
||||
pub(crate) use do_::Do;
|
||||
pub(crate) use drop::Drop;
|
||||
pub(crate) use du::Du;
|
||||
pub(crate) use each::Each;
|
||||
|
96
crates/nu-cli/src/commands/ansi.rs
Normal file
96
crates/nu-cli/src/commands/ansi.rs
Normal file
@ -0,0 +1,96 @@
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{ReturnSuccess, Signature, SyntaxShape, UntaggedValue, Value};
|
||||
|
||||
pub struct Ansi;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct AnsiArgs {
|
||||
color: Value,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Ansi {
|
||||
fn name(&self) -> &str {
|
||||
"ansi"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("ansi").required(
|
||||
"color",
|
||||
SyntaxShape::Any,
|
||||
"the name of the color to use or 'reset' to reset the color",
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Output ANSI codes to change color"
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Change color to green",
|
||||
example: r#"ansi green"#,
|
||||
result: Some(vec![Value::from("\u{1b}[32m")]),
|
||||
},
|
||||
Example {
|
||||
description: "Reset the color",
|
||||
example: r#"ansi reset"#,
|
||||
result: Some(vec![Value::from("\u{1b}[0m")]),
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let (AnsiArgs { color }, _) = args.process(®istry).await?;
|
||||
|
||||
let color_string = color.as_string()?;
|
||||
|
||||
let ansi_code = str_to_ansi_color(color_string);
|
||||
|
||||
if let Some(output) = ansi_code {
|
||||
Ok(OutputStream::one(ReturnSuccess::value(
|
||||
UntaggedValue::string(output).into_value(color.tag()),
|
||||
)))
|
||||
} else {
|
||||
Err(ShellError::labeled_error(
|
||||
"Unknown color",
|
||||
"unknown color",
|
||||
color.tag(),
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn str_to_ansi_color(s: String) -> Option<String> {
|
||||
match s.as_str() {
|
||||
"g" | "green" => Some(ansi_term::Color::Green.prefix().to_string()),
|
||||
"r" | "red" => Some(ansi_term::Color::Red.prefix().to_string()),
|
||||
"u" | "blue" => Some(ansi_term::Color::Blue.prefix().to_string()),
|
||||
"b" | "black" => Some(ansi_term::Color::Black.prefix().to_string()),
|
||||
"y" | "yellow" => Some(ansi_term::Color::Yellow.prefix().to_string()),
|
||||
"p" | "purple" => Some(ansi_term::Color::Purple.prefix().to_string()),
|
||||
"c" | "cyan" => Some(ansi_term::Color::Cyan.prefix().to_string()),
|
||||
"w" | "white" => Some(ansi_term::Color::White.prefix().to_string()),
|
||||
"reset" => Some("\x1b[0m".to_owned()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Ansi;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Ansi {})
|
||||
}
|
||||
}
|
@ -214,6 +214,9 @@ fn spawn(
|
||||
if !is_last {
|
||||
process.stdout(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stdout pipe");
|
||||
|
||||
process.stderr(Stdio::piped());
|
||||
trace!(target: "nu::run::external", "set up stderr pipe");
|
||||
}
|
||||
|
||||
// open since we have some contents for stdin
|
||||
@ -312,6 +315,20 @@ fn spawn(
|
||||
return Err(());
|
||||
};
|
||||
|
||||
let stderr = if let Some(stderr) = child.stderr.take() {
|
||||
stderr
|
||||
} else {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
"Can't redirect the stderr for external command",
|
||||
"can't redirect stderr",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag,
|
||||
}));
|
||||
return Err(());
|
||||
};
|
||||
|
||||
let file = futures::io::AllowStdIo::new(stdout);
|
||||
let stream = FramedRead::new(file, MaybeTextCodec);
|
||||
|
||||
@ -365,6 +382,64 @@ fn spawn(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let file = futures::io::AllowStdIo::new(stderr);
|
||||
let err_stream = FramedRead::new(file, MaybeTextCodec);
|
||||
|
||||
for err_line in block_on_stream(err_stream) {
|
||||
match err_line {
|
||||
Ok(line) => match line {
|
||||
StringOrBinary::String(s) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(
|
||||
ShellError::untagged_runtime_error(s.clone()),
|
||||
),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
|
||||
if result.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
StringOrBinary::Binary(_) => {
|
||||
let result = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(
|
||||
ShellError::untagged_runtime_error(
|
||||
"Binary in stderr output",
|
||||
),
|
||||
),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
|
||||
if result.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(e) => {
|
||||
// If there's an exit status, it makes sense that we may error when
|
||||
// trying to read from its stdout pipe (likely been closed). In that
|
||||
// case, don't emit an error.
|
||||
let should_error = match child.wait() {
|
||||
Ok(exit_status) => !exit_status.success(),
|
||||
Err(_) => true,
|
||||
};
|
||||
|
||||
if should_error {
|
||||
let _ = stdout_read_tx.send(Ok(Value {
|
||||
value: UntaggedValue::Error(ShellError::labeled_error(
|
||||
format!("Unable to read from stderr ({})", e),
|
||||
"unable to read from stderr",
|
||||
&stdout_name_tag,
|
||||
)),
|
||||
tag: stdout_name_tag.clone(),
|
||||
}));
|
||||
}
|
||||
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We can give an error when we see a non-zero exit code, but this is different
|
||||
|
109
crates/nu-cli/src/commands/do_.rs
Normal file
109
crates/nu-cli/src/commands/do_.rs
Normal file
@ -0,0 +1,109 @@
|
||||
use crate::commands::classified::block::run_block;
|
||||
use crate::commands::WholeStreamCommand;
|
||||
use crate::prelude::*;
|
||||
use nu_errors::ShellError;
|
||||
use nu_protocol::{hir::Block, ReturnSuccess, Signature, SyntaxShape, Value};
|
||||
|
||||
pub struct Do;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct DoArgs {
|
||||
block: Block,
|
||||
ignore_errors: bool,
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WholeStreamCommand for Do {
|
||||
fn name(&self) -> &str {
|
||||
"do"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("with-env")
|
||||
.required("block", SyntaxShape::Block, "the block to run ")
|
||||
.switch(
|
||||
"ignore_errors",
|
||||
"ignore errors as the block runs",
|
||||
Some('i'),
|
||||
)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Runs a block, optionally ignoring errors"
|
||||
}
|
||||
|
||||
async fn run(
|
||||
&self,
|
||||
args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
do_(args, registry).await
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "Run the block",
|
||||
example: r#"do { echo hello }"#,
|
||||
result: Some(vec![Value::from("hello")]),
|
||||
},
|
||||
Example {
|
||||
description: "Run the block and ignore errors",
|
||||
example: r#"do -i { thisisnotarealcommand }"#,
|
||||
result: Some(vec![Value::nothing()]),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
async fn do_(
|
||||
raw_args: CommandArgs,
|
||||
registry: &CommandRegistry,
|
||||
) -> Result<OutputStream, ShellError> {
|
||||
let registry = registry.clone();
|
||||
|
||||
let mut context = Context::from_raw(&raw_args, ®istry);
|
||||
let scope = raw_args.call_info.scope.clone();
|
||||
let (
|
||||
DoArgs {
|
||||
ignore_errors,
|
||||
block,
|
||||
},
|
||||
input,
|
||||
) = raw_args.process(®istry).await?;
|
||||
|
||||
let result = run_block(
|
||||
&block,
|
||||
&mut context,
|
||||
input,
|
||||
&scope.it,
|
||||
&scope.vars,
|
||||
&scope.env,
|
||||
)
|
||||
.await;
|
||||
|
||||
if ignore_errors {
|
||||
match result {
|
||||
Ok(mut stream) => {
|
||||
let output = stream.drain_vec().await;
|
||||
context.clear_errors();
|
||||
Ok(futures::stream::iter(output).to_output_stream())
|
||||
}
|
||||
Err(_) => Ok(OutputStream::one(ReturnSuccess::value(Value::nothing()))),
|
||||
}
|
||||
} else {
|
||||
result.map(|x| x.to_output_stream())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Do;
|
||||
|
||||
#[test]
|
||||
fn examples_work_as_expected() {
|
||||
use crate::examples::test as test_examples;
|
||||
|
||||
test_examples(Do {})
|
||||
}
|
||||
}
|
@ -74,7 +74,7 @@ mod tests {
|
||||
Ok(int(10)),
|
||||
Ok(int(10)),
|
||||
Ok(int(10)),
|
||||
Ok(table(&vec![int(10)])),
|
||||
Ok(table(&[int(10)])),
|
||||
Ok(int(10)),
|
||||
],
|
||||
},
|
||||
@ -87,7 +87,7 @@ mod tests {
|
||||
Ok(int(10)),
|
||||
Ok(int(30)),
|
||||
Ok(int(20)),
|
||||
Ok(table(&vec![int(10), int(20), int(30)])),
|
||||
Ok(table(&[int(10), int(20), int(30)])),
|
||||
Ok(int(60)),
|
||||
],
|
||||
},
|
||||
@ -100,7 +100,7 @@ mod tests {
|
||||
Ok(int(10)),
|
||||
Ok(decimal(26.5)),
|
||||
Ok(decimal(26.5)),
|
||||
Ok(table(&vec![decimal(26.5)])),
|
||||
Ok(table(&[decimal(26.5)])),
|
||||
Ok(decimal(63)),
|
||||
],
|
||||
},
|
||||
@ -113,7 +113,7 @@ mod tests {
|
||||
Ok(int(-14)),
|
||||
Ok(int(10)),
|
||||
Ok(int(-11)),
|
||||
Ok(table(&vec![int(-14), int(-11), int(10)])),
|
||||
Ok(table(&[int(-14), int(-11), int(10)])),
|
||||
Ok(int(-15)),
|
||||
],
|
||||
},
|
||||
@ -126,7 +126,7 @@ mod tests {
|
||||
Ok(decimal(-13.5)),
|
||||
Ok(int(10)),
|
||||
Ok(decimal(-11.5)),
|
||||
Ok(table(&vec![decimal(-13.5), decimal(-11.5), int(10)])),
|
||||
Ok(table(&[decimal(-13.5), decimal(-11.5), int(10)])),
|
||||
Ok(decimal(-15)),
|
||||
],
|
||||
},
|
||||
@ -145,8 +145,8 @@ mod tests {
|
||||
Ok(row!["col1".to_owned() => int(4), "col2".to_owned() => int(8)]),
|
||||
Ok(row!["col1".to_owned() => decimal(2.5), "col2".to_owned() => decimal(6.5)]),
|
||||
Ok(row![
|
||||
"col1".to_owned() => table(&vec![int(1), int(2), int(3), int(4)]),
|
||||
"col2".to_owned() => table(&vec![int(5), int(6), int(7), int(8)])
|
||||
"col1".to_owned() => table(&[int(1), int(2), int(3), int(4)]),
|
||||
"col2".to_owned() => table(&[int(5), int(6), int(7), int(8)])
|
||||
]),
|
||||
Ok(row!["col1".to_owned() => int(10), "col2".to_owned() => int(26)]),
|
||||
],
|
||||
|
Loading…
Reference in New Issue
Block a user