Make scoped_push nicer

In C++ it's easy to make an RAII-type object like "increment a counter for
the duration of this function." Such an object might accept a pointer or
reference, increment the value, and then restore it in its destructor. We
do this all the time - for example to mark a region of code as
non-interactive, etc.

Rust makes this more awkward, because now the reference is tracked by the
borrow checker: it "owns" the object for the duration of the function. This
leads to approaches like "zelf" where the object that marks the parser as
non-interactive itself becomes the new parser, but we can't call it "self"
and it's just yucky.

In this commit we introduce a notion of the "scoped data" of the Parser,
factored out of the library data. This is data which is typically set in a
scoped fashion: whether we are a subshell, are interactive, emit fish_trace
debugging info, etc. Crucially we set this as Rc: this allow the scope
itself to share data with the Parser and we can get rid of lots of "zelf"s.

Introduce a new function `Parser::push_scope` which creates a new scope and
allows modifying these variables associated with the scope. This ends up as
a nice simplification.
This commit is contained in:
Peter Ammon
2024-12-28 12:41:22 -08:00
parent 2930466d53
commit 29ae571afa
14 changed files with 165 additions and 201 deletions

View File

@@ -25,7 +25,7 @@ pub fn eval(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Bui
// buffer in that case.
let mut stdout_fill = None;
if streams.out_is_piped {
match IoBufferfill::create_opts(parser.libdata().read_limit, STDOUT_FILENO) {
match IoBufferfill::create_opts(parser.scope().read_limit, STDOUT_FILENO) {
Err(_) => {
// We were unable to create a pipe, probably fd exhaustion.
return Err(STATUS_CMD_ERROR);
@@ -40,7 +40,7 @@ pub fn eval(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Bui
// Of course the same applies to stderr.
let mut stderr_fill = None;
if streams.err_is_piped {
match IoBufferfill::create_opts(parser.libdata().read_limit, STDERR_FILENO) {
match IoBufferfill::create_opts(parser.scope().read_limit, STDERR_FILENO) {
Err(_) => {
// We were unable to create a pipe, probably fd exhaustion.
return Err(STATUS_CMD_ERROR);

View File

@@ -134,9 +134,8 @@ fn parse_cmd_opts(
let woptarg = w.woptarg.unwrap();
let e: EventDescription;
if opt == 'j' && woptarg == "caller" {
let libdata = parser.libdata();
let caller_id = if libdata.is_subshell {
libdata.caller_id
let caller_id = if parser.scope().is_subshell {
parser.libdata().caller_id
} else {
0
};

View File

@@ -3,7 +3,6 @@
use super::prelude::*;
use crate::common::escape;
use crate::common::read_blocked;
use crate::common::scoped_push_replacer;
use crate::common::str2wcstring;
use crate::common::unescape_string;
use crate::common::valid_var_name;
@@ -238,11 +237,7 @@ fn read_interactive(
commandline_set_buffer(Some(commandline.to_owned()), None);
let mline = {
let _interactive = scoped_push_replacer(
|new_value| std::mem::replace(&mut parser.libdata_mut().is_interactive, new_value),
true,
);
let _interactive = parser.push_scope(|s| s.is_interactive = true);
reader_readline(parser, nchars)
};
terminal_protocols_disable_ifn();

View File

@@ -68,9 +68,8 @@ pub fn r#return(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) ->
// If we're not in a function, exit the current script (but not an interactive shell).
if !has_function_block {
let ld = &mut parser.libdata_mut();
if !ld.is_interactive {
ld.exit_current_script = true;
if !parser.scope().is_interactive {
parser.libdata_mut().exit_current_script = true;
}
return retval;
}

View File

@@ -516,7 +516,7 @@ pub fn status(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> B
return Err(STATUS_CMD_ERROR);
}
STATUS_IS_COMMAND_SUB => {
if parser.libdata().is_subshell {
if parser.scope().is_subshell {
return Ok(SUCCESS);
} else {
return Err(STATUS_CMD_ERROR);

View File

@@ -1200,14 +1200,12 @@ fn complete_abbr(&mut self, cmd: WString) {
fn complete_from_args(&mut self, s: &wstr, args: &wstr, desc: &wstr, flags: CompleteFlags) {
let is_autosuggest = self.flags.autosuggestion;
let saved_state = if let Some(parser) = self.ctx.maybe_parser() {
let saved_interactive = parser.libdata().is_interactive;
parser.libdata_mut().is_interactive = false;
Some((saved_interactive, parser.get_last_statuses()))
} else {
None
};
let mut saved_statuses = None;
let mut scope = None;
if let Some(parser) = self.ctx.maybe_parser() {
saved_statuses = Some(parser.get_last_statuses());
scope = Some(parser.push_scope(|s| s.is_interactive = false));
}
let eflags = if is_autosuggest {
ExpandFlags::FAIL_ON_CMDSUBST
@@ -1218,10 +1216,9 @@ fn complete_from_args(&mut self, s: &wstr, args: &wstr, desc: &wstr, flags: Comp
let possible_comp = Parser::expand_argument_list(args, eflags, self.ctx);
if let Some(parser) = self.ctx.maybe_parser() {
let (saved_interactive, status) = saved_state.unwrap();
parser.libdata_mut().is_interactive = saved_interactive;
parser.set_last_statuses(status);
parser.set_last_statuses(saved_statuses.unwrap());
}
std::mem::drop(scope);
// Allow leading dots - see #3707.
self.complete_strings(

View File

@@ -7,7 +7,7 @@
use std::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::sync::{Arc, Mutex};
use crate::common::{escape, scoped_push_replacer, ScopeGuard};
use crate::common::{escape, ScopeGuard};
use crate::flog::FLOG;
use crate::io::{IoChain, IoStreams};
use crate::job_group::MaybeJobId;
@@ -461,14 +461,10 @@ pub fn get_function_handlers(name: &wstr) -> EventHandlerList {
/// allocated/initialized unless needed.
fn fire_internal(parser: &Parser, event: &Event) {
// Suppress fish_trace during events.
let _set_event = scoped_push_replacer(
|new_value| std::mem::replace(&mut parser.libdata_mut().is_event, new_value),
true,
);
let _suppress_trace = scoped_push_replacer(
|new_value| std::mem::replace(&mut parser.libdata_mut().suppress_fish_trace, new_value),
true,
);
let _saved = parser.push_scope(|s| {
s.is_event = true;
s.suppress_fish_trace = true;
});
// Capture the event handlers that match this event.
let fire: Vec<_> = EVENT_HANDLERS
@@ -497,12 +493,10 @@ fn fire_internal(parser: &Parser, event: &Event) {
// Event handlers are not part of the main flow of code, so they are marked as
// non-interactive.
let saved_is_interactive =
std::mem::replace(&mut parser.libdata_mut().is_interactive, false);
let _non_interactive = parser.push_scope(|s| s.is_interactive = false);
let saved_statuses = parser.get_last_statuses();
let _cleanup = ScopeGuard::new((), |()| {
parser.set_last_statuses(saved_statuses);
parser.libdata_mut().is_interactive = saved_is_interactive;
});
FLOG!(
@@ -530,10 +524,8 @@ fn fire_internal(parser: &Parser, event: &Event) {
/// Fire all delayed events attached to the given parser.
pub fn fire_delayed(parser: &Parser) {
{
let ld = &parser.libdata();
// Do not invoke new event handlers from within event handlers.
if ld.is_event {
if parser.scope().is_event {
return;
};
}

View File

@@ -8,8 +8,8 @@
STATUS_READ_TOO_MUCH,
};
use crate::common::{
exit_without_destructors, scoped_push_replacer, str2wcstring, truncate_at_nul, wcs2string,
wcs2zstring, write_loop, ScopeGuard,
exit_without_destructors, str2wcstring, truncate_at_nul, wcs2string, wcs2zstring, write_loop,
ScopeGuard,
};
use crate::env::{EnvMode, EnvStack, Environment, Statuses, READ_BYTE_LIMIT};
use crate::env_dispatch::use_posix_spawn;
@@ -1450,18 +1450,14 @@ fn exec_subshell_internal(
is_subcmd: bool,
) -> Result<(), ErrorCode> {
parser.assert_can_execute();
let _is_subshell = scoped_push_replacer(
|new_value| std::mem::replace(&mut parser.libdata_mut().is_subshell, new_value),
true,
);
let _read_limit = scoped_push_replacer(
|new_value| std::mem::replace(&mut parser.libdata_mut().read_limit, new_value),
if is_subcmd {
let _scoped = parser.push_scope(|s| {
s.is_subshell = true;
s.read_limit = if is_subcmd {
READ_BYTE_LIMIT.load(Ordering::Relaxed)
} else {
0
},
);
};
});
let prev_statuses = parser.get_last_statuses();
let _put_back = ScopeGuard::new((), |()| {
@@ -1474,8 +1470,7 @@ fn exec_subshell_internal(
// IO buffer creation may fail (e.g. if we have too many open files to make a pipe), so this may
// be null.
let Ok(bufferfill) = IoBufferfill::create_opts(parser.libdata().read_limit, STDOUT_FILENO)
else {
let Ok(bufferfill) = IoBufferfill::create_opts(parser.scope().read_limit, STDOUT_FILENO) else {
*break_expand = true;
return Err(STATUS_CMD_ERROR);
};

View File

@@ -1623,10 +1623,10 @@ fn run_1_job(
props.initial_background = job_is_background;
{
let parser = ctx.parser();
let ld = &parser.libdata();
let sc = parser.scope();
props.skip_notification =
ld.is_subshell || parser.is_block() || ld.is_event || !parser.is_interactive();
props.from_event_handler = ld.is_event;
sc.is_subshell || parser.is_block() || sc.is_event || !parser.is_interactive();
props.from_event_handler = sc.is_event;
}
let mut job = Job::new(props, self.node_source_owned(job_node));
@@ -1887,7 +1887,7 @@ fn setup_group(&self, ctx: &OperationContext<'_>, j: &mut Job) {
} else {
// This is a "real job" that gets its own pgroup.
j.processes_mut()[0].leads_pgrp = true;
let wants_terminal = !ctx.parser().libdata().is_event;
let wants_terminal = !ctx.parser().scope().is_event;
j.group = Some(JobGroup::create_with_job_control(
j.command().to_owned(),
wants_terminal,

View File

@@ -4,7 +4,7 @@
use crate::builtins::shared::STATUS_ILLEGAL_CMD;
use crate::common::{
escape_string, scoped_push_replacer, CancelChecker, EscapeFlags, EscapeStringStyle,
FilenameRef, ScopeGuarding, PROFILING_ACTIVE,
FilenameRef, ScopeGuarding, ScopedCell, PROFILING_ACTIVE,
};
use crate::complete::CompletionList;
use crate::env::{EnvMode, EnvStack, EnvStackSetResult, Environment, Statuses};
@@ -217,6 +217,30 @@ pub fn now() -> Microseconds {
}
}
/// Data which is managed in a scoped fashion: is generally set for the duration of a block
/// of code. Note this is stored in a Cell and so must be Copy.
#[derive(Copy, Clone, Default)]
pub struct ScopedData {
/// Whether we are running a subshell command.
pub is_subshell: bool,
/// Whether we are running an event handler.
pub is_event: bool,
/// Whether we are currently interactive.
pub is_interactive: bool,
/// Whether to suppress fish_trace output. This occurs in the prompt, event handlers, and key
/// bindings.
pub suppress_fish_trace: bool,
/// The read limit to apply to captured subshell output, or 0 for none.
pub read_limit: usize,
/// Whether we are currently cleaning processes.
pub is_cleaning_procs: bool,
}
/// Miscellaneous data used to avoid recursion and others.
#[derive(Default)]
pub struct LibraryData {
@@ -232,6 +256,7 @@ pub struct LibraryData {
/// This is never null and never invalid.
pub cwd_fd: Option<Arc<OwnedFd>>,
/// Variables supporting the "status" builtin.
pub status_vars: StatusVars,
/// A counter incremented every time a command executes.
@@ -259,26 +284,10 @@ pub struct LibraryData {
/// Whether we called builtin_complete -C without parameter.
pub builtin_complete_current_commandline: bool,
/// Whether we are currently cleaning processes.
pub is_cleaning_procs: bool,
/// The internal job id of the job being populated, or 0 if none.
/// This supports the '--on-job-exit caller' feature.
pub caller_id: u64, // TODO should be InternalJobId
/// Whether we are running a subshell command.
pub is_subshell: bool,
/// Whether we are running an event handler.
pub is_event: bool,
/// Whether we are currently interactive.
pub is_interactive: bool,
/// Whether to suppress fish_trace output. This occurs in the prompt, event handlers, and key
/// bindings.
pub suppress_fish_trace: bool,
/// Whether we should break or continue the current loop.
/// This is set by the 'break' and 'continue' commands.
pub loop_status: LoopStatus,
@@ -292,9 +301,6 @@ pub struct LibraryData {
/// Note this only exits up to the "current script boundary." That is, a call to exit within a
/// 'source' or 'read' command will only exit up to that command.
pub exit_current_script: bool,
/// The read limit to apply to captured subshell output, or 0 for none.
pub read_limit: usize,
}
impl LibraryData {
@@ -395,6 +401,9 @@ pub struct Parser {
/// Set of variables for the parser.
pub variables: Rc<EnvStack>,
/// Data managed in a scoped fashion.
scoped_data: ScopedCell<ScopedData>,
/// Miscellaneous library data.
library_data: RefCell<LibraryData>,
@@ -422,6 +431,7 @@ pub fn new(variables: Rc<EnvStack>, cancel_behavior: CancelBehavior) -> Parser {
block_list: RefCell::default(),
eval_level: AtomicIsize::new(-1),
variables,
scoped_data: ScopedCell::new(ScopedData::default()),
library_data: RefCell::new(LibraryData::new()),
syncs_uvars: RelaxedAtomicBool::new(false),
cancel_behavior,
@@ -814,10 +824,28 @@ pub fn vars_ref(&self) -> Rc<EnvStack> {
Rc::clone(&self.variables)
}
/// Get a copy of the scoped data.
#[inline(always)]
pub fn scope(&self) -> ScopedData {
self.scoped_data.get()
}
/// Modify the scoped values for the duration of the caller's scope (or whenever the ParserScope is dropped).
/// This accepts a closure which modifies the ScopedData, and returns a ParserScope which restores the
/// data when dropped.
pub fn push_scope<'a, F: FnOnce(&mut ScopedData)>(
&'a self,
modifier: F,
) -> impl ScopeGuarding + 'a {
self.scoped_data.scoped_mod(modifier)
}
/// Get the library data.
pub fn libdata(&self) -> Ref<'_, LibraryData> {
self.library_data.borrow()
}
/// Get the library data, mutably.
pub fn libdata_mut(&self) -> RefMut<'_, LibraryData> {
self.library_data.borrow_mut()
}
@@ -1074,7 +1102,7 @@ pub fn current_filename(&self) -> Option<FilenameRef> {
/// Return if we are interactive, which means we are executing a command that the user typed in
/// (and not, say, a prompt).
pub fn is_interactive(&self) -> bool {
self.libdata().is_interactive
self.scope().is_interactive
}
/// Return a string representing the current stack trace.

View File

@@ -4,8 +4,8 @@
use crate::ast;
use crate::common::{
charptr2wcstring, escape, is_windows_subsystem_for_linux, redirect_tty_output,
scoped_push_replacer, timef, Timepoint, WSL,
charptr2wcstring, escape, is_windows_subsystem_for_linux, redirect_tty_output, timef,
Timepoint, WSL,
};
use crate::env::Statuses;
use crate::event::{self, Event};
@@ -1044,7 +1044,7 @@ pub fn continue_job(&self, parser: &Parser) {
} else {
"UNCOMPLETED"
},
if parser.libdata().is_interactive {
if parser.scope().is_interactive {
"INTERACTIVE"
} else {
"NON-INTERACTIVE"
@@ -1357,7 +1357,7 @@ fn handle_child_status(job: &Job, proc: &Process, status: &ProcStatus) {
/// Wait for any process finishing, or receipt of a signal.
pub fn proc_wait_any(parser: &Parser) {
process_mark_finished_children(parser, true /*block_ok*/);
let is_interactive = parser.libdata().is_interactive;
let is_interactive = parser.scope().is_interactive;
process_clean_after_marking(parser, is_interactive);
}
@@ -1779,14 +1779,11 @@ fn process_clean_after_marking(parser: &Parser, allow_interactive: bool) -> bool
// This function may fire an event handler, we do not want to call ourselves recursively (to
// avoid infinite recursion).
if parser.libdata().is_cleaning_procs {
if parser.scope().is_cleaning_procs {
return false;
}
let _cleaning = scoped_push_replacer(
|new_value| std::mem::replace(&mut parser.libdata_mut().is_cleaning_procs, new_value),
true,
);
let _cleaning = parser.push_scope(|s| s.is_cleaning_procs = true);
// This may be invoked in an exit handler, after the TERM has been torn down
// Don't try to print in that case (#3222)

View File

@@ -51,9 +51,8 @@
use crate::common::restore_term_foreground_process_group_for_exit;
use crate::common::{
escape, escape_string, exit_without_destructors, get_ellipsis_char, get_obfuscation_read_char,
redirect_tty_output, scoped_push_replacer, scoped_push_replacer_ctx, shell_modes, str2wcstring,
wcs2string, write_loop, EscapeFlags, EscapeStringStyle, ScopeGuard, PROGRAM_NAME,
UTF8_BOM_WCHAR,
redirect_tty_output, shell_modes, str2wcstring, wcs2string, write_loop, EscapeFlags,
EscapeStringStyle, ScopeGuard, PROGRAM_NAME, UTF8_BOM_WCHAR,
};
use crate::complete::{
complete, complete_load, sort_and_prioritize, CompleteFlags, Completion, CompletionList,
@@ -637,10 +636,7 @@ pub fn reader_read(parser: &Parser, fd: RawFd, io: &IoChain) -> Result<(), Error
}
}
let _interactive_push = scoped_push_replacer(
|new_value| std::mem::replace(&mut parser.libdata_mut().is_interactive, new_value),
interactive,
);
let _interactive_push = parser.push_scope(|s| s.is_interactive = interactive);
signal_set_handlers_once(interactive);
let res = if interactive {
@@ -2155,40 +2151,31 @@ fn readline(&mut self, nchars: Option<NonZeroUsize>) -> Option<WString> {
// Suppress fish_trace during executing key bindings.
// This is simply to reduce noise.
let mut zelf = scoped_push_replacer_ctx(
self,
|zelf, new_value| {
std::mem::replace(
&mut zelf.parser.libdata_mut().suppress_fish_trace,
new_value,
)
},
true,
);
let _restore = self.parser.push_scope(|s| s.suppress_fish_trace = true);
// If nchars_or_0 is positive, then that's the maximum number of chars. Otherwise keep it at
// SIZE_MAX.
zelf.rls_mut().nchars = nchars;
self.rls_mut().nchars = nchars;
// The command line before completion.
zelf.cycle_command_line.clear();
zelf.cycle_cursor_pos = 0;
self.cycle_command_line.clear();
self.cycle_cursor_pos = 0;
zelf.history_search.reset();
self.history_search.reset();
// It may happen that a command we ran when job control was disabled nevertheless stole the tty
// from us. In that case when we read from our fd, it will trigger SIGTTIN. So just
// unconditionally reclaim the tty. See #9181.
unsafe { libc::tcsetpgrp(zelf.conf.inputfd, libc::getpgrp()) };
unsafe { libc::tcsetpgrp(self.conf.inputfd, libc::getpgrp()) };
// Get the current terminal modes. These will be restored when the function returns.
let mut old_modes: libc::termios = unsafe { std::mem::zeroed() };
if unsafe { libc::tcgetattr(zelf.conf.inputfd, &mut old_modes) } == -1 && errno().0 == EIO {
if unsafe { libc::tcgetattr(self.conf.inputfd, &mut old_modes) } == -1 && errno().0 == EIO {
redirect_tty_output(false);
}
// Set the new modes.
if unsafe { libc::tcsetattr(zelf.conf.inputfd, TCSANOW, &*shell_modes()) } == -1 {
if unsafe { libc::tcsetattr(self.conf.inputfd, TCSANOW, &*shell_modes()) } == -1 {
let err = errno().0;
if err == EIO {
redirect_tty_output(false);
@@ -2202,17 +2189,17 @@ fn readline(&mut self, nchars: Option<NonZeroUsize>) -> Option<WString> {
}
}
if *zelf.blocking_wait() == Some(BlockingWait::Startup(Queried::NotYet)) {
if *self.blocking_wait() == Some(BlockingWait::Startup(Queried::NotYet)) {
if is_dumb() || IN_MIDNIGHT_COMMANDER.load() || IN_DVTM.load() {
*zelf.blocking_wait() = None;
*self.blocking_wait() = None;
} else {
*zelf.blocking_wait() = Some(BlockingWait::Startup(Queried::Once));
*self.blocking_wait() = Some(BlockingWait::Startup(Queried::Once));
let mut out = Outputter::stdoutput().borrow_mut();
out.begin_buffering();
// Query for kitty keyboard protocol support.
let _ = out.write_all(kitty_progressive_enhancements_query());
// Query for cursor position reporting support.
zelf.request_cursor_position(&mut out, None);
self.request_cursor_position(&mut out, None);
// Query for synchronized output support.
let _ = out.write_all(b"\x1b[?2026$p");
let _ = out.write_all(b"\x1b[>0q"); // XTVERSION
@@ -2230,64 +2217,64 @@ fn readline(&mut self, nchars: Option<NonZeroUsize>) -> Option<WString> {
// appear constantly.
//
// I can't see a good way around this.
if !zelf.first_prompt {
zelf.screen
if !self.first_prompt {
self.screen
.reset_abandoning_line(usize::try_from(termsize_last().width).unwrap());
}
zelf.first_prompt = false;
self.first_prompt = false;
if !zelf.conf.event.is_empty() {
event::fire_generic(zelf.parser, zelf.conf.event.to_owned(), vec![]);
if !self.conf.event.is_empty() {
event::fire_generic(self.parser, self.conf.event.to_owned(), vec![]);
}
zelf.exec_prompt();
self.exec_prompt();
// Start out as initially dirty.
zelf.force_exec_prompt_and_repaint = true;
self.force_exec_prompt_and_repaint = true;
while !zelf.rls().finished && !check_exit_loop_maybe_warning(Some(&mut zelf)) {
if zelf.handle_char_event(None).is_break() {
while !self.rls().finished && !check_exit_loop_maybe_warning(Some(self)) {
if self.handle_char_event(None).is_break() {
break;
}
}
// Redraw the command line. This is what ensures the autosuggestion is hidden, etc. after the
// user presses enter.
if zelf.is_repaint_needed(None)
|| zelf.screen.scrolled()
|| zelf.conf.inputfd != STDIN_FILENO
if self.is_repaint_needed(None)
|| self.screen.scrolled()
|| self.conf.inputfd != STDIN_FILENO
{
zelf.layout_and_repaint_before_execution();
self.layout_and_repaint_before_execution();
}
// Finish syntax highlighting (but do not wait forever).
if zelf.rls().finished {
zelf.finish_highlighting_before_exec();
if self.rls().finished {
self.finish_highlighting_before_exec();
}
// Emit a newline so that the output is on the line after the command.
// But do not emit a newline if the cursor has wrapped onto a new line all its own - see #6826.
if !zelf.screen.cursor_is_wrapped_to_own_line() {
if !self.screen.cursor_is_wrapped_to_own_line() {
let _ = write_to_fd(b"\n", STDOUT_FILENO);
}
// HACK: If stdin isn't the same terminal as stdout, we just moved the cursor.
// For now, just reset it to the beginning of the line.
if zelf.conf.inputfd != STDIN_FILENO {
if self.conf.inputfd != STDIN_FILENO {
let _ = write_loop(&STDOUT_FILENO, b"\r");
}
// Ensure we have no pager contents when we exit.
if !zelf.pager.is_empty() {
if !self.pager.is_empty() {
// Clear to end of screen to erase the pager contents.
// TODO: this may fail if eos doesn't exist, in which case we should emit newlines.
screen_force_clear_to_end();
zelf.clear_pager();
self.clear_pager();
}
if EXIT_STATE.load(Ordering::Relaxed) != ExitState::FinishedHandlers as _ {
// The order of the two conditions below is important. Try to restore the mode
// in all cases, but only complain if interactive.
if unsafe { libc::tcsetattr(zelf.conf.inputfd, TCSANOW, &old_modes) } == -1
if unsafe { libc::tcsetattr(self.conf.inputfd, TCSANOW, &old_modes) } == -1
&& is_interactive_session()
{
if errno().0 == EIO {
@@ -2299,11 +2286,11 @@ fn readline(&mut self, nchars: Option<NonZeroUsize>) -> Option<WString> {
.borrow_mut()
.set_color(RgbColor::RESET, RgbColor::RESET);
}
let result = zelf
let result = self
.rls()
.finished
.then(|| zelf.command_line.text().to_owned());
zelf.rls = None;
.then(|| self.command_line.text().to_owned());
self.rls = None;
result
}
@@ -4513,14 +4500,10 @@ pub fn reader_write_title(
parser: &Parser,
reset_cursor_position: bool, /* = true */
) {
let _noninteractive = scoped_push_replacer(
|new_value| std::mem::replace(&mut parser.libdata_mut().is_interactive, new_value),
false,
);
let _in_title = scoped_push_replacer(
|new_value| std::mem::replace(&mut parser.libdata_mut().suppress_fish_trace, new_value),
true,
);
let _scoped = parser.push_scope(|s| {
s.is_interactive = false;
s.suppress_fish_trace = true;
});
let mut fish_title_command = DEFAULT_TITLE.to_owned();
if function::exists(L!("fish_title"), parser) {
@@ -4586,67 +4569,51 @@ fn exec_prompt(&mut self) {
self.right_prompt_buff.clear();
// Suppress fish_trace while in the prompt.
let mut zelf = scoped_push_replacer_ctx(
self,
|zelf, new_value| {
std::mem::replace(
&mut zelf.parser.libdata_mut().suppress_fish_trace,
new_value,
)
},
true,
);
let _suppress_trace = self.parser.push_scope(|s| s.suppress_fish_trace = true);
// Update the termsize now.
// This allows prompts to react to $COLUMNS.
zelf.update_termsize();
self.update_termsize();
// If we have any prompts, they must be run non-interactively.
if !zelf.conf.left_prompt_cmd.is_empty() || !zelf.conf.right_prompt_cmd.is_empty() {
let mut zelf = scoped_push_replacer_ctx(
&mut zelf,
|zelf, new_value| {
std::mem::replace(&mut zelf.parser.libdata_mut().is_interactive, new_value)
},
false,
);
if !self.conf.left_prompt_cmd.is_empty() || !self.conf.right_prompt_cmd.is_empty() {
let _noninteractive = self.parser.push_scope(|s| s.is_interactive = false);
self.exec_mode_prompt();
zelf.exec_mode_prompt();
if !zelf.conf.left_prompt_cmd.is_empty() {
if !self.conf.left_prompt_cmd.is_empty() {
// Status is ignored.
let mut prompt_list = vec![];
// Historic compatibility hack.
// If the left prompt function is deleted, then use a default prompt instead of
// producing an error.
let left_prompt_deleted = zelf.conf.left_prompt_cmd == LEFT_PROMPT_FUNCTION_NAME
&& !function::exists(&zelf.conf.left_prompt_cmd, zelf.parser);
let left_prompt_deleted = self.conf.left_prompt_cmd == LEFT_PROMPT_FUNCTION_NAME
&& !function::exists(&self.conf.left_prompt_cmd, self.parser);
let _ = exec_subshell(
if left_prompt_deleted {
DEFAULT_PROMPT
} else {
&zelf.conf.left_prompt_cmd
&self.conf.left_prompt_cmd
},
zelf.parser,
self.parser,
Some(&mut prompt_list),
/*apply_exit_status=*/ false,
);
zelf.left_prompt_buff = join_strings(&prompt_list, '\n');
self.left_prompt_buff = join_strings(&prompt_list, '\n');
}
if !zelf.conf.right_prompt_cmd.is_empty() {
if function::exists(&zelf.conf.right_prompt_cmd, zelf.parser) {
if !self.conf.right_prompt_cmd.is_empty() {
if function::exists(&self.conf.right_prompt_cmd, self.parser) {
// Status is ignored.
let mut prompt_list = vec![];
let _ = exec_subshell(
&zelf.conf.right_prompt_cmd,
zelf.parser,
&self.conf.right_prompt_cmd,
self.parser,
Some(&mut prompt_list),
/*apply_exit_status=*/ false,
);
// Right prompt does not support multiple lines, so just concatenate all of them.
for i in prompt_list {
zelf.right_prompt_buff.push_utfstr(&i);
self.right_prompt_buff.push_utfstr(&i);
}
}
}
@@ -4655,17 +4622,17 @@ fn exec_prompt(&mut self) {
// Write the screen title. Do not reset the cursor position: exec_prompt is called when there
// may still be output on the line from the previous command (#2499) and we need our PROMPT_SP
// hack to work.
reader_write_title(L!(""), zelf.parser, false);
reader_write_title(L!(""), self.parser, false);
// Reap jobs but do NOT trigger a repaint.
// This is to prevent infinite loops in case a job from the prompt triggers a repaint.
// See #9796.
job_reap(zelf.parser, true);
job_reap(self.parser, true);
// Some prompt may have requested an exit (#8033).
let exit_current_script = zelf.parser.libdata().exit_current_script;
zelf.exit_loop_requested |= exit_current_script;
zelf.parser.libdata_mut().exit_current_script = false;
let exit_current_script = self.parser.libdata().exit_current_script;
self.exit_loop_requested |= exit_current_script;
self.parser.libdata_mut().exit_current_script = false;
}
}
@@ -5354,10 +5321,7 @@ fn expand_replacer(
let mut cmd = escape(&repl.replacement);
cmd.push(' ');
cmd.push_utfstr(&escape(token));
let _not_interactive = scoped_push_replacer(
|new_value| std::mem::replace(&mut parser.libdata_mut().is_interactive, new_value),
false,
);
let _not_interactive = parser.push_scope(|s| s.is_interactive = false);
let mut outputs = vec![];
if exec_subshell(
@@ -5807,10 +5771,7 @@ fn should_add_to_history(&mut self, text: &wstr) -> bool {
let mut cmd: WString = L!("fish_should_add_to_history ").into();
cmd.push_utfstr(&escape(text));
let _not_interactive = scoped_push_replacer(
|new_value| std::mem::replace(&mut parser.libdata_mut().is_interactive, new_value),
false,
);
let _not_interactive = parser.push_scope(|s| s.is_interactive = false);
exec_subshell(&cmd, parser, None, /*apply_exit_status=*/ false).is_ok()
}

View File

@@ -204,12 +204,14 @@ fn add_if_new(&mut self, search_match: SearchMatch) {
/// Attempt to append matches from the current history item.
/// Return true if something was appended.
fn append_matches_from_search(&mut self) -> bool {
fn find(zelf: &ReaderHistorySearch, haystack: &wstr, needle: &wstr) -> Option<usize> {
if zelf.search().ignores_case() {
return ifind(haystack, needle, false);
let icase = self.search().ignores_case();
let find = |haystack: &wstr, needle: &wstr| -> Option<usize> {
if icase {
ifind(haystack, needle, false)
} else {
haystack.find(needle)
}
haystack.find(needle)
}
};
let before = self.matches.len();
let text = self.search().current_string();
let needle = self.search_string();
@@ -220,7 +222,7 @@ fn find(zelf: &ReaderHistorySearch, haystack: &wstr, needle: &wstr) -> Option<us
// However, because the user experience of having it crash is horrible,
// and the worst thing that can otherwise happen here is that a search is unsuccessful,
// we just check it instead.
if let Some(offset) = find(self, text, needle) {
if let Some(offset) = find(text, needle) {
self.add_if_new(SearchMatch::new(text.to_owned(), offset));
}
} else if matches!(self.mode, SearchMode::Token | SearchMode::LastToken) {
@@ -232,7 +234,7 @@ fn find(zelf: &ReaderHistorySearch, haystack: &wstr, needle: &wstr) -> Option<us
continue;
}
let text = tok.text_of(&token);
if let Some(offset) = find(self, text, needle) {
if let Some(offset) = find(text, needle) {
local_tokens.push(SearchMatch::new(text.to_owned(), offset));
}
}

View File

@@ -10,8 +10,7 @@ pub fn trace_set_enabled(do_enable: bool) {
/// return whether tracing is enabled.
pub fn trace_enabled(parser: &Parser) -> bool {
let ld = &parser.libdata();
if ld.suppress_fish_trace {
if parser.scope().suppress_fish_trace {
return false;
}
DO_TRACE.load()