nushell/crates/nu-cli/src/prompt.rs
Darren Schroeder 5466da3b52
cleanup osc calls for shell_integration (#12810)
# Description

This PR is a continuation of #12629 and meant to address [Reilly's
stated
issue](https://github.com/nushell/nushell/pull/12629#issuecomment-2099660609).

With this PR, nushell should work more consistently with WezTerm on
Windows. However, that means continued scrolling with typing if osc133
is enabled. If it's possible to run WezTerm inside of vscode, then
having osc633 enabled will also cause the display to scroll with every
character typed. I think the cause of this is that reedline paints the
entire prompt on each character typed. We need to figure out how to fix
that, but that's in reedline.

For my purposes, I keep osc133 and osc633 set to true and don't use
WezTerm on Windows.

Thanks @rgwood for reporting the issue. I found several logic errors.
It's often good to come back to PRs and look at them with fresh eyes. I
think this is pretty close to logically correct now. However, I'm
approaching burn out on ansi escape codes so i could've missed
something.

Kudos to [escape-artist](https://github.com/rgwood/escape-artist) for
helping me debug an ansi escape codes that are actually being sent to
the terminal. It was an invaluable tool.

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

# Tests + Formatting
<!--
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
> ```
-->

# After Submitting
<!-- If your PR had any user-facing changes, update [the
documentation](https://github.com/nushell/nushell.github.io) after the
PR is merged, if necessary. This will help us keep the docs up to date.
-->
2024-05-08 13:34:04 -05:00

213 lines
7.2 KiB
Rust

use crate::prompt_update::{
POST_PROMPT_MARKER, PRE_PROMPT_MARKER, VSCODE_POST_PROMPT_MARKER, VSCODE_PRE_PROMPT_MARKER,
};
use nu_protocol::{
engine::{EngineState, Stack},
Value,
};
#[cfg(windows)]
use nu_utils::enable_vt_processing;
use reedline::{
DefaultPrompt, Prompt, PromptEditMode, PromptHistorySearch, PromptHistorySearchStatus,
PromptViMode,
};
use std::borrow::Cow;
/// Nushell prompt definition
#[derive(Clone)]
pub struct NushellPrompt {
shell_integration_osc133: bool,
shell_integration_osc633: bool,
left_prompt_string: Option<String>,
right_prompt_string: Option<String>,
default_prompt_indicator: Option<String>,
default_vi_insert_prompt_indicator: Option<String>,
default_vi_normal_prompt_indicator: Option<String>,
default_multiline_indicator: Option<String>,
render_right_prompt_on_last_line: bool,
engine_state: EngineState,
stack: Stack,
}
impl NushellPrompt {
pub fn new(
shell_integration_osc133: bool,
shell_integration_osc633: bool,
engine_state: EngineState,
stack: Stack,
) -> NushellPrompt {
NushellPrompt {
shell_integration_osc133,
shell_integration_osc633,
left_prompt_string: None,
right_prompt_string: None,
default_prompt_indicator: None,
default_vi_insert_prompt_indicator: None,
default_vi_normal_prompt_indicator: None,
default_multiline_indicator: None,
render_right_prompt_on_last_line: false,
engine_state,
stack,
}
}
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(
&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;
self.right_prompt_string = right_prompt_string;
self.default_prompt_indicator = prompt_indicator_string;
self.default_multiline_indicator = prompt_multiline_indicator_string;
self.default_vi_insert_prompt_indicator = prompt_vi_insert_string;
self.default_vi_normal_prompt_indicator = prompt_vi_normal_string;
self.render_right_prompt_on_last_line = render_right_prompt_on_last_line;
}
fn default_wrapped_custom_string(&self, str: String) -> String {
format!("({str})")
}
}
impl Prompt for NushellPrompt {
fn render_prompt_left(&self) -> Cow<str> {
#[cfg(windows)]
{
let _ = enable_vt_processing();
}
if let Some(prompt_string) = &self.left_prompt_string {
prompt_string.replace('\n', "\r\n").into()
} else {
let default = DefaultPrompt::default();
let prompt = default
.render_prompt_left()
.to_string()
.replace('\n', "\r\n");
if self.shell_integration_osc633 {
if self.stack.get_env_var(&self.engine_state, "TERM_PROGRAM")
== Some(Value::test_string("vscode"))
{
// We're in vscode and we have osc633 enabled
format!("{VSCODE_PRE_PROMPT_MARKER}{prompt}{VSCODE_POST_PROMPT_MARKER}").into()
} else if self.shell_integration_osc133 {
// If we're in VSCode but we don't find the env var, but we have osc133 set, then use it
format!("{PRE_PROMPT_MARKER}{prompt}{POST_PROMPT_MARKER}").into()
} else {
prompt.into()
}
} else if self.shell_integration_osc133 {
format!("{PRE_PROMPT_MARKER}{prompt}{POST_PROMPT_MARKER}").into()
} else {
prompt.into()
}
}
}
fn render_prompt_right(&self) -> Cow<str> {
if let Some(prompt_string) = &self.right_prompt_string {
prompt_string.replace('\n', "\r\n").into()
} else {
let default = DefaultPrompt::default();
default
.render_prompt_right()
.to_string()
.replace('\n', "\r\n")
.into()
}
}
fn render_prompt_indicator(&self, edit_mode: PromptEditMode) -> Cow<str> {
match edit_mode {
PromptEditMode::Default => match &self.default_prompt_indicator {
Some(indicator) => indicator,
None => "> ",
}
.into(),
PromptEditMode::Emacs => match &self.default_prompt_indicator {
Some(indicator) => indicator,
None => "> ",
}
.into(),
PromptEditMode::Vi(vi_mode) => match vi_mode {
PromptViMode::Normal => match &self.default_vi_normal_prompt_indicator {
Some(indicator) => indicator,
None => "> ",
},
PromptViMode::Insert => match &self.default_vi_insert_prompt_indicator {
Some(indicator) => indicator,
None => ": ",
},
}
.into(),
PromptEditMode::Custom(str) => self.default_wrapped_custom_string(str).into(),
}
}
fn render_prompt_multiline_indicator(&self) -> Cow<str> {
match &self.default_multiline_indicator {
Some(indicator) => indicator,
None => "::: ",
}
.into()
}
fn render_prompt_history_search_indicator(
&self,
history_search: PromptHistorySearch,
) -> Cow<str> {
let prefix = match history_search.status {
PromptHistorySearchStatus::Passing => "",
PromptHistorySearchStatus::Failing => "failing ",
};
Cow::Owned(format!(
"({}reverse-search: {})",
prefix, history_search.term
))
}
fn right_prompt_on_last_line(&self) -> bool {
self.render_right_prompt_on_last_line
}
}