Only run $env.PROMPT_COMMAND once per prompt (#10986)

# Description

If `$env.TRANSIENT_PROMPT_COMMAND` is not set, use the prompt created by
`$env.PROMPT_COMMAND` instead of running the command a second time. As a
side effect, `$env.TRANSIENT_PROMPT_COMMAND` now runs after the hooks
`pre_prompt` and `env_change`, instead of before.

# User-Facing Changes

- `$env.PROMPT_COMMAND` gets run only once per prompt instead of twice
- `$env.TRANSIENT_PROMPT_COMMAND` now sees any environment set in a
`pre_prompt` or `env_change` hook, like `$env.PROMPT_COMMAND` does
This commit is contained in:
Christopher Durham 2023-12-15 08:56:29 -05:00 committed by GitHub
parent 84742275a1
commit 533c1a89af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 102 additions and 146 deletions

View file

@ -39,35 +39,6 @@ impl NushellPrompt {
} }
} }
pub fn update_prompt_left(&mut self, prompt_string: Option<String>) {
self.left_prompt_string = prompt_string;
}
pub fn update_prompt_right(
&mut self,
prompt_string: Option<String>,
render_right_prompt_on_last_line: bool,
) {
self.right_prompt_string = prompt_string;
self.render_right_prompt_on_last_line = render_right_prompt_on_last_line;
}
pub fn update_prompt_indicator(&mut self, prompt_indicator_string: Option<String>) {
self.default_prompt_indicator = prompt_indicator_string;
}
pub fn update_prompt_vi_insert(&mut self, prompt_vi_insert_string: Option<String>) {
self.default_vi_insert_prompt_indicator = prompt_vi_insert_string;
}
pub fn update_prompt_vi_normal(&mut self, prompt_vi_normal_string: Option<String>) {
self.default_vi_normal_prompt_indicator = prompt_vi_normal_string;
}
pub fn update_prompt_multiline(&mut self, prompt_multiline_indicator_string: Option<String>) {
self.default_multiline_indicator = prompt_multiline_indicator_string;
}
pub fn update_all_prompt_strings( pub fn update_all_prompt_strings(
&mut self, &mut self,
left_prompt_string: Option<String>, left_prompt_string: Option<String>,
@ -90,6 +61,32 @@ impl NushellPrompt {
self.render_right_prompt_on_last_line = render_right_prompt_on_last_line; self.render_right_prompt_on_last_line = render_right_prompt_on_last_line;
} }
pub fn overlay_all_prompt_strings(
&mut self,
left_prompt_string: Option<String>,
right_prompt_string: Option<String>,
prompt_indicator_string: Option<String>,
prompt_multiline_indicator_string: Option<String>,
prompt_vi: (Option<String>, Option<String>),
render_right_prompt_on_last_line: bool,
) {
let (prompt_vi_insert_string, prompt_vi_normal_string) = prompt_vi;
self.left_prompt_string = left_prompt_string.or(self.left_prompt_string.take());
self.right_prompt_string = right_prompt_string.or(self.right_prompt_string.take());
self.default_prompt_indicator =
prompt_indicator_string.or(self.default_prompt_indicator.take());
self.default_multiline_indicator =
prompt_multiline_indicator_string.or(self.default_multiline_indicator.take());
self.default_vi_insert_prompt_indicator =
prompt_vi_insert_string.or(self.default_vi_insert_prompt_indicator.take());
self.default_vi_normal_prompt_indicator =
prompt_vi_normal_string.or(self.default_vi_normal_prompt_indicator.take());
self.render_right_prompt_on_last_line = render_right_prompt_on_last_line;
}
fn default_wrapped_custom_string(&self, str: String) -> String { fn default_wrapped_custom_string(&self, str: String) -> String {
format!("({str})") format!("({str})")
} }

View file

@ -7,8 +7,6 @@ use nu_protocol::{
Config, PipelineData, Value, Config, PipelineData, Value,
}; };
use reedline::Prompt; use reedline::Prompt;
use std::borrow::Cow;
use std::sync::Arc;
// Name of environment variable where the prompt could be stored // Name of environment variable where the prompt could be stored
pub(crate) const PROMPT_COMMAND: &str = "PROMPT_COMMAND"; pub(crate) const PROMPT_COMMAND: &str = "PROMPT_COMMAND";
@ -153,118 +151,79 @@ pub(crate) fn update_prompt<'prompt>(
ret_val ret_val
} }
struct TransientPrompt { pub(crate) fn make_transient_prompt(
engine_state: Arc<EngineState>,
stack: Stack,
}
/// Try getting `$env.TRANSIENT_PROMPT_<X>`, and get `$env.PROMPT_<X>` if that fails
fn get_transient_prompt_string(
transient_prompt: &str,
prompt: &str,
config: &Config, config: &Config,
engine_state: &EngineState, engine_state: &EngineState,
stack: &mut Stack, stack: &Stack,
) -> Option<String> { nu_prompt: &NushellPrompt,
get_prompt_string(transient_prompt, config, engine_state, stack) ) -> Box<dyn Prompt> {
.or_else(|| get_prompt_string(prompt, config, engine_state, stack)) let mut stack = stack.clone();
} let mut nu_prompt = nu_prompt.clone();
impl Prompt for TransientPrompt { let left_prompt_string =
fn render_prompt_left(&self) -> Cow<str> { get_prompt_string(TRANSIENT_PROMPT_COMMAND, config, engine_state, &mut stack);
let mut nu_prompt = NushellPrompt::new();
let config = &self.engine_state.get_config().clone();
let mut stack = self.stack.clone();
nu_prompt.update_prompt_left(get_transient_prompt_string(
TRANSIENT_PROMPT_COMMAND,
PROMPT_COMMAND,
config,
&self.engine_state,
&mut stack,
));
nu_prompt.render_prompt_left().to_string().into()
}
fn render_prompt_right(&self) -> Cow<str> { // Now that we have the prompt string lets ansify it.
let mut nu_prompt = NushellPrompt::new(); // <133 A><prompt><133 B><command><133 C><command output>
let config = &self.engine_state.get_config().clone(); let left_prompt_string = if config.shell_integration {
let mut stack = self.stack.clone(); if let Some(prompt_string) = left_prompt_string {
nu_prompt.update_prompt_right( Some(format!(
get_transient_prompt_string( "{PRE_PROMPT_MARKER}{prompt_string}{POST_PROMPT_MARKER}"
TRANSIENT_PROMPT_COMMAND_RIGHT, ))
PROMPT_COMMAND_RIGHT, } else {
config, left_prompt_string
&self.engine_state, }
&mut stack, } else {
), left_prompt_string
config.render_right_prompt_on_last_line, };
);
nu_prompt.render_prompt_right().to_string().into()
}
fn render_prompt_indicator(&self, prompt_mode: reedline::PromptEditMode) -> Cow<str> { let right_prompt_string = get_prompt_string(
let mut nu_prompt = NushellPrompt::new(); TRANSIENT_PROMPT_COMMAND_RIGHT,
let config = &self.engine_state.get_config().clone(); config,
let mut stack = self.stack.clone();
nu_prompt.update_prompt_indicator(get_transient_prompt_string(
TRANSIENT_PROMPT_INDICATOR,
PROMPT_INDICATOR,
config,
&self.engine_state,
&mut stack,
));
nu_prompt.update_prompt_vi_insert(get_transient_prompt_string(
TRANSIENT_PROMPT_INDICATOR_VI_INSERT,
PROMPT_INDICATOR_VI_INSERT,
config,
&self.engine_state,
&mut stack,
));
nu_prompt.update_prompt_vi_normal(get_transient_prompt_string(
TRANSIENT_PROMPT_INDICATOR_VI_NORMAL,
PROMPT_INDICATOR_VI_NORMAL,
config,
&self.engine_state,
&mut stack,
));
nu_prompt
.render_prompt_indicator(prompt_mode)
.to_string()
.into()
}
fn render_prompt_multiline_indicator(&self) -> Cow<str> {
let mut nu_prompt = NushellPrompt::new();
let config = &self.engine_state.get_config().clone();
let mut stack = self.stack.clone();
nu_prompt.update_prompt_multiline(get_transient_prompt_string(
TRANSIENT_PROMPT_MULTILINE_INDICATOR,
PROMPT_MULTILINE_INDICATOR,
config,
&self.engine_state,
&mut stack,
));
nu_prompt
.render_prompt_multiline_indicator()
.to_string()
.into()
}
fn render_prompt_history_search_indicator(
&self,
history_search: reedline::PromptHistorySearch,
) -> Cow<str> {
NushellPrompt::new()
.render_prompt_history_search_indicator(history_search)
.to_string()
.into()
}
}
/// Construct the transient prompt
pub(crate) fn transient_prompt(engine_state: Arc<EngineState>, stack: &Stack) -> Box<dyn Prompt> {
Box::new(TransientPrompt {
engine_state, engine_state,
stack: stack.clone(), &mut stack,
}) );
let prompt_indicator_string =
get_prompt_string(TRANSIENT_PROMPT_INDICATOR, config, engine_state, &mut stack);
let prompt_multiline_string = get_prompt_string(
TRANSIENT_PROMPT_MULTILINE_INDICATOR,
config,
engine_state,
&mut stack,
);
let prompt_vi_insert_string = get_prompt_string(
TRANSIENT_PROMPT_INDICATOR_VI_INSERT,
config,
engine_state,
&mut stack,
);
let prompt_vi_normal_string = get_prompt_string(
TRANSIENT_PROMPT_INDICATOR_VI_NORMAL,
config,
engine_state,
&mut stack,
);
// apply the other indicators
nu_prompt.overlay_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!(
"make_transient_prompt {}:{}:{}",
file!(),
line!(),
column!()
);
Box::new(nu_prompt)
} }

View file

@ -267,11 +267,7 @@ pub fn evaluate_repl(
.with_quick_completions(config.quick_completions) .with_quick_completions(config.quick_completions)
.with_partial_completions(config.partial_completions) .with_partial_completions(config.partial_completions)
.with_ansi_colors(config.use_ansi_coloring) .with_ansi_colors(config.use_ansi_coloring)
.with_cursor_config(cursor_config) .with_cursor_config(cursor_config);
.with_transient_prompt(prompt_update::transient_prompt(
engine_reference.clone(),
stack,
));
perf( perf(
"reedline builder", "reedline builder",
start_time, start_time,
@ -423,8 +419,11 @@ pub fn evaluate_repl(
); );
start_time = std::time::Instant::now(); start_time = std::time::Instant::now();
// Now we compute the prompt after running the hooks
let config = &engine_state.get_config().clone(); let config = &engine_state.get_config().clone();
let prompt = prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt); prompt_update::update_prompt(config, engine_state, stack, &mut nu_prompt);
let transient_prompt =
prompt_update::make_transient_prompt(config, engine_state, stack, &nu_prompt);
perf( perf(
"update_prompt", "update_prompt",
start_time, start_time,
@ -437,7 +436,8 @@ pub fn evaluate_repl(
entry_num += 1; entry_num += 1;
start_time = std::time::Instant::now(); start_time = std::time::Instant::now();
let input = line_editor.read_line(prompt); line_editor = line_editor.with_transient_prompt(transient_prompt);
let input = line_editor.read_line(&nu_prompt);
let shell_integration = config.shell_integration; let shell_integration = config.shell_integration;
match input { match input {