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 async fn run_interactively_once(&mut self) -> Result<InteractiveExecutionResult, ShellError> {
188 let mut shell = self.shell.lock().await;
189
190 Self::run_pre_prompt_actions(&mut shell, &self.options).await?;
192
193 let prompt = Self::compose_prompt(&mut shell, self.terminal_integration.as_ref()).await?;
195
196 drop(shell);
197
198 match self.input.read_line(&self.shell, prompt)? {
200 ReadResult::Input(read_result) => {
201 self.execute_line(read_result, true ).await
203 }
204 ReadResult::BoundCommand(read_result) => {
205 self.execute_line(read_result, false ).await
207 }
208 ReadResult::Eof => {
209 Ok(InteractiveExecutionResult::Eof)
211 }
212 ReadResult::Interrupted => {
213 let result: brush_core::ExecutionResult =
215 brush_core::ExecutionExitCode::Interrupted.into();
216 self.shell
217 .lock()
218 .await
219 .set_last_exit_status(result.exit_code.into());
220 Ok(InteractiveExecutionResult::Executed(result))
221 }
222 }
223 }
224
225 async fn compose_prompt(
226 shell: &mut brush_core::Shell<SE>,
227 terminal_integration: Option<&crate::term_integration::TerminalIntegration>,
228 ) -> Result<InteractivePrompt, ShellError> {
229 let mut prompt = InteractivePrompt {
231 prompt: shell.compose_prompt().await?,
232 alt_side_prompt: shell.compose_alt_side_prompt().await?,
233 continuation_prompt: shell.compose_continuation_prompt().await?,
234 };
235
236 if let Some(terminal_integration) = terminal_integration {
237 let pre_prompt = terminal_integration.pre_prompt();
238 let working_dir = terminal_integration.report_cwd(shell.working_dir());
239 let post_prompt = terminal_integration.post_prompt();
240
241 prompt.prompt = [
242 pre_prompt.as_ref(),
243 working_dir.as_ref(),
244 prompt.prompt.as_str(),
245 post_prompt.as_ref(),
246 ]
247 .concat();
248 }
249
250 Ok(prompt)
251 }
252
253 async fn execute_line(
261 &mut self,
262 read_result: String,
263 user_input: bool,
264 ) -> Result<InteractiveExecutionResult, ShellError> {
265 let mut shell = self.shell.lock().await;
266
267 let buffer_info = self.input.get_read_buffer();
269
270 let nonempty_buffer = if let Some((buffer, cursor)) = buffer_info {
274 if !buffer.is_empty() {
275 shell.set_edit_buffer(buffer, cursor)?;
276 true
277 } else {
278 false
279 }
280 } else {
281 false
282 };
283
284 if user_input {
287 Self::run_pre_exec_actions(
288 &mut shell,
289 read_result.as_str(),
290 &self.options,
291 self.terminal_integration.as_ref(),
292 )
293 .await?;
294 }
295
296 let line_count = read_result.lines().count().max(1);
298
299 let params = shell.default_exec_params();
301 let source_info = brush_core::SourceInfo::from("main");
302 let result = match shell.run_string(read_result, &source_info, ¶ms).await {
303 Ok(result) => Ok(InteractiveExecutionResult::Executed(result)),
304 Err(e) => Ok(InteractiveExecutionResult::Failed(e)),
305 };
306
307 shell.increment_interactive_line_offset(line_count);
309
310 let mut buffer_and_cursor = shell.pop_edit_buffer()?;
315
316 drop(shell);
317
318 if buffer_and_cursor.is_none() && nonempty_buffer {
319 buffer_and_cursor = Some((String::new(), 0));
320 }
321
322 if let Some((updated_buffer, updated_cursor)) = buffer_and_cursor {
323 self.input.set_read_buffer(updated_buffer, updated_cursor);
324 }
325
326 if let Some(terminal_integration) = &self.terminal_integration {
328 let exit_code = result.as_ref().map_or(1, i32::from);
329 print!(
330 "{}",
331 terminal_integration.post_exec_command(exit_code).as_ref()
332 );
333 std::io::stdout().flush()?;
334 }
335
336 result
337 }
338
339 async fn run_pre_prompt_actions(
340 shell: &mut brush_core::Shell<SE>,
341 options: &InteractiveOptions,
342 ) -> Result<(), ShellError> {
343 shell.check_for_completed_jobs()?;
345
346 if options.run_prompt_command
348 && let Some(prompt_cmd_var) = shell.env_var("PROMPT_COMMAND")
349 {
350 match prompt_cmd_var.value() {
351 brush_core::ShellValue::String(cmd_str) => {
352 Self::run_pre_prompt_command(shell, cmd_str.to_owned()).await?;
353 }
354 brush_core::ShellValue::IndexedArray(values) => {
355 let owned_values: Vec<_> = values.values().cloned().collect();
356 for cmd_str in owned_values {
357 Self::run_pre_prompt_command(shell, cmd_str).await?;
358 }
359 }
360 _ => (),
362 }
363 }
364
365 if options.run_cmd_exec_funcs {
368 if let Some(brush_core::ShellValue::IndexedArray(precmd_funcs)) = shell
370 .env_var("precmd_functions")
371 .map(|var| var.value())
372 .cloned()
373 {
374 for func_name in precmd_funcs.values() {
375 let _ = shell
376 .invoke_function(
377 func_name,
378 std::iter::empty::<&str>(),
379 &shell.default_exec_params(),
380 )
381 .await;
382 }
383 }
384 }
385
386 Ok(())
387 }
388
389 async fn run_pre_exec_actions(
390 shell: &mut brush_core::Shell<SE>,
391 command_line: &str,
392 options: &InteractiveOptions,
393 terminal_integration: Option<&crate::term_integration::TerminalIntegration>,
394 ) -> Result<(), ShellError> {
395 let precmd_prompt = shell.compose_precmd_prompt().await?;
397 if !precmd_prompt.is_empty() {
398 print!("{precmd_prompt}");
399 }
400
401 shell.add_to_history(command_line.trim_end_matches('\n'))?;
403
404 if options.run_cmd_exec_funcs {
407 if let Some(brush_core::ShellValue::IndexedArray(preexec_funcs)) = shell
409 .env_var("preexec_functions")
410 .map(|var| var.value())
411 .cloned()
412 {
413 for func_name in preexec_funcs.values() {
414 let _ = shell
415 .invoke_function(func_name, &[command_line], &shell.default_exec_params())
416 .await;
417 }
418 }
419 }
420
421 if let Some(terminal_integration) = terminal_integration {
423 print!(
424 "{}",
425 terminal_integration.pre_exec_command(command_line).as_ref()
426 );
427 std::io::stdout().flush()?;
428 }
429
430 Ok(())
431 }
432
433 async fn run_pre_prompt_command(
434 shell: &mut brush_core::Shell<SE>,
435 prompt_cmd: String,
436 ) -> Result<(), ShellError> {
437 let prev_last_result = shell.last_exit_status();
439 let prev_last_pipeline_statuses = shell.last_pipeline_statuses().to_vec();
440
441 let params = shell.default_exec_params();
443 let source_info = brush_core::SourceInfo::from("PROMPT_COMMAND");
444 shell.run_string(prompt_cmd, &source_info, ¶ms).await?;
445
446 *shell.last_pipeline_statuses_mut() = prev_last_pipeline_statuses;
448 shell.set_last_exit_status(prev_last_result);
449
450 Ok(())
451 }
452}
453
454struct HostEnvironment;
457
458impl crate::term_detection::TerminalEnvironment for HostEnvironment {
459 fn get_env_var(&self, name: &str) -> Option<String> {
466 std::env::var(name).ok()
467 }
468}