brush_interactive/interactive_shell.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
use crate::ShellError;
use std::io::Write;
/// Result of a read operation.
pub enum ReadResult {
/// The user entered a line of input.
Input(String),
/// End of input was reached.
Eof,
/// The user interrupted the input operation.
Interrupted,
}
/// Result of an interactive execution.
pub enum InteractiveExecutionResult {
/// The command was executed and returned the given result.
Executed(brush_core::ExecutionResult),
/// The command failed to execute.
Failed(brush_core::Error),
/// End of input was reached.
Eof,
}
/// Represents a shell capable of taking commands from standard input.
#[async_trait::async_trait]
pub trait InteractiveShell {
/// Returns an immutable reference to the inner shell object.
fn shell(&self) -> impl AsRef<brush_core::Shell> + Send;
/// Returns a mutable reference to the inner shell object.
fn shell_mut(&mut self) -> impl AsMut<brush_core::Shell> + Send;
/// Reads a line of input, using the given prompt.
///
/// # Arguments
///
/// * `prompt` - The prompt to display to the user.
fn read_line(&mut self, prompt: &str) -> Result<ReadResult, ShellError>;
/// Update history, if relevant.
fn update_history(&mut self) -> Result<(), ShellError>;
/// Runs the interactive shell loop, reading commands from standard input and writing
/// results to standard output and standard error. Continues until the shell
/// normally exits or until a fatal error occurs.
async fn run_interactively(&mut self) -> Result<(), ShellError> {
// TODO: Consider finding a better place for this.
let _ = brush_core::TerminalControl::acquire()?;
loop {
let result = self.run_interactively_once().await?;
match result {
InteractiveExecutionResult::Executed(brush_core::ExecutionResult {
exit_shell,
return_from_function_or_script,
..
}) => {
if exit_shell {
break;
}
if return_from_function_or_script {
tracing::error!("return from non-function/script");
}
}
InteractiveExecutionResult::Failed(e) => {
// Report the error, but continue to execute.
tracing::error!("error: {:#}", e);
}
InteractiveExecutionResult::Eof => {
break;
}
}
}
if self.shell().as_ref().options.interactive {
writeln!(self.shell().as_ref().stderr(), "exit")?;
}
if let Err(e) = self.update_history() {
// N.B. This seems like the sort of thing that's worth being noisy about,
// but bash doesn't do that -- and probably for a reason.
tracing::debug!("couldn't save history: {e}");
}
Ok(())
}
/// Runs the interactive shell loop once, reading a single command from standard input.
async fn run_interactively_once(&mut self) -> Result<InteractiveExecutionResult, ShellError> {
let mut shell_mut = self.shell_mut();
// Check for any completed jobs.
shell_mut.as_mut().check_for_completed_jobs()?;
// If there's a variable called PROMPT_COMMAND, then run it first.
if let Some((_, prompt_cmd)) = shell_mut.as_mut().env.get("PROMPT_COMMAND") {
let prompt_cmd = prompt_cmd.value().to_cow_string().to_string();
// Save (and later restore) the last exit status.
let prev_last_result = shell_mut.as_mut().last_exit_status;
let params = shell_mut.as_mut().default_exec_params();
shell_mut.as_mut().run_string(prompt_cmd, ¶ms).await?;
shell_mut.as_mut().last_exit_status = prev_last_result;
}
// Now that we've done that, compose the prompt.
let prompt = shell_mut.as_mut().compose_prompt().await?;
drop(shell_mut);
match self.read_line(prompt.as_str())? {
ReadResult::Input(read_result) => {
let mut shell_mut = self.shell_mut();
let params = shell_mut.as_mut().default_exec_params();
match shell_mut.as_mut().run_string(read_result, ¶ms).await {
Ok(result) => Ok(InteractiveExecutionResult::Executed(result)),
Err(e) => Ok(InteractiveExecutionResult::Failed(e)),
}
}
ReadResult::Eof => Ok(InteractiveExecutionResult::Eof),
ReadResult::Interrupted => {
let mut shell_mut = self.shell_mut();
shell_mut.as_mut().last_exit_status = 130;
Ok(InteractiveExecutionResult::Executed(
brush_core::ExecutionResult::new(130),
))
}
}
}
}