mirror of
https://github.com/nushell/nushell.git
synced 2025-06-02 08:06:12 +02:00
# Description When implementing a `Command`, one must also import all the types present in the function signatures for `Command`. This makes it so that we often import the same set of types in each command implementation file. E.g., something like this: ```rust use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ record, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, PipelineData, ShellError, Signature, Span, Type, Value, }; ``` This PR adds the `nu_engine::command_prelude` module which contains the necessary and commonly used types to implement a `Command`: ```rust // command_prelude.rs pub use crate::CallExt; pub use nu_protocol::{ ast::{Call, CellPath}, engine::{Command, EngineState, Stack}, record, Category, Example, IntoInterruptiblePipelineData, IntoPipelineData, IntoSpanned, PipelineData, Record, ShellError, Signature, Span, Spanned, SyntaxShape, Type, Value, }; ``` This should reduce the boilerplate needed to implement a command and also gives us a place to track the breadth of the `Command` API. I tried to be conservative with what went into the prelude modules, since it might be hard/annoying to remove items from the prelude in the future. Let me know if something should be included or excluded.
196 lines
6.9 KiB
Rust
196 lines
6.9 KiB
Rust
use crate::NushellPrompt;
|
|
use log::trace;
|
|
use nu_engine::get_eval_subexpression;
|
|
use nu_protocol::{
|
|
engine::{EngineState, Stack, StateWorkingSet},
|
|
report_error, Config, PipelineData, Value,
|
|
};
|
|
use reedline::Prompt;
|
|
|
|
// Name of environment variable where the prompt could be stored
|
|
pub(crate) const PROMPT_COMMAND: &str = "PROMPT_COMMAND";
|
|
pub(crate) const PROMPT_COMMAND_RIGHT: &str = "PROMPT_COMMAND_RIGHT";
|
|
pub(crate) const PROMPT_INDICATOR: &str = "PROMPT_INDICATOR";
|
|
pub(crate) const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT";
|
|
pub(crate) const PROMPT_INDICATOR_VI_NORMAL: &str = "PROMPT_INDICATOR_VI_NORMAL";
|
|
pub(crate) const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR";
|
|
pub(crate) const TRANSIENT_PROMPT_COMMAND: &str = "TRANSIENT_PROMPT_COMMAND";
|
|
pub(crate) const TRANSIENT_PROMPT_COMMAND_RIGHT: &str = "TRANSIENT_PROMPT_COMMAND_RIGHT";
|
|
pub(crate) const TRANSIENT_PROMPT_INDICATOR: &str = "TRANSIENT_PROMPT_INDICATOR";
|
|
pub(crate) const TRANSIENT_PROMPT_INDICATOR_VI_INSERT: &str =
|
|
"TRANSIENT_PROMPT_INDICATOR_VI_INSERT";
|
|
pub(crate) const TRANSIENT_PROMPT_INDICATOR_VI_NORMAL: &str =
|
|
"TRANSIENT_PROMPT_INDICATOR_VI_NORMAL";
|
|
pub(crate) const TRANSIENT_PROMPT_MULTILINE_INDICATOR: &str =
|
|
"TRANSIENT_PROMPT_MULTILINE_INDICATOR";
|
|
// According to Daniel Imms @Tyriar, we need to do these this way:
|
|
// <133 A><prompt><133 B><command><133 C><command output>
|
|
pub(crate) const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\";
|
|
pub(crate) const POST_PROMPT_MARKER: &str = "\x1b]133;B\x1b\\";
|
|
|
|
fn get_prompt_string(
|
|
prompt: &str,
|
|
config: &Config,
|
|
engine_state: &EngineState,
|
|
stack: &mut Stack,
|
|
) -> Option<String> {
|
|
let eval_subexpression = get_eval_subexpression(engine_state);
|
|
|
|
stack
|
|
.get_env_var(engine_state, prompt)
|
|
.and_then(|v| match v {
|
|
Value::Closure { val, .. } => {
|
|
let block = engine_state.get_block(val.block_id);
|
|
let mut stack = stack.captures_to_stack(val.captures);
|
|
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt
|
|
let ret_val =
|
|
eval_subexpression(engine_state, &mut stack, block, PipelineData::empty());
|
|
trace!(
|
|
"get_prompt_string (block) {}:{}:{}",
|
|
file!(),
|
|
line!(),
|
|
column!()
|
|
);
|
|
|
|
ret_val
|
|
.map_err(|err| {
|
|
let working_set = StateWorkingSet::new(engine_state);
|
|
report_error(&working_set, &err);
|
|
})
|
|
.ok()
|
|
}
|
|
Value::Block { val: block_id, .. } => {
|
|
let block = engine_state.get_block(block_id);
|
|
// Use eval_subexpression to force a redirection of output, so we can use everything in prompt
|
|
let ret_val = eval_subexpression(engine_state, stack, block, PipelineData::empty());
|
|
trace!(
|
|
"get_prompt_string (block) {}:{}:{}",
|
|
file!(),
|
|
line!(),
|
|
column!()
|
|
);
|
|
|
|
ret_val
|
|
.map_err(|err| {
|
|
let working_set = StateWorkingSet::new(engine_state);
|
|
report_error(&working_set, &err);
|
|
})
|
|
.ok()
|
|
}
|
|
Value::String { .. } => Some(PipelineData::Value(v.clone(), None)),
|
|
_ => None,
|
|
})
|
|
.and_then(|pipeline_data| {
|
|
let output = pipeline_data.collect_string("", config).ok();
|
|
|
|
output.map(|mut x| {
|
|
// Just remove the very last newline.
|
|
if x.ends_with('\n') {
|
|
x.pop();
|
|
}
|
|
|
|
if x.ends_with('\r') {
|
|
x.pop();
|
|
}
|
|
x
|
|
})
|
|
})
|
|
}
|
|
|
|
pub(crate) fn update_prompt(
|
|
config: &Config,
|
|
engine_state: &EngineState,
|
|
stack: &mut Stack,
|
|
nu_prompt: &mut NushellPrompt,
|
|
) {
|
|
let left_prompt_string = get_prompt_string(PROMPT_COMMAND, config, engine_state, stack);
|
|
|
|
// Now that we have the prompt string lets ansify it.
|
|
// <133 A><prompt><133 B><command><133 C><command output>
|
|
let left_prompt_string = if config.shell_integration {
|
|
if let Some(prompt_string) = left_prompt_string {
|
|
Some(format!(
|
|
"{PRE_PROMPT_MARKER}{prompt_string}{POST_PROMPT_MARKER}"
|
|
))
|
|
} else {
|
|
left_prompt_string
|
|
}
|
|
} else {
|
|
left_prompt_string
|
|
};
|
|
|
|
let right_prompt_string = get_prompt_string(PROMPT_COMMAND_RIGHT, config, engine_state, stack);
|
|
|
|
let prompt_indicator_string = get_prompt_string(PROMPT_INDICATOR, config, engine_state, stack);
|
|
|
|
let prompt_multiline_string =
|
|
get_prompt_string(PROMPT_MULTILINE_INDICATOR, config, engine_state, stack);
|
|
|
|
let prompt_vi_insert_string =
|
|
get_prompt_string(PROMPT_INDICATOR_VI_INSERT, config, engine_state, stack);
|
|
|
|
let prompt_vi_normal_string =
|
|
get_prompt_string(PROMPT_INDICATOR_VI_NORMAL, config, engine_state, stack);
|
|
|
|
// apply the other indicators
|
|
nu_prompt.update_all_prompt_strings(
|
|
left_prompt_string,
|
|
right_prompt_string,
|
|
prompt_indicator_string,
|
|
prompt_multiline_string,
|
|
(prompt_vi_insert_string, prompt_vi_normal_string),
|
|
config.render_right_prompt_on_last_line,
|
|
);
|
|
trace!("update_prompt {}:{}:{}", file!(), line!(), column!());
|
|
}
|
|
|
|
/// Construct the transient prompt based on the normal nu_prompt
|
|
pub(crate) fn make_transient_prompt(
|
|
config: &Config,
|
|
engine_state: &EngineState,
|
|
stack: &mut Stack,
|
|
nu_prompt: &NushellPrompt,
|
|
) -> Box<dyn Prompt> {
|
|
let mut nu_prompt = nu_prompt.clone();
|
|
|
|
if let Some(s) = get_prompt_string(TRANSIENT_PROMPT_COMMAND, config, engine_state, stack) {
|
|
nu_prompt.update_prompt_left(Some(s))
|
|
}
|
|
|
|
if let Some(s) = get_prompt_string(TRANSIENT_PROMPT_COMMAND_RIGHT, config, engine_state, stack)
|
|
{
|
|
nu_prompt.update_prompt_right(Some(s), config.render_right_prompt_on_last_line)
|
|
}
|
|
|
|
if let Some(s) = get_prompt_string(TRANSIENT_PROMPT_INDICATOR, config, engine_state, stack) {
|
|
nu_prompt.update_prompt_indicator(Some(s))
|
|
}
|
|
if let Some(s) = get_prompt_string(
|
|
TRANSIENT_PROMPT_INDICATOR_VI_INSERT,
|
|
config,
|
|
engine_state,
|
|
stack,
|
|
) {
|
|
nu_prompt.update_prompt_vi_insert(Some(s))
|
|
}
|
|
if let Some(s) = get_prompt_string(
|
|
TRANSIENT_PROMPT_INDICATOR_VI_NORMAL,
|
|
config,
|
|
engine_state,
|
|
stack,
|
|
) {
|
|
nu_prompt.update_prompt_vi_normal(Some(s))
|
|
}
|
|
|
|
if let Some(s) = get_prompt_string(
|
|
TRANSIENT_PROMPT_MULTILINE_INDICATOR,
|
|
config,
|
|
engine_state,
|
|
stack,
|
|
) {
|
|
nu_prompt.update_prompt_multiline(Some(s))
|
|
}
|
|
|
|
Box::new(nu_prompt)
|
|
}
|