Don't redraw prompt when transient prompt disabled (#10788)

# Description
<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

moonlander pointed out in Discord that the transient prompt feature
added in release 0.86 (implemented in #10391) is causing the normal
prompt to be redrawn when the transient prompt variables are unset or
set to null. This PR is for fixing that, although it's more of a bandaid
fix. Maybe the transient prompt feature should be taken out entirely for
now so more thought can be given to its implementation.

Previously, I'd thought that when reedline redraws the prompt after a
command is entered, it's a whole new prompt, but apparently it's
actually the same prompt as the current line (?). So now, `nu_prompt` in
`repl.rs` is an `Arc<RwLock<NushellPrompt>>` (rather than just a
`NushellPrompt`), and this `Arc` is shared with the `TransientPrompt`
object so that if it can't find one of the `TRANSIENT_PROMPT_*`
variables, it uses a segment from `NushellPrompt` rather than
re-evaluate `PROMPT_COMMAND`, `PROMPT_COMMAND_RIGHT`, etc. Using an
`RwLock` means that there's a bunch of `.expect()`s all over the place,
which is not nice. It could perhaps be avoided with some changes on the
reedline side.

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

`$env.PROMPT_COMMAND` (and other such variables) should no longer be
executed twice if the corresponding `$env.TRANSIENT_PROMPT_*` variable
is not set.

<!--
Don't forget to add tests that cover your changes.

Make sure you've run and fixed any issues with these commands:

- `cargo fmt --all -- --check` to check standard code formatting (`cargo
fmt --all` applies these changes)
- `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used` to
check that you're using the standard code style
- `cargo test --workspace` to check that all tests pass (on Windows make
sure to [enable developer
mode](https://learn.microsoft.com/en-us/windows/apps/get-started/developer-mode-features-and-debugging))
- `cargo run -- -c "use std testing; testing run-tests --path
crates/nu-std"` to run the tests for the standard library

> **Note**
> from `nushell` you can also use the `toolkit` as follows
> ```bash
> use toolkit.nu # or use an `env_change` hook to activate it
automatically
> toolkit check pr
> ```
-->

# Steps to reproduce

Described by moonlander in Discord
[here](https://discord.com/channels/601130461678272522/614593951969574961/1164928022126792844).

Adding this to `env.nu` will result in `11` being added to
`/tmp/run_count` every time any command is run. The expected behavior is
a single `1` being added to `/tmp/run_count` instead of two. The prompt
command should not be executed again when the prompt is redrawn after a
command is executed.

```nu
$env.PROMPT_COMMAND = {||
  touch /tmp/run_count
  '1' | save /tmp/run_count --append
  '>'
}

# $env.TRANSIENT_PROMPT_COMMAND not set
```

If the following is added to `env.nu`, then `12` will be added to
`/tmp/run_count` every time any command is run, which is expected
behavior because the normal prompt command must be displayed the first
time the prompt is shown, then the transient prompt command is run when
the prompt is redrawn.
```nu
$env.TRANSIENT_PROMPT_COMMAND = {||
  touch /tmp/run_count
  '2' | save /tmp/run_count --append
  '>'
}
```

Here's a screenshot of what adding that first snippet looks like (`cargo
run` in the `main` branch):

![image](https://github.com/nushell/nushell/assets/45539777/b27a5c07-55b4-43c7-8a2c-0deba2d9d53a)


Here's a screenshot of what it looks like with this PR (only one `1` is
added to `/tmp/run_count` each time):

![image](https://github.com/nushell/nushell/assets/45539777/2b5c0a3a-8566-4428-9fda-1ffcc1dd6ae3)
This commit is contained in:
Yash Thakur 2023-12-15 14:41:44 -05:00 committed by GitHub
parent 3c6fac059e
commit 87717b9ddd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 74 additions and 59 deletions

View File

@ -8,7 +8,7 @@ use nu_protocol::{
}; };
use reedline::Prompt; use reedline::Prompt;
use std::borrow::Cow; use std::borrow::Cow;
use std::sync::Arc; use std::sync::{Arc, RwLock};
// 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";
@ -98,12 +98,12 @@ fn get_prompt_string(
}) })
} }
pub(crate) fn update_prompt<'prompt>( pub(crate) fn update_prompt(
config: &Config, config: &Config,
engine_state: &EngineState, engine_state: &EngineState,
stack: &Stack, stack: &Stack,
nu_prompt: &'prompt mut NushellPrompt, nu_prompt: Arc<RwLock<NushellPrompt>>,
) -> &'prompt dyn Prompt { ) {
let mut stack = stack.clone(); let mut stack = stack.clone();
let left_prompt_string = get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack); let left_prompt_string = get_prompt_string(PROMPT_COMMAND, config, engine_state, &mut stack);
@ -138,95 +138,96 @@ pub(crate) fn update_prompt<'prompt>(
get_prompt_string(PROMPT_INDICATOR_VI_NORMAL, config, engine_state, &mut stack); get_prompt_string(PROMPT_INDICATOR_VI_NORMAL, config, engine_state, &mut stack);
// apply the other indicators // apply the other indicators
nu_prompt.update_all_prompt_strings( nu_prompt
left_prompt_string, .write()
right_prompt_string, .expect("Could not lock on nu_prompt to update")
prompt_indicator_string, .update_all_prompt_strings(
prompt_multiline_string, left_prompt_string,
(prompt_vi_insert_string, prompt_vi_normal_string), right_prompt_string,
config.render_right_prompt_on_last_line, prompt_indicator_string,
); prompt_multiline_string,
(prompt_vi_insert_string, prompt_vi_normal_string),
config.render_right_prompt_on_last_line,
);
let ret_val = nu_prompt as &dyn Prompt;
trace!("update_prompt {}:{}:{}", file!(), line!(), column!()); trace!("update_prompt {}:{}:{}", file!(), line!(), column!());
ret_val
} }
struct TransientPrompt { struct TransientPrompt {
prompt_lock: Arc<RwLock<NushellPrompt>>,
engine_state: Arc<EngineState>, engine_state: Arc<EngineState>,
stack: Stack, stack: Stack,
} }
/// Try getting `$env.TRANSIENT_PROMPT_<X>`, and get `$env.PROMPT_<X>` if that fails impl TransientPrompt {
fn get_transient_prompt_string( fn new_prompt(&self) -> NushellPrompt {
transient_prompt: &str, if let Ok(prompt) = self.prompt_lock.read() {
prompt: &str, prompt.clone()
config: &Config, } else {
engine_state: &EngineState, NushellPrompt::new()
stack: &mut Stack, }
) -> Option<String> { }
get_prompt_string(transient_prompt, config, engine_state, stack)
.or_else(|| get_prompt_string(prompt, config, engine_state, stack))
} }
impl Prompt for TransientPrompt { impl Prompt for TransientPrompt {
fn render_prompt_left(&self) -> Cow<str> { fn render_prompt_left(&self) -> Cow<str> {
let mut nu_prompt = NushellPrompt::new(); let mut nu_prompt = self.new_prompt();
let config = &self.engine_state.get_config().clone(); let config = &self.engine_state.get_config().clone();
let mut stack = self.stack.clone(); let mut stack = self.stack.clone();
nu_prompt.update_prompt_left(get_transient_prompt_string( if let Some(s) = get_prompt_string(
TRANSIENT_PROMPT_COMMAND, TRANSIENT_PROMPT_COMMAND,
PROMPT_COMMAND,
config, config,
&self.engine_state, &self.engine_state,
&mut stack, &mut stack,
)); ) {
nu_prompt.update_prompt_left(Some(s))
}
nu_prompt.render_prompt_left().to_string().into() nu_prompt.render_prompt_left().to_string().into()
} }
fn render_prompt_right(&self) -> Cow<str> { fn render_prompt_right(&self) -> Cow<str> {
let mut nu_prompt = NushellPrompt::new(); let mut nu_prompt = self.new_prompt();
let config = &self.engine_state.get_config().clone(); let config = &self.engine_state.get_config().clone();
let mut stack = self.stack.clone(); let mut stack = self.stack.clone();
nu_prompt.update_prompt_right( if let Some(s) = get_prompt_string(
get_transient_prompt_string( TRANSIENT_PROMPT_COMMAND_RIGHT,
TRANSIENT_PROMPT_COMMAND_RIGHT, config,
PROMPT_COMMAND_RIGHT, &self.engine_state,
config, &mut stack,
&self.engine_state, ) {
&mut stack, nu_prompt.update_prompt_right(Some(s), config.render_right_prompt_on_last_line)
), }
config.render_right_prompt_on_last_line,
);
nu_prompt.render_prompt_right().to_string().into() nu_prompt.render_prompt_right().to_string().into()
} }
fn render_prompt_indicator(&self, prompt_mode: reedline::PromptEditMode) -> Cow<str> { fn render_prompt_indicator(&self, prompt_mode: reedline::PromptEditMode) -> Cow<str> {
let mut nu_prompt = NushellPrompt::new(); let mut nu_prompt = self.new_prompt();
let config = &self.engine_state.get_config().clone(); let config = &self.engine_state.get_config().clone();
let mut stack = self.stack.clone(); let mut stack = self.stack.clone();
nu_prompt.update_prompt_indicator(get_transient_prompt_string( if let Some(s) = get_prompt_string(
TRANSIENT_PROMPT_INDICATOR, TRANSIENT_PROMPT_INDICATOR,
PROMPT_INDICATOR,
config, config,
&self.engine_state, &self.engine_state,
&mut stack, &mut stack,
)); ) {
nu_prompt.update_prompt_vi_insert(get_transient_prompt_string( nu_prompt.update_prompt_indicator(Some(s))
}
if let Some(s) = get_prompt_string(
TRANSIENT_PROMPT_INDICATOR_VI_INSERT, TRANSIENT_PROMPT_INDICATOR_VI_INSERT,
PROMPT_INDICATOR_VI_INSERT,
config, config,
&self.engine_state, &self.engine_state,
&mut stack, &mut stack,
)); ) {
nu_prompt.update_prompt_vi_normal(get_transient_prompt_string( nu_prompt.update_prompt_vi_insert(Some(s))
}
if let Some(s) = get_prompt_string(
TRANSIENT_PROMPT_INDICATOR_VI_NORMAL, TRANSIENT_PROMPT_INDICATOR_VI_NORMAL,
PROMPT_INDICATOR_VI_NORMAL,
config, config,
&self.engine_state, &self.engine_state,
&mut stack, &mut stack,
)); ) {
nu_prompt.update_prompt_vi_normal(Some(s))
}
nu_prompt nu_prompt
.render_prompt_indicator(prompt_mode) .render_prompt_indicator(prompt_mode)
.to_string() .to_string()
@ -234,16 +235,17 @@ impl Prompt for TransientPrompt {
} }
fn render_prompt_multiline_indicator(&self) -> Cow<str> { fn render_prompt_multiline_indicator(&self) -> Cow<str> {
let mut nu_prompt = NushellPrompt::new(); let mut nu_prompt = self.new_prompt();
let config = &self.engine_state.get_config().clone(); let config = &self.engine_state.get_config().clone();
let mut stack = self.stack.clone(); let mut stack = self.stack.clone();
nu_prompt.update_prompt_multiline(get_transient_prompt_string( if let Some(s) = get_prompt_string(
TRANSIENT_PROMPT_MULTILINE_INDICATOR, TRANSIENT_PROMPT_MULTILINE_INDICATOR,
PROMPT_MULTILINE_INDICATOR,
config, config,
&self.engine_state, &self.engine_state,
&mut stack, &mut stack,
)); ) {
nu_prompt.update_prompt_multiline(Some(s))
}
nu_prompt nu_prompt
.render_prompt_multiline_indicator() .render_prompt_multiline_indicator()
.to_string() .to_string()
@ -262,8 +264,13 @@ impl Prompt for TransientPrompt {
} }
/// Construct the transient prompt /// Construct the transient prompt
pub(crate) fn transient_prompt(engine_state: Arc<EngineState>, stack: &Stack) -> Box<dyn Prompt> { pub(crate) fn transient_prompt(
prompt_lock: Arc<RwLock<NushellPrompt>>,
engine_state: Arc<EngineState>,
stack: &Stack,
) -> Box<dyn Prompt> {
Box::new(TransientPrompt { Box::new(TransientPrompt {
prompt_lock,
engine_state, engine_state,
stack: stack.clone(), stack: stack.clone(),
}) })

View File

@ -29,7 +29,7 @@ use std::{
env::temp_dir, env::temp_dir,
io::{self, IsTerminal, Write}, io::{self, IsTerminal, Write},
path::Path, path::Path,
sync::atomic::Ordering, sync::{atomic::Ordering, Arc, RwLock},
time::Instant, time::Instant,
}; };
use sysinfo::SystemExt; use sysinfo::SystemExt;
@ -68,7 +68,7 @@ pub fn evaluate_repl(
let mut entry_num = 0; let mut entry_num = 0;
let mut nu_prompt = NushellPrompt::new(); let nu_prompt = Arc::new(RwLock::new(NushellPrompt::new()));
let start_time = std::time::Instant::now(); let start_time = std::time::Instant::now();
// Translate environment variables from Strings to Values // Translate environment variables from Strings to Values
@ -269,6 +269,7 @@ pub fn evaluate_repl(
.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( .with_transient_prompt(prompt_update::transient_prompt(
Arc::clone(&nu_prompt),
engine_reference.clone(), engine_reference.clone(),
stack, stack,
)); ));
@ -424,7 +425,7 @@ pub fn evaluate_repl(
start_time = std::time::Instant::now(); start_time = std::time::Instant::now();
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, Arc::clone(&nu_prompt));
perf( perf(
"update_prompt", "update_prompt",
start_time, start_time,
@ -437,7 +438,14 @@ 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); let input = {
line_editor.read_line(
&nu_prompt
.read()
.expect("Could not lock on prompt to pass to read_line")
.to_owned(),
)
};
let shell_integration = config.shell_integration; let shell_integration = config.shell_integration;
match input { match input {