1use crate::NushellPrompt;
2use log::{info, trace};
3use nu_engine::ClosureEvalOnce;
4use nu_protocol::{
5 Config, PipelineData, Value,
6 engine::{EngineState, PromptContents, Stack},
7 report_shell_error,
8};
9use reedline::Prompt;
10use std::sync::Arc;
11
12pub(crate) const PROMPT_COMMAND: &str = "PROMPT_COMMAND";
14pub(crate) const PROMPT_COMMAND_RIGHT: &str = "PROMPT_COMMAND_RIGHT";
15pub(crate) const PROMPT_INDICATOR: &str = "PROMPT_INDICATOR";
16pub(crate) const PROMPT_INDICATOR_VI_INSERT: &str = "PROMPT_INDICATOR_VI_INSERT";
17pub(crate) const PROMPT_INDICATOR_VI_NORMAL: &str = "PROMPT_INDICATOR_VI_NORMAL";
18pub(crate) const PROMPT_MULTILINE_INDICATOR: &str = "PROMPT_MULTILINE_INDICATOR";
19pub(crate) const TRANSIENT_PROMPT_COMMAND: &str = "TRANSIENT_PROMPT_COMMAND";
20pub(crate) const TRANSIENT_PROMPT_COMMAND_RIGHT: &str = "TRANSIENT_PROMPT_COMMAND_RIGHT";
21pub(crate) const TRANSIENT_PROMPT_INDICATOR: &str = "TRANSIENT_PROMPT_INDICATOR";
22pub(crate) const TRANSIENT_PROMPT_INDICATOR_VI_INSERT: &str =
23 "TRANSIENT_PROMPT_INDICATOR_VI_INSERT";
24pub(crate) const TRANSIENT_PROMPT_INDICATOR_VI_NORMAL: &str =
25 "TRANSIENT_PROMPT_INDICATOR_VI_NORMAL";
26pub(crate) const TRANSIENT_PROMPT_MULTILINE_INDICATOR: &str =
27 "TRANSIENT_PROMPT_MULTILINE_INDICATOR";
28
29pub(crate) const PRE_EXECUTION_MARKER: &str = "\x1b]133;C\x1b\\";
37pub(crate) const POST_EXECUTION_MARKER_PREFIX: &str = "\x1b]133;D;";
38pub(crate) const POST_EXECUTION_MARKER_SUFFIX: &str = "\x1b\\";
39
40pub(crate) const VSCODE_PRE_EXECUTION_MARKER: &str = "\x1b]633;C\x1b\\";
42pub(crate) const VSCODE_POST_EXECUTION_MARKER_PREFIX: &str = "\x1b]633;D;";
43pub(crate) const VSCODE_POST_EXECUTION_MARKER_SUFFIX: &str = "\x1b\\";
44pub(crate) const VSCODE_COMMANDLINE_MARKER_PREFIX: &str = "\x1b]633;E;";
45pub(crate) const VSCODE_COMMANDLINE_MARKER_SUFFIX: &str = "\x1b\\";
46pub(crate) const VSCODE_CWD_PROPERTY_MARKER_PREFIX: &str = "\x1b]633;P;Cwd=";
47pub(crate) const VSCODE_CWD_PROPERTY_MARKER_SUFFIX: &str = "\x1b\\";
48
49pub(crate) const RESET_APPLICATION_MODE: &str = "\x1b[?1l";
51
52fn get_prompt_string(
53 prompt: &str,
54 config: &Config,
55 engine_state: &EngineState,
56 stack: &mut Stack,
57) -> Option<String> {
58 let mut output = match stack.get_env_var(engine_state, prompt)? {
59 Value::String { val, .. } => val.clone(),
60 Value::Closure { val, .. } => {
61 let result = ClosureEvalOnce::new(engine_state, stack, val.as_ref().clone())
62 .run_with_input(PipelineData::empty());
63
64 trace!(
65 "get_prompt_string (block) {}:{}:{}",
66 file!(),
67 line!(),
68 column!()
69 );
70
71 let result_string = result
72 .map_err(|err| report_shell_error(None, engine_state, &err))
73 .ok()
74 .and_then(|pd| pd.collect_string("", config).ok());
75
76 result_string?
77 }
78 _ => return None,
79 };
80
81 if output.is_empty() && prompt == PROMPT_COMMAND_RIGHT {
84 output.insert_str(0, "\x1b[0m")
85 };
86
87 info!("{}:{}:{} {:?}", file!(), line!(), column!(), output);
89
90 Some(output)
91}
92
93pub fn update_prompt(config: &Config, engine_state: &EngineState, stack: &mut Stack) {
97 let new_contents = build_prompt_contents(config, engine_state, stack);
98
99 engine_state.prompt_state.set_contents(new_contents);
101
102 trace!("update_prompt {}:{}:{}", file!(), line!(), column!());
103}
104
105fn build_prompt_contents(
106 config: &Config,
107 engine_state: &EngineState,
108 stack: &mut Stack,
109) -> PromptContents {
110 let mut fetch_prompt =
111 |prompt_type| get_prompt_string(prompt_type, config, engine_state, stack).map(Arc::from);
112
113 PromptContents {
114 left: fetch_prompt(PROMPT_COMMAND),
115 right: fetch_prompt(PROMPT_COMMAND_RIGHT),
116 indicator: fetch_prompt(PROMPT_INDICATOR),
117 vi_insert: fetch_prompt(PROMPT_INDICATOR_VI_INSERT),
118 vi_normal: fetch_prompt(PROMPT_INDICATOR_VI_NORMAL),
119 multiline: fetch_prompt(PROMPT_MULTILINE_INDICATOR),
120 render_right_on_last_line: config.render_right_prompt_on_last_line,
121 }
122}
123
124pub(crate) fn make_transient_prompt(
134 config: &Config,
135 engine_state: &EngineState,
136 stack: &mut Stack,
137) -> Box<dyn Prompt> {
138 let mut fetch_transient =
139 |env_var| get_prompt_string(env_var, config, engine_state, stack).map(Arc::from);
140
141 let overrides = PromptContents {
142 left: fetch_transient(TRANSIENT_PROMPT_COMMAND),
143 right: fetch_transient(TRANSIENT_PROMPT_COMMAND_RIGHT),
144 indicator: fetch_transient(TRANSIENT_PROMPT_INDICATOR),
145 vi_insert: fetch_transient(TRANSIENT_PROMPT_INDICATOR_VI_INSERT),
146 vi_normal: fetch_transient(TRANSIENT_PROMPT_INDICATOR_VI_NORMAL),
147 multiline: fetch_transient(TRANSIENT_PROMPT_MULTILINE_INDICATOR),
148 render_right_on_last_line: false,
151 };
152
153 Box::new(NushellPrompt::transient(
154 engine_state.prompt_state.clone(),
155 overrides,
156 ))
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162 use nu_protocol::Span;
163
164 #[test]
165 fn update_prompt_does_not_embed_osc_markers() {
166 let mut config = Config::default();
167 config.shell_integration.osc133 = true;
168
169 let engine_state = EngineState::new();
170 let mut stack = Stack::new();
171 stack.add_env_var(
172 PROMPT_COMMAND.into(),
173 Value::string("test", Span::test_data()),
174 );
175
176 update_prompt(&config, &engine_state, &mut stack);
177
178 let nu_prompt = NushellPrompt::shared(engine_state.prompt_state.clone());
179 assert_eq!(nu_prompt.render_prompt_left(), "test");
180 }
181
182 #[test]
183 fn transient_prompt_override_still_wins_over_the_live_baseline() {
184 use nu_protocol::engine::PromptSegment;
185
186 let config = Config::default();
187 let engine_state = EngineState::new();
188 let mut stack = Stack::new();
189 stack.add_env_var(
190 TRANSIENT_PROMPT_INDICATOR.into(),
191 Value::string("transient> ", Span::test_data()),
192 );
193
194 let transient_prompt = make_transient_prompt(&config, &engine_state, &mut stack);
195
196 engine_state
198 .prompt_state
199 .set(PromptSegment::Indicator, "live> ");
200
201 assert_eq!(
202 transient_prompt.render_prompt_indicator(reedline::PromptEditMode::Emacs),
203 "transient> "
204 );
205 }
206}