brush_interactive/
interactive_shell.rs1use crate::ShellError;
2use std::io::Write;
3
4pub enum ReadResult {
6 Input(String),
8 Eof,
10 Interrupted,
12}
13
14pub enum InteractiveExecutionResult {
16 Executed(brush_core::ExecutionResult),
18 Failed(brush_core::Error),
20 Eof,
22}
23
24pub struct InteractivePrompt {
26 pub prompt: String,
28 pub alt_side_prompt: String,
30 pub continuation_prompt: String,
32}
33
34pub trait InteractiveShell {
36 fn shell(&self) -> impl AsRef<brush_core::Shell> + Send;
38
39 fn shell_mut(&mut self) -> impl AsMut<brush_core::Shell> + Send;
41
42 fn read_line(&mut self, prompt: InteractivePrompt) -> Result<ReadResult, ShellError>;
48
49 fn update_history(&mut self) -> Result<(), ShellError>;
51
52 fn run_interactively(&mut self) -> impl std::future::Future<Output = Result<(), ShellError>> {
58 async {
59 let _ = brush_core::TerminalControl::acquire()?;
61
62 let mut announce_exit = self.shell().as_ref().options.interactive;
63
64 loop {
65 let result = self.run_interactively_once().await?;
66 match result {
67 InteractiveExecutionResult::Executed(brush_core::ExecutionResult {
68 exit_shell,
69 return_from_function_or_script,
70 ..
71 }) => {
72 if exit_shell {
73 break;
74 }
75
76 if return_from_function_or_script {
77 tracing::error!("return from non-function/script");
78 }
79 }
80 InteractiveExecutionResult::Failed(e) => {
81 tracing::error!("error: {:#}", e);
83 }
84 InteractiveExecutionResult::Eof => {
85 break;
86 }
87 }
88
89 if self.shell().as_ref().options.exit_after_one_command {
90 announce_exit = false;
91 break;
92 }
93 }
94
95 if announce_exit {
96 writeln!(self.shell().as_ref().stderr(), "exit")?;
97 }
98
99 if let Err(e) = self.update_history() {
100 tracing::debug!("couldn't save history: {e}");
103 }
104
105 Ok(())
106 }
107 }
108
109 fn run_interactively_once(
111 &mut self,
112 ) -> impl std::future::Future<Output = Result<InteractiveExecutionResult, ShellError>> {
113 async {
114 let mut shell = self.shell_mut();
115 let shell_mut = shell.as_mut();
116
117 shell_mut.check_for_completed_jobs()?;
119
120 if let Some(prompt_cmd) = shell_mut.get_env_str("PROMPT_COMMAND") {
122 let prev_last_result = shell_mut.last_exit_status;
124 let prev_last_pipeline_statuses = shell_mut.last_pipeline_statuses.clone();
125
126 let params = shell_mut.default_exec_params();
127
128 shell_mut
129 .run_string(prompt_cmd.into_owned(), ¶ms)
130 .await?;
131
132 shell_mut.last_pipeline_statuses = prev_last_pipeline_statuses;
133 shell_mut.last_exit_status = prev_last_result;
134 }
135
136 let prompt = InteractivePrompt {
138 prompt: shell_mut.as_mut().compose_prompt().await?,
139 alt_side_prompt: shell_mut.as_mut().compose_alt_side_prompt().await?,
140 continuation_prompt: shell_mut.as_mut().compose_continuation_prompt().await?,
141 };
142
143 drop(shell);
144
145 match self.read_line(prompt)? {
146 ReadResult::Input(read_result) => {
147 let mut shell_mut = self.shell_mut();
148
149 let precmd_prompt = shell_mut.as_mut().compose_precmd_prompt().await?;
150 if !precmd_prompt.is_empty() {
151 print!("{precmd_prompt}");
152 }
153
154 let params = shell_mut.as_mut().default_exec_params();
155 match shell_mut.as_mut().run_string(read_result, ¶ms).await {
156 Ok(result) => Ok(InteractiveExecutionResult::Executed(result)),
157 Err(e) => Ok(InteractiveExecutionResult::Failed(e)),
158 }
159 }
160 ReadResult::Eof => Ok(InteractiveExecutionResult::Eof),
161 ReadResult::Interrupted => {
162 let mut shell_mut = self.shell_mut();
163 shell_mut.as_mut().last_exit_status = 130;
164 Ok(InteractiveExecutionResult::Executed(
165 brush_core::ExecutionResult::new(130),
166 ))
167 }
168 }
169 }
170 }
171}