1use std::io::IsTerminal as _;
2use std::io::Write as _;
3
4use crate::InputBackend;
5use crate::InteractivePrompt;
6use crate::ReadResult;
7use crate::ShellError;
8
9pub enum InteractiveExecutionResult {
11 Executed(brush_core::ExecutionResult),
13 Failed(brush_core::Error),
15 Eof,
17}
18
19impl From<&InteractiveExecutionResult> for i32 {
20 fn from(value: &InteractiveExecutionResult) -> Self {
22 match value {
23 InteractiveExecutionResult::Executed(result) => u8::from(result.exit_code).into(),
24 InteractiveExecutionResult::Failed(_) => 1,
25 InteractiveExecutionResult::Eof => 0,
26 }
27 }
28}
29
30#[derive(Clone)]
32pub struct InteractiveOptions {
33 pub terminal_shell_integration: bool,
35 pub run_prompt_command: bool,
37 pub run_cmd_exec_funcs: bool,
40}
41
42impl Default for InteractiveOptions {
43 fn default() -> Self {
44 Self {
45 terminal_shell_integration: false,
46 run_prompt_command: true,
47 run_cmd_exec_funcs: false,
48 }
49 }
50}
51
52pub struct InteractiveShell<'a, IB: InputBackend, SE: brush_core::ShellExtensions> {
54 shell: crate::ShellRef<SE>,
56 input: &'a mut IB,
58 _terminal_control: Option<brush_core::terminal::TerminalControl>,
62 terminal_integration: Option<crate::term_integration::TerminalIntegration>,
64 options: InteractiveOptions,
66}
67
68impl<'a, IB: InputBackend, SE: brush_core::ShellExtensions> InteractiveShell<'a, IB, SE> {
69 pub fn new(
77 shell: &crate::ShellRef<SE>,
78 input: &'a mut IB,
79 options: &InteractiveOptions,
80 ) -> Result<Self, ShellError> {
81 let stdin_is_terminal = std::io::stdin().is_terminal();
82
83 let terminal_control = if stdin_is_terminal {
89 Some(brush_core::terminal::TerminalControl::acquire()?)
90 } else {
91 None
92 };
93
94 let terminal_integration = if options.terminal_shell_integration && stdin_is_terminal {
96 let terminfo = crate::term_detection::get_terminal_info(&HostEnvironment);
97 let terminal_integration = crate::term_integration::TerminalIntegration::new(terminfo);
98
99 print!("{}", terminal_integration.initialize().as_ref());
100 std::io::stdout().flush()?;
101
102 Some(terminal_integration)
103 } else {
104 None
105 };
106
107 Ok(Self {
108 shell: shell.clone(),
109 input,
110 _terminal_control: terminal_control,
111 terminal_integration,
112 options: options.clone(),
113 })
114 }
115
116 pub async fn run_interactively(&mut self) -> Result<(), ShellError> {
120 let mut shell = self.shell.lock().await;
121
122 let mut announce_exit = shell.options().interactive;
123
124 shell.start_interactive_session()?;
125
126 drop(shell);
127
128 loop {
129 let result = self.run_interactively_once().await?;
130 match result {
131 InteractiveExecutionResult::Executed(brush_core::ExecutionResult {
132 next_control_flow: brush_core::results::ExecutionControlFlow::ExitShell,
133 ..
134 }) => {
135 break;
136 }
137 InteractiveExecutionResult::Executed(brush_core::ExecutionResult {
138 next_control_flow:
139 brush_core::results::ExecutionControlFlow::ReturnFromFunctionOrScript,
140 ..
141 }) => {
142 tracing::error!("return from non-function/script");
143 }
144 InteractiveExecutionResult::Executed(_) => {}
145 InteractiveExecutionResult::Failed(err) => {
146 let shell = self.shell.lock().await;
148 let mut stderr = shell.stderr();
149 let _ = shell.display_error(&mut stderr, &err);
150
151 drop(shell);
152 }
153 InteractiveExecutionResult::Eof => {
154 break;
155 }
156 }
157
158 if self.shell.lock().await.options().exit_after_one_command {
159 announce_exit = false;
160 break;
161 }
162 }
163
164 let mut shell = self.shell.lock().await;
165
166 shell.end_interactive_session()?;
167
168 if announce_exit {
169 writeln!(shell.stderr(), "exit")?;
170 }
171
172 if let Err(e) = shell.save_history() {
173 tracing::debug!("couldn't save history: {e}");
176 }
177
178 shell.on_exit().await?;
180
181 drop(shell);
182
183 Ok(())
184 }
185
186 pub async fn start(&mut self) -> Result<(), ShellError> {
188 let mut shell = self.shell.lock().await;
189 shell.start_interactive_session()?;
190 Ok(())
191 }
192
193 pub async fn finish(&mut self) -> Result<(), ShellError> {
195 let mut shell = self.shell.lock().await;
196
197 shell.end_interactive_session()?;
198
199 if let Err(e) = shell.save_history() {
200 tracing::debug!("couldn't save history: {e}");
201 }
202
203 shell.on_exit().await?;
204
205 Ok(())
206 }
207
208 pub async fn run_interactively_once(
210 &mut self,
211 ) -> Result<InteractiveExecutionResult, ShellError> {
212 let mut shell = self.shell.lock().await;
213
214 Self::run_pre_prompt_actions(&mut shell, &self.options).await?;
216
217 let prompt = Self::compose_prompt(&mut shell, self.terminal_integration.as_ref()).await?;
219
220 drop(shell);
221
222 match self.input.read_line(&self.shell, prompt)? {
224 ReadResult::Input(read_result) => {
225 self.execute_line(read_result, true ).await
227 }
228 ReadResult::BoundCommand(read_result) => {
229 self.execute_line(read_result, false ).await
231 }
232 ReadResult::Eof => {
233 Ok(InteractiveExecutionResult::Eof)
235 }
236 ReadResult::Interrupted => {
237 let result: brush_core::ExecutionResult =
239 brush_core::ExecutionExitCode::Interrupted.into();
240 self.shell
241 .lock()
242 .await
243 .set_last_exit_status(result.exit_code.into());
244 Ok(InteractiveExecutionResult::Executed(result))
245 }
246 }
247 }
248
249 async fn compose_prompt(
250 shell: &mut brush_core::Shell<SE>,
251 terminal_integration: Option<&crate::term_integration::TerminalIntegration>,
252 ) -> Result<InteractivePrompt, ShellError> {
253 let mut prompt = InteractivePrompt {
255 prompt: shell.compose_prompt().await?,
256 alt_side_prompt: shell.compose_alt_side_prompt().await?,
257 continuation_prompt: shell.compose_continuation_prompt().await?,
258 };
259
260 if let Some(terminal_integration) = terminal_integration {
261 let pre_prompt = terminal_integration.pre_prompt();
262 let working_dir = terminal_integration.report_cwd(shell.working_dir());
263 let post_prompt = terminal_integration.post_prompt();
264
265 prompt.prompt = [
266 pre_prompt.as_ref(),
267 working_dir.as_ref(),
268 prompt.prompt.as_str(),
269 post_prompt.as_ref(),
270 ]
271 .concat();
272 }
273
274 Ok(prompt)
275 }
276
277 async fn execute_line(
285 &mut self,
286 read_result: String,
287 user_input: bool,
288 ) -> Result<InteractiveExecutionResult, ShellError> {
289 let mut shell = self.shell.lock().await;
290
291 let buffer_info = self.input.get_read_buffer();
293
294 let nonempty_buffer = if let Some((buffer, cursor)) = buffer_info {
298 if !buffer.is_empty() {
299 shell.set_edit_buffer(buffer, cursor)?;
300 true
301 } else {
302 false
303 }
304 } else {
305 false
306 };
307
308 if user_input {
311 Self::run_pre_exec_actions(
312 &mut shell,
313 read_result.as_str(),
314 &self.options,
315 self.terminal_integration.as_ref(),
316 )
317 .await?;
318 }
319
320 let line_count = read_result.lines().count().max(1);
322
323 let params = shell.default_exec_params();
325 let source_info = brush_core::SourceInfo::from("main");
326 let result = match shell.run_string(read_result, &source_info, ¶ms).await {
327 Ok(result) => Ok(InteractiveExecutionResult::Executed(result)),
328 Err(e) => Ok(InteractiveExecutionResult::Failed(e)),
329 };
330
331 shell.increment_interactive_line_offset(line_count);
333
334 let mut buffer_and_cursor = shell.pop_edit_buffer()?;
339
340 drop(shell);
341
342 if buffer_and_cursor.is_none() && nonempty_buffer {
343 buffer_and_cursor = Some((String::new(), 0));
344 }
345
346 if let Some((updated_buffer, updated_cursor)) = buffer_and_cursor {
347 self.input.set_read_buffer(updated_buffer, updated_cursor);
348 }
349
350 if let Some(terminal_integration) = &self.terminal_integration {
352 let exit_code = result.as_ref().map_or(1, i32::from);
353 print!(
354 "{}",
355 terminal_integration.post_exec_command(exit_code).as_ref()
356 );
357 std::io::stdout().flush()?;
358 }
359
360 result
361 }
362
363 async fn run_pre_prompt_actions(
364 shell: &mut brush_core::Shell<SE>,
365 options: &InteractiveOptions,
366 ) -> Result<(), ShellError> {
367 shell.check_for_completed_jobs()?;
369
370 if options.run_prompt_command
372 && let Some(prompt_cmd_var) = shell.env_var("PROMPT_COMMAND")
373 {
374 match prompt_cmd_var.value() {
375 brush_core::ShellValue::String(cmd_str) => {
376 Self::run_pre_prompt_command(shell, cmd_str.to_owned()).await?;
377 }
378 brush_core::ShellValue::IndexedArray(values) => {
379 let owned_values: Vec<_> = values.values().cloned().collect();
380 for cmd_str in owned_values {
381 Self::run_pre_prompt_command(shell, cmd_str).await?;
382 }
383 }
384 _ => (),
386 }
387 }
388
389 if options.run_cmd_exec_funcs {
392 if let Some(brush_core::ShellValue::IndexedArray(precmd_funcs)) = shell
394 .env_var("precmd_functions")
395 .map(|var| var.value())
396 .cloned()
397 {
398 for func_name in precmd_funcs.values() {
399 let _ = shell
400 .invoke_function(
401 func_name,
402 std::iter::empty::<&str>(),
403 &shell.default_exec_params(),
404 )
405 .await;
406 }
407 }
408 }
409
410 Ok(())
411 }
412
413 async fn run_pre_exec_actions(
414 shell: &mut brush_core::Shell<SE>,
415 command_line: &str,
416 options: &InteractiveOptions,
417 terminal_integration: Option<&crate::term_integration::TerminalIntegration>,
418 ) -> Result<(), ShellError> {
419 let precmd_prompt = shell.compose_precmd_prompt().await?;
421 if !precmd_prompt.is_empty() {
422 print!("{precmd_prompt}");
423 }
424
425 shell.add_to_history(command_line.trim_end_matches('\n'))?;
427
428 if options.run_cmd_exec_funcs {
431 if let Some(brush_core::ShellValue::IndexedArray(preexec_funcs)) = shell
433 .env_var("preexec_functions")
434 .map(|var| var.value())
435 .cloned()
436 {
437 for func_name in preexec_funcs.values() {
438 let _ = shell
439 .invoke_function(func_name, &[command_line], &shell.default_exec_params())
440 .await;
441 }
442 }
443 }
444
445 if let Some(terminal_integration) = terminal_integration {
447 print!(
448 "{}",
449 terminal_integration.pre_exec_command(command_line).as_ref()
450 );
451 std::io::stdout().flush()?;
452 }
453
454 Ok(())
455 }
456
457 async fn run_pre_prompt_command(
458 shell: &mut brush_core::Shell<SE>,
459 prompt_cmd: String,
460 ) -> Result<(), ShellError> {
461 let prev_last_result = shell.last_exit_status();
463 let prev_last_pipeline_statuses = shell.last_pipeline_statuses().to_vec();
464
465 let params = shell.default_exec_params();
467 let source_info = brush_core::SourceInfo::from("PROMPT_COMMAND");
468 shell.run_string(prompt_cmd, &source_info, ¶ms).await?;
469
470 *shell.last_pipeline_statuses_mut() = prev_last_pipeline_statuses;
472 shell.set_last_exit_status(prev_last_result);
473
474 Ok(())
475 }
476}
477
478struct HostEnvironment;
481
482impl crate::term_detection::TerminalEnvironment for HostEnvironment {
483 fn get_env_var(&self, name: &str) -> Option<String> {
490 std::env::var(name).ok()
491 }
492}