From 414216edfa58c68a66d6a1984f8c2349ab782af0 Mon Sep 17 00:00:00 2001 From: Yash Thakur <45539777+ysthakur@users.noreply.github.com> Date: Fri, 22 Sep 2023 15:35:09 -0400 Subject: [PATCH] Transient prompt (#10391) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Description This PR uses environment variables to enable and set a transient prompt, which lets you draw a different prompt once you've entered a command and you've moved on to the next line. This is useful if you have a fancy two-line prompt with a bunch of info about time and git status that you don't really need in your scrollback buffer. Here's a screenshot. You can see how my usual prompt has two lines and would take up a lot more space if every past command also used the full prompt, but reducing past prompts to `🚀` or `>` makes it take up less space. ![image](https://github.com/nushell/nushell/assets/45539777/dde8d0f5-f95f-4529-9a14-b7919bd51126) I added the following lines to my `env.nu` to get that rocket as the prompt initially: ```nu $env.TRANSIENT_PROMPT_COMMAND = {|| "" } $env.TRANSIENT_PROMPT_INDICATOR = {|| open --raw "~/.prompt-indicator" } $env.TRANSIENT_PROMPT_INDICATOR_VI_INSERT = $env.TRANSIENT_PROMPT_INDICATOR ``` ## User-Facing Changes If you want to change a segment of the prompt, set the corresponding `TRANSIENT_PROMPT_*` variable. ## Problems/Things to Consider: - The transient prompt clones the `Stack` at the very beginning of the session and keeps that around. I'm not sure if that could cause problems, but if so, it could probably take an `Arc` instead. - This isn't truly a problem, but now there's even more environment variables, which is kinda annoying. - There might be some performance issues with creating a new `NushellPrompt` object and cloning the `Stack` for every segment of the transient prompt. What's more, the transient prompt is added to the `Reedline` object whether or not the user has enabled transient prompt, so if there are indeed performance issues, simply disabling the transient prompt won't help. - Perhaps instead of a separate `TRANSIENT_PROMPT_INDICATOR_VI_INSERT` and `TRANSIENT_PROMPT_INDICATOR_VI_NORMAL`, `TRANSIENT_PROMPT_INDICATOR` could be used for both (if it exists). Insert and normal mode don't really matter for previously entered commands. --- crates/nu-cli/src/prompt_update.rs | 127 ++++++++++++++++++ crates/nu-cli/src/repl.rs | 6 +- .../nu-utils/src/sample_config/default_env.nu | 12 ++ 3 files changed, 144 insertions(+), 1 deletion(-) diff --git a/crates/nu-cli/src/prompt_update.rs b/crates/nu-cli/src/prompt_update.rs index 127c02458..d212ab2de 100644 --- a/crates/nu-cli/src/prompt_update.rs +++ b/crates/nu-cli/src/prompt_update.rs @@ -7,6 +7,8 @@ use nu_protocol::{ Config, PipelineData, Value, }; use reedline::Prompt; +use std::borrow::Cow; +use std::sync::Arc; // Name of environment variable where the prompt could be stored pub(crate) const PROMPT_COMMAND: &str = "PROMPT_COMMAND"; @@ -15,6 +17,15 @@ 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><133 B><133 C> const PRE_PROMPT_MARKER: &str = "\x1b]133;A\x1b\\"; @@ -145,3 +156,119 @@ pub(crate) fn update_prompt<'prompt>( ret_val } + +struct TransientPrompt { + engine_state: Arc, + stack: Stack, +} + +/// Try getting `$env.TRANSIENT_PROMPT_`, and get `$env.PROMPT_` if that fails +fn get_transient_prompt_string( + transient_prompt: &str, + prompt: &str, + config: &Config, + engine_state: &EngineState, + stack: &mut Stack, +) -> Option { + get_prompt_string(transient_prompt, config, engine_state, stack) + .or_else(|| get_prompt_string(prompt, config, engine_state, stack)) +} + +impl Prompt for TransientPrompt { + fn render_prompt_left(&self) -> Cow { + 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 { + let mut nu_prompt = NushellPrompt::new(); + let config = &self.engine_state.get_config().clone(); + let mut stack = self.stack.clone(); + nu_prompt.update_prompt_right( + get_transient_prompt_string( + TRANSIENT_PROMPT_COMMAND_RIGHT, + PROMPT_COMMAND_RIGHT, + config, + &self.engine_state, + &mut stack, + ), + 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 { + let mut nu_prompt = NushellPrompt::new(); + let config = &self.engine_state.get_config().clone(); + 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 { + 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 { + NushellPrompt::new() + .render_prompt_history_search_indicator(history_search) + .to_string() + .into() + } +} + +/// Construct the transient prompt +pub(crate) fn transient_prompt(engine_state: Arc, stack: &Stack) -> Box { + Box::new(TransientPrompt { + engine_state, + stack: stack.clone(), + }) +} diff --git a/crates/nu-cli/src/repl.rs b/crates/nu-cli/src/repl.rs index 7898285bb..9341a1b33 100644 --- a/crates/nu-cli/src/repl.rs +++ b/crates/nu-cli/src/repl.rs @@ -271,7 +271,11 @@ pub fn evaluate_repl( .with_quick_completions(config.quick_completions) .with_partial_completions(config.partial_completions) .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( "reedline builder", start_time, diff --git a/crates/nu-utils/src/sample_config/default_env.nu b/crates/nu-utils/src/sample_config/default_env.nu index 12a24ade1..9935859be 100644 --- a/crates/nu-utils/src/sample_config/default_env.nu +++ b/crates/nu-utils/src/sample_config/default_env.nu @@ -47,6 +47,18 @@ $env.PROMPT_INDICATOR_VI_INSERT = {|| ": " } $env.PROMPT_INDICATOR_VI_NORMAL = {|| "> " } $env.PROMPT_MULTILINE_INDICATOR = {|| "::: " } +# If you want previously entered commands to have a different prompt from the usual one, +# you can uncomment one or more of the following lines. +# This can be useful if you have a 2-line prompt and it's taking up a lot of space +# because every command entered takes up 2 lines instead of 1. You can then uncomment +# the line below so that previously entered commands show with a single `🚀`. +# $env.TRANSIENT_PROMPT_COMMAND = {|| "🚀 " } +# $env.TRANSIENT_PROMPT_INDICATOR = {|| "" } +# $env.TRANSIENT_PROMPT_INDICATOR_VI_INSERT = {|| "" } +# $env.TRANSIENT_PROMPT_INDICATOR_VI_NORMAL = {|| "" } +# $env.TRANSIENT_PROMPT_MULTILINE_INDICATOR = {|| "" } +# $env.TRANSIENT_PROMPT_COMMAND_RIGHT = {|| "" } + # Specifies how environment variables are: # - converted from a string to a value on Nushell startup (from_string) # - converted from a value back to a string when running external commands (to_string)