Merge nucleic/clever-quartz-marten-uqnx into dev
This commit is contained in:
@@ -4,7 +4,7 @@ use brush_core::Shell;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
InputBackend, ShellError, completion,
|
InputBackend, ShellError, completion,
|
||||||
input_backend::{InteractivePrompt, ReadResult},
|
input_backend::{InteractivePrompt, ReadResult, parse_result_is_complete},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{non_term_line_reader, term_line_reader};
|
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 {
|
fn is_valid_input(shell: &Shell<impl brush_core::ShellExtensions>, input: &str) -> bool {
|
||||||
match shell.parse_string(input.to_owned()) {
|
parse_result_is_complete(&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(
|
fn generate_completions(
|
||||||
|
|||||||
@@ -49,3 +49,24 @@ pub struct InteractivePrompt {
|
|||||||
/// Prompt to display on a continuation line of input.
|
/// Prompt to display on a continuation line of input.
|
||||||
pub continuation_prompt: String,
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,12 +2,21 @@ use std::io::{IsTerminal, Write};
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
InputBackend, ShellError,
|
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.
|
/// Represents a minimal shell input backend, capable of taking commands from standard input.
|
||||||
#[derive(Default)]
|
#[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 {
|
impl InputBackend for MinimalInputBackend {
|
||||||
fn read_line(
|
fn read_line(
|
||||||
@@ -17,12 +26,26 @@ impl InputBackend for MinimalInputBackend {
|
|||||||
) -> Result<ReadResult, ShellError> {
|
) -> Result<ReadResult, ShellError> {
|
||||||
self.display_prompt(&prompt)?;
|
self.display_prompt(&prompt)?;
|
||||||
|
|
||||||
let result = match Self::read_input_line()? {
|
let mut result = String::new();
|
||||||
ReadResult::Input(s) => s,
|
loop {
|
||||||
ReadResult::BoundCommand(s) => s,
|
match Self::read_input_line()? {
|
||||||
ReadResult::Eof => return Ok(ReadResult::Eof),
|
ReadResult::Input(s) | ReadResult::BoundCommand(s) => {
|
||||||
ReadResult::Interrupted => return Ok(ReadResult::Interrupted),
|
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() {
|
if result.is_empty() {
|
||||||
Ok(ReadResult::Eof)
|
Ok(ReadResult::Eof)
|
||||||
@@ -33,6 +56,11 @@ impl InputBackend for MinimalInputBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl 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)]
|
#[expect(clippy::unused_self)]
|
||||||
fn should_display_prompt(&self) -> bool {
|
fn should_display_prompt(&self) -> bool {
|
||||||
std::io::stdin().is_terminal()
|
std::io::stdin().is_terminal()
|
||||||
|
|||||||
@@ -307,7 +307,8 @@ async fn run_async(
|
|||||||
|
|
||||||
#[cfg(feature = "minimal")]
|
#[cfg(feature = "minimal")]
|
||||||
InputBackendType::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
|
run_in_shell(&shell, args.clone(), &mut input_backend, &ui_options).await
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "minimal"))]
|
#[cfg(not(feature = "minimal"))]
|
||||||
|
|||||||
Reference in New Issue
Block a user