Merge nucleic/sleek-thistle-egret-fyej into dev
This commit is contained in:
@@ -0,0 +1,117 @@
|
||||
use std::io::IsTerminal;
|
||||
|
||||
use brush_core::Shell;
|
||||
|
||||
use crate::{
|
||||
InputBackend, ShellError, completion,
|
||||
input_backend::{InteractivePrompt, ReadResult},
|
||||
};
|
||||
|
||||
use super::{non_term_line_reader, term_line_reader};
|
||||
|
||||
/// Represents a basic shell input backend capable of interactive usage, with primitive support
|
||||
/// for completion and test-focused automation via pexpect and similar technologies.
|
||||
#[derive(Default)]
|
||||
pub struct BasicInputBackend;
|
||||
|
||||
impl InputBackend for BasicInputBackend {
|
||||
fn read_line(
|
||||
&mut self,
|
||||
shell: &crate::ShellRef<impl brush_core::ShellExtensions>,
|
||||
prompt: InteractivePrompt,
|
||||
) -> Result<ReadResult, ShellError> {
|
||||
if std::io::stdin().is_terminal() {
|
||||
self.read_line_via(shell, &term_line_reader::TermLineReader::new()?, &prompt)
|
||||
} else {
|
||||
self.read_line_via(shell, &non_term_line_reader::NonTermLineReader, &prompt)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BasicInputBackend {
|
||||
fn read_line_via<R: super::LineReader, SE: brush_core::ShellExtensions>(
|
||||
&self,
|
||||
shell_ref: &crate::ShellRef<SE>,
|
||||
reader: &R,
|
||||
prompt: &InteractivePrompt,
|
||||
) -> Result<ReadResult, ShellError> {
|
||||
let mut prompt_to_use = self.should_display_prompt().then_some(&prompt);
|
||||
let mut result = String::new();
|
||||
|
||||
loop {
|
||||
match reader.read_line(prompt_to_use.map(|p| p.prompt.as_str()), |line, cursor| {
|
||||
let mut shell = tokio::task::block_in_place(|| {
|
||||
tokio::runtime::Handle::current().block_on(shell_ref.lock())
|
||||
});
|
||||
|
||||
Self::generate_completions(&mut shell, line, cursor)
|
||||
})? {
|
||||
ReadResult::Input(s) => {
|
||||
result.push_str(s.as_str());
|
||||
|
||||
let shell = tokio::task::block_in_place(|| {
|
||||
tokio::runtime::Handle::current().block_on(shell_ref.lock())
|
||||
});
|
||||
|
||||
if Self::is_valid_input(&shell, result.as_str()) {
|
||||
break;
|
||||
}
|
||||
|
||||
prompt_to_use = None;
|
||||
}
|
||||
ReadResult::BoundCommand(s) => {
|
||||
result.push_str(s.as_str());
|
||||
break;
|
||||
}
|
||||
ReadResult::Eof => {
|
||||
if result.is_empty() {
|
||||
return Ok(ReadResult::Eof);
|
||||
}
|
||||
break;
|
||||
}
|
||||
ReadResult::Interrupted => return Ok(ReadResult::Interrupted),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(ReadResult::Input(result))
|
||||
}
|
||||
|
||||
#[expect(clippy::unused_self)]
|
||||
fn should_display_prompt(&self) -> bool {
|
||||
std::io::stdin().is_terminal()
|
||||
}
|
||||
|
||||
fn is_valid_input(shell: &Shell<impl brush_core::ShellExtensions>, input: &str) -> bool {
|
||||
match shell.parse_string(input.to_owned()) {
|
||||
// Incomplete tokenizing (unclosed quotes, etc.) - need more input
|
||||
Err(brush_parser::ParseError::Tokenizing { inner, position: _ })
|
||||
if inner.is_incomplete() =>
|
||||
{
|
||||
false
|
||||
}
|
||||
// Parse error at end of input - could be incomplete
|
||||
Err(brush_parser::ParseError::ParsingAtEndOfInput) => false,
|
||||
// Parse error at a specific position OR successful parse - complete
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn generate_completions(
|
||||
shell: &mut Shell<impl brush_core::ShellExtensions>,
|
||||
line: &str,
|
||||
cursor: usize,
|
||||
) -> Result<brush_core::completion::Completions, ShellError> {
|
||||
tokio::task::block_in_place(|| {
|
||||
tokio::runtime::Handle::current()
|
||||
.block_on(Self::generate_completions_async(shell, line, cursor))
|
||||
})
|
||||
}
|
||||
|
||||
async fn generate_completions_async(
|
||||
shell: &mut Shell<impl brush_core::ShellExtensions>,
|
||||
line: &str,
|
||||
cursor: usize,
|
||||
) -> Result<brush_core::completion::Completions, ShellError> {
|
||||
Ok(completion::complete_async(shell, line, cursor).await)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user