mirror of
https://github.com/fish-shell/fish-shell.git
synced 2026-06-21 06:41:16 -03:00
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:
@@ -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);
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(
|
||||
|
||||
22
src/event.rs
22
src/event.rs
@@ -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;
|
||||
};
|
||||
}
|
||||
|
||||
21
src/exec.rs
21
src/exec.rs
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
15
src/proc.rs
15
src/proc.rs
@@ -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)
|
||||
|
||||
169
src/reader.rs
169
src/reader.rs
@@ -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()
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user