Merge nucleic/clever-quartz-marten-uqnx into dev

This commit is contained in:
2026-07-18 21:19:16 -07:00
parent 109cab4cc7
commit a0c3e601be
4 changed files with 61 additions and 22 deletions
+2 -13
View File
@@ -4,7 +4,7 @@ use brush_core::Shell;
use crate::{
InputBackend, ShellError, completion,
input_backend::{InteractivePrompt, ReadResult},
input_backend::{InteractivePrompt, ReadResult, parse_result_is_complete},
};
use super::{non_term_line_reader, term_line_reader};
@@ -82,18 +82,7 @@ impl BasicInputBackend {
}
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,
}
parse_result_is_complete(&shell.parse_string(input.to_owned()))
}
fn generate_completions(
+21
View File
@@ -49,3 +49,24 @@ pub struct InteractivePrompt {
/// Prompt to display on a continuation line of input.
pub continuation_prompt: String,
}
/// Returns whether a parser result represents a complete command that can be
/// executed, as opposed to a command that needs more physical input lines.
pub(crate) fn parse_result_is_complete(
result: &Result<brush_parser::ast::Program, brush_parser::ParseError>,
) -> bool {
match result {
// Unclosed quotes and similar tokenizer states need another line.
Err(brush_parser::ParseError::Tokenizing { inner, position: _ })
if inner.is_incomplete() =>
{
false
}
// An otherwise-valid compound command may simply be missing its
// closing token (`fi`, `done`, `}`, ...).
Err(brush_parser::ParseError::ParsingAtEndOfInput) => false,
// A located syntax error is complete input and should be surfaced.
// A successful parse is ready to execute.
_ => true,
}
}
+35 -7
View File
@@ -2,12 +2,21 @@ use std::io::{IsTerminal, Write};
use crate::{
InputBackend, ShellError,
input_backend::{InteractivePrompt, ReadResult},
input_backend::{InteractivePrompt, ReadResult, parse_result_is_complete},
};
/// Represents a minimal shell input backend, capable of taking commands from standard input.
#[derive(Default)]
pub struct MinimalInputBackend;
pub struct MinimalInputBackend {
parser_options: brush_parser::ParserOptions,
}
impl MinimalInputBackend {
/// Creates a minimal backend using the shell's active parser options.
pub const fn new(parser_options: brush_parser::ParserOptions) -> Self {
Self { parser_options }
}
}
impl InputBackend for MinimalInputBackend {
fn read_line(
@@ -17,12 +26,26 @@ impl InputBackend for MinimalInputBackend {
) -> Result<ReadResult, ShellError> {
self.display_prompt(&prompt)?;
let result = match Self::read_input_line()? {
ReadResult::Input(s) => s,
ReadResult::BoundCommand(s) => s,
ReadResult::Eof => return Ok(ReadResult::Eof),
let mut result = String::new();
loop {
match Self::read_input_line()? {
ReadResult::Input(s) | ReadResult::BoundCommand(s) => {
result.push_str(&s);
if self.is_complete_input(&result) {
break;
}
}
ReadResult::Eof => {
if result.is_empty() {
return Ok(ReadResult::Eof);
}
// Return the partial command so the normal parser path can
// report the incomplete construct instead of dropping it.
break;
}
ReadResult::Interrupted => return Ok(ReadResult::Interrupted),
};
}
}
if result.is_empty() {
Ok(ReadResult::Eof)
@@ -33,6 +56,11 @@ impl InputBackend for MinimalInputBackend {
}
impl MinimalInputBackend {
fn is_complete_input(&self, input: &str) -> bool {
let mut parser = brush_parser::Parser::new(input.as_bytes(), &self.parser_options);
parse_result_is_complete(&parser.parse_program())
}
#[expect(clippy::unused_self)]
fn should_display_prompt(&self) -> bool {
std::io::stdin().is_terminal()
+2 -1
View File
@@ -307,7 +307,8 @@ async fn run_async(
#[cfg(feature = "minimal")]
InputBackendType::Minimal => {
let mut input_backend = brush_interactive::MinimalInputBackend;
let parser_options = shell.lock().await.parser_options();
let mut input_backend = brush_interactive::MinimalInputBackend::new(parser_options);
run_in_shell(&shell, args.clone(), &mut input_backend, &ui_options).await
}
#[cfg(not(feature = "minimal"))]