brush_interactive/
interactive_shell.rs1use crate::ShellError;
2use std::io::{IsTerminal, Write};
3
4pub enum ReadResult {
6 Input(String),
8 BoundCommand(String),
10 Eof,
12 Interrupted,
14}
15
16pub enum InteractiveExecutionResult {
18 Executed(brush_core::ExecutionResult),
20 Failed(brush_core::Error),
22 Eof,
24}
25
26pub struct InteractivePrompt {
28 pub prompt: String,
30 pub alt_side_prompt: String,
32 pub continuation_prompt: String,
34}
35
36pub trait InteractiveShell: Send {
38 fn shell(&self) -> impl AsRef<brush_core::Shell> + Send;
40
41 fn shell_mut(&mut self) -> impl AsMut<brush_core::Shell> + Send;
43
44 fn read_line(&mut self, prompt: InteractivePrompt) -> Result<ReadResult, ShellError>;
50
51 fn get_read_buffer(&self) -> Option<(String, usize)> {
55 None
56 }
57
58 fn set_read_buffer(&mut self, _buffer: String, _cursor: usize) {
61 }
63
64 fn run_interactively(
70 &mut self,
71 ) -> impl std::future::Future<Output = Result<(), ShellError>> + Send {
72 async {
73 if std::io::stdin().is_terminal() {
75 brush_core::terminal::TerminalControl::acquire()?;
76 }
77
78 let mut announce_exit = self.shell().as_ref().options.interactive;
79
80 loop {
81 let result = self.run_interactively_once().await?;
82 match result {
83 InteractiveExecutionResult::Executed(brush_core::ExecutionResult {
84 next_control_flow: brush_core::results::ExecutionControlFlow::ExitShell,
85 ..
86 }) => {
87 break;
88 }
89 InteractiveExecutionResult::Executed(brush_core::ExecutionResult {
90 next_control_flow:
91 brush_core::results::ExecutionControlFlow::ReturnFromFunctionOrScript,
92 ..
93 }) => {
94 tracing::error!("return from non-function/script");
95 }
96 InteractiveExecutionResult::Executed(_) => {}
97 InteractiveExecutionResult::Failed(err) => {
98 let shell = self.shell();
100 let mut stderr = shell.as_ref().stderr();
101 let _ = shell.as_ref().display_error(&mut stderr, &err).await;
102 }
103 InteractiveExecutionResult::Eof => {
104 break;
105 }
106 }
107
108 if self.shell().as_ref().options.exit_after_one_command {
109 announce_exit = false;
110 break;
111 }
112 }
113
114 if announce_exit {
115 writeln!(self.shell().as_ref().stderr(), "exit")?;
116 }
117
118 if let Err(e) = self.shell_mut().as_mut().save_history() {
119 tracing::debug!("couldn't save history: {e}");
122 }
123
124 self.shell_mut().as_mut().on_exit().await?;
126
127 Ok(())
128 }
129 }
130
131 fn run_interactively_once(
133 &mut self,
134 ) -> impl std::future::Future<Output = Result<InteractiveExecutionResult, ShellError>> + Send
135 {
136 async {
137 let mut shell = self.shell_mut();
138 let shell_mut = shell.as_mut();
139
140 shell_mut.check_for_completed_jobs()?;
142
143 run_pre_prompt_commands(shell_mut).await?;
145
146 let prompt = InteractivePrompt {
148 prompt: shell_mut.as_mut().compose_prompt().await?,
149 alt_side_prompt: shell_mut.as_mut().compose_alt_side_prompt().await?,
150 continuation_prompt: shell_mut.as_mut().compose_continuation_prompt().await?,
151 };
152
153 drop(shell);
154
155 match self.read_line(prompt)? {
156 ReadResult::Input(read_result) => {
157 self.execute_line(read_result, true ).await
158 }
159 ReadResult::BoundCommand(read_result) => {
160 self.execute_line(read_result, false ).await
161 }
162 ReadResult::Eof => Ok(InteractiveExecutionResult::Eof),
163 ReadResult::Interrupted => {
164 let mut shell_mut = self.shell_mut();
165 let result: brush_core::ExecutionResult =
166 brush_core::ExecutionExitCode::Interrupted.into();
167 *shell_mut.as_mut().last_exit_status_mut() = result.exit_code.into();
168 Ok(InteractiveExecutionResult::Executed(result))
169 }
170 }
171 }
172 }
173
174 fn execute_line(
176 &mut self,
177 read_result: String,
178 user_input: bool,
179 ) -> impl std::future::Future<Output = Result<InteractiveExecutionResult, ShellError>> + Send
180 {
181 async move {
182 let buffer_info = self.get_read_buffer();
184
185 let mut shell_mut = self.shell_mut();
186
187 let nonempty_buffer = if let Some((buffer, cursor)) = buffer_info {
191 if !buffer.is_empty() {
192 shell_mut.as_mut().set_edit_buffer(buffer, cursor)?;
193 true
194 } else {
195 false
196 }
197 } else {
198 false
199 };
200
201 if user_input {
204 let precmd_prompt = shell_mut.as_mut().compose_precmd_prompt().await?;
206 if !precmd_prompt.is_empty() {
207 print!("{precmd_prompt}");
208 }
209
210 shell_mut
212 .as_mut()
213 .add_to_history(read_result.trim_end_matches('\n'))?;
214 }
215
216 let params = shell_mut.as_mut().default_exec_params();
218 let result = match shell_mut.as_mut().run_string(read_result, ¶ms).await {
219 Ok(result) => Ok(InteractiveExecutionResult::Executed(result)),
220 Err(e) => Ok(InteractiveExecutionResult::Failed(e)),
221 };
222
223 let mut buffer_and_cursor = shell_mut.as_mut().pop_edit_buffer()?;
228
229 if buffer_and_cursor.is_none() && nonempty_buffer {
230 buffer_and_cursor = Some((String::new(), 0));
231 }
232
233 if let Some((updated_buffer, updated_cursor)) = buffer_and_cursor {
234 drop(shell_mut);
235
236 self.set_read_buffer(updated_buffer, updated_cursor);
237 }
238
239 result
240 }
241 }
242}
243
244async fn run_pre_prompt_commands(shell: &mut brush_core::Shell) -> Result<(), ShellError> {
245 if let Some(prompt_cmd_var) = shell.env_var("PROMPT_COMMAND") {
247 match prompt_cmd_var.value() {
248 brush_core::ShellValue::String(cmd_str) => {
249 run_pre_prompt_command(shell, cmd_str.to_owned()).await?;
250 }
251 brush_core::ShellValue::IndexedArray(values) => {
252 let owned_values: Vec<_> = values.values().cloned().collect();
253 for cmd_str in owned_values {
254 run_pre_prompt_command(shell, cmd_str).await?;
255 }
256 }
257 _ => (),
259 }
260 }
261
262 Ok(())
263}
264
265async fn run_pre_prompt_command(
266 shell: &mut brush_core::Shell,
267 prompt_cmd: impl Into<String>,
268) -> Result<(), ShellError> {
269 let prev_last_result = shell.last_result();
271 let prev_last_pipeline_statuses = shell.last_pipeline_statuses.clone();
272
273 let params = shell.default_exec_params();
275 shell.run_string(prompt_cmd, ¶ms).await?;
276
277 shell.last_pipeline_statuses = prev_last_pipeline_statuses;
279 *shell.last_exit_status_mut() = prev_last_result;
280
281 Ok(())
282}