Split Reader off from ReaderData

Prior to this commit, there was a stack of ReaderDatas, each one has a
reference to a Parser (same Parser in each, for now). However, the current
ReaderData is globally accessible. Because it holds a Parser, effectively
anything can run fish script; this also prevents us from making the Parser
&mut.

Split these up. Create ReaderData, which holds the data portion of the
reader machinery, and then create Reader which holds a ReaderData and a
Parser. Now `reader_current_data()` can only return the data itself; it
cannot execute fish script.

This results in some other nice simplifications.
This commit is contained in:
ridiculousfish 2024-06-01 11:20:22 -07:00 committed by Peter Ammon
parent dfd948fcb5
commit dee692759a
No known key found for this signature in database
8 changed files with 214 additions and 189 deletions

View File

@ -336,7 +336,7 @@ pub fn commandline(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr])
}
// Inserts the readline function at the back of the queue.
reader_execute_readline_cmd(CharEvent::from_readline(cmd));
reader_execute_readline_cmd(parser, CharEvent::from_readline(cmd));
}
return STATUS_CMD_OK;

View File

@ -241,7 +241,7 @@ fn read_interactive(
true,
);
reader_readline(nchars)
reader_readline(parser, nchars)
};
if let Some(line) = mline {
*buff = line;

View File

@ -9,8 +9,7 @@ use crate::input_common::{
use crate::key::{self, canonicalize_raw_escapes, ctrl, Key, Modifiers};
use crate::proc::job_reap;
use crate::reader::{
reader_reading_interrupted, reader_reset_interrupted, reader_schedule_prompt_repaint,
ReaderData,
reader_reading_interrupted, reader_reset_interrupted, reader_schedule_prompt_repaint, Reader,
};
use crate::signal::signal_clear_cancel;
use crate::threads::{assert_is_main_thread, iothread_service_main};
@ -42,7 +41,7 @@ pub enum KeyNameStyle {
}
/// Struct representing a keybinding. Returned by input_get_mappings.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct InputMapping {
/// Character sequence which generates this event.
seq: Vec<Key>,
@ -390,19 +389,19 @@ pub fn init_input() {
}
}
impl InputEventQueuer for ReaderData {
impl<'a> InputEventQueuer for Reader<'a> {
fn get_input_data(&self) -> &InputData {
&self.input_data
&self.data.input_data
}
fn get_input_data_mut(&mut self) -> &mut InputData {
&mut self.input_data
&mut self.data.input_data
}
fn prepare_to_select(&mut self) {
// Fire any pending events and reap stray processes, including printing exit status messages.
event::fire_delayed(self.parser());
if job_reap(self.parser(), true) {
event::fire_delayed(self.parser);
if job_reap(self.parser, true) {
reader_schedule_prompt_repaint();
}
}
@ -413,7 +412,7 @@ impl InputEventQueuer for ReaderData {
signal_clear_cancel();
// Fire any pending events and reap stray processes, including printing exit status messages.
let parser = self.parser();
let parser = self.parser;
event::fire_delayed(parser);
if job_reap(parser, true) {
reader_schedule_prompt_repaint();
@ -431,7 +430,7 @@ impl InputEventQueuer for ReaderData {
}
fn uvar_change_notified(&mut self) {
self.parser().sync_uvars_and_fire(true /* always */);
self.parser.sync_uvars_and_fire(true /* always */);
}
fn ioport_notified(&mut self) {
@ -724,7 +723,7 @@ impl<Queue: InputEventQueuer + ?Sized> Drop for EventQueuePeeker<'_, Queue> {
}
/// Support for reading keys from the terminal for the Reader.
impl ReaderData {
impl<'a> Reader<'a> {
/// Read a key from our fd.
pub fn read_char(&mut self) -> CharEvent {
// Clear the interrupted flag.
@ -796,7 +795,7 @@ impl ReaderData {
}
fn mapping_execute_matching_or_generic(&mut self) {
let vars = self.parser().vars_ref();
let vars = self.parser.vars_ref();
let mut peeker = EventQueuePeeker::new(self);
// Check for ordinary mappings.
let ip = input_mappings();

View File

@ -93,7 +93,7 @@ use crate::parse_util::{
parse_util_get_offset_from_line, parse_util_lineno, parse_util_locate_cmdsubst_range,
parse_util_token_extent,
};
use crate::parser::{BlockType, EvalRes, Parser, ParserRef};
use crate::parser::{BlockType, EvalRes, Parser};
use crate::proc::{
have_proc_stat, hup_jobs, is_interactive_session, job_reap, jobs_requiring_warning_on_exit,
print_exit_warning_for_jobs, proc_update_jiffies,
@ -219,30 +219,19 @@ pub fn current_data() -> Option<&'static mut ReaderData> {
pub use current_data as reader_current_data;
/// Add a new reader to the reader stack.
/// Return a shared pointer to it.
fn reader_push_ret(
parser: &Parser,
history_name: &wstr,
conf: ReaderConfig,
) -> &'static mut ReaderData {
/// If `history_name` is empty, then save history in-memory only; do not write it to disk.
pub fn reader_push<'a>(parser: &'a Parser, history_name: &wstr, conf: ReaderConfig) -> Reader<'a> {
assert_is_main_thread();
let hist = History::with_name(history_name);
hist.resolve_pending();
let data = ReaderData::new(parser.shared(), hist, conf);
let data = ReaderData::new(hist, conf);
reader_data_stack().push(data);
let data = current_data().unwrap();
data.command_line_changed(EditableLineTag::Commandline);
if reader_data_stack().len() == 1 {
reader_interactive_init(parser);
}
data
}
/// Push a new reader environment controlled by `conf`, using the given history name.
/// If `history_name` is empty, then save history in-memory only; do not write it to disk.
pub fn reader_push(parser: &Parser, history_name: &wstr, conf: ReaderConfig) {
// Public variant which discards the return value.
reader_push_ret(parser, history_name, conf);
Reader { data, parser }
}
/// Return to previous reader environment.
@ -459,14 +448,13 @@ enum EditableLineTag {
/// A struct describing the state of the interactive reader. These states can be stacked, in case
/// reader_readline() calls are nested. This happens when the 'read' builtin is used.
/// ReaderData does not contain a Parser - by itself it cannot execute fish script.
pub struct ReaderData {
/// We could put the entire thing in an Rc but Rc::get_unchecked_mut is not yet stable.
/// This is sufficient for our use.
canary: Rc<()>,
/// Configuration for the reader.
conf: ReaderConfig,
/// The parser being used.
parser_ref: ParserRef,
/// String containing the whole current commandline.
command_line: EditableLine,
/// Whether the most recent modification to the command line was done by either history search
@ -554,6 +542,33 @@ pub struct ReaderData {
rls: Option<ReadlineLoopState>,
}
/// Reader is ReaderData equippeed with a Parser, so it can execute fish script.
pub struct Reader<'a> {
pub data: &'a mut ReaderData,
pub parser: &'a Parser,
}
/// Reader dereferences to its referenced ReaderData.
impl<'a> std::ops::Deref for Reader<'a> {
type Target = ReaderData;
fn deref(&self) -> &ReaderData {
self.data
}
}
impl<'a> std::ops::DerefMut for Reader<'a> {
fn deref_mut(&mut self) -> &mut ReaderData {
self.data
}
}
impl<'a> Reader<'a> {
/// Return the variable set used for e.g. command duration.
fn vars(&self) -> &dyn Environment {
self.parser.vars()
}
}
/// Read commands from \c fd until encountering EOF.
/// The fd is not closed.
pub fn reader_read(parser: &Parser, fd: RawFd, io: &IoChain) -> c_int {
@ -612,10 +627,10 @@ fn read_i(parser: &Parser) -> i32 {
conf.right_prompt_cmd = RIGHT_PROMPT_FUNCTION_NAME.to_owned();
}
let data = reader_push_ret(parser, &history_session_id(parser.vars()), conf);
let mut data = reader_push(parser, &history_session_id(parser.vars()), conf);
data.import_history_if_necessary();
while !check_exit_loop_maybe_warning(Some(data)) {
while !check_exit_loop_maybe_warning(Some(&mut data)) {
RUN_COUNT.fetch_add(1, Ordering::Relaxed);
let Some(command) = data.readline(None) else {
@ -651,7 +666,7 @@ fn read_i(parser: &Parser) -> i32 {
data.history.resolve_pending();
let already_warned = data.did_warn_for_bg_jobs;
if check_exit_loop_maybe_warning(Some(data)) {
if check_exit_loop_maybe_warning(Some(&mut data)) {
break;
}
if already_warned {
@ -900,8 +915,9 @@ pub fn reader_schedule_prompt_repaint() {
}
}
pub fn reader_execute_readline_cmd(ch: CharEvent) {
pub fn reader_execute_readline_cmd(parser: &Parser, ch: CharEvent) {
if let Some(data) = current_data() {
let mut data = Reader { parser, data };
let CharEvent::Readline(readline_cmd_evt) = &ch else {
panic!()
};
@ -948,10 +964,11 @@ pub fn reader_reading_interrupted() -> i32 {
/// characters even if a full line has not yet been read. Note: the returned value may be longer
/// than nchars if a single keypress resulted in multiple characters being inserted into the
/// commandline.
pub fn reader_readline(nchars: usize) -> Option<WString> {
pub fn reader_readline(parser: &Parser, nchars: usize) -> Option<WString> {
let nchars = NonZeroUsize::try_from(nchars).ok();
let data = current_data().unwrap();
data.readline(nchars)
let mut reader = Reader { parser, data };
reader.readline(nchars)
}
/// Get the command line state. This may be fetched on a background thread.
@ -1074,11 +1091,10 @@ fn reader_received_sighup() -> bool {
}
impl ReaderData {
fn new(parser: ParserRef, history: Arc<History>, conf: ReaderConfig) -> Pin<Box<Self>> {
fn new(history: Arc<History>, conf: ReaderConfig) -> Pin<Box<Self>> {
let input_data = InputData::new(conf.inputfd);
Pin::new(Box::new(Self {
canary: Rc::new(()),
parser_ref: parser,
conf,
command_line: Default::default(),
command_line_has_transient_edit: false,
@ -1120,6 +1136,13 @@ impl ReaderData {
}))
}
// We repaint our prompt if fstat reports the tty as having changed.
// But don't react to tty changes that we initiated, because of commands or
// on-variable events (e.g. for fish_bind_mode). See #3481.
pub fn save_screen_state(&mut self) {
self.screen.save_status();
}
fn is_navigating_pager_contents(&self) -> bool {
self.pager.is_navigating_contents() || self.history_pager_active
}
@ -1157,16 +1180,6 @@ impl ReaderData {
(elt, self.edit_line_mut(elt))
}
/// Return the variable set used for e.g. command duration.
fn vars(&self) -> &dyn Environment {
self.parser().vars()
}
/// Access the parser.
pub fn parser(&self) -> &Parser {
&self.parser_ref
}
fn rls(&self) -> &ReadlineLoopState {
self.rls.as_ref().unwrap()
}
@ -1174,13 +1187,6 @@ impl ReaderData {
self.rls.as_mut().unwrap()
}
// We repaint our prompt if fstat reports the tty as having changed.
// But don't react to tty changes that we initiated, because of commands or
// on-variable events (e.g. for fish_bind_mode). See #3481.
pub(crate) fn save_screen_state(&mut self) {
self.screen.save_status();
}
/// Do what we need to do whenever our command line changes.
fn command_line_changed(&mut self, elt: EditableLineTag) {
assert_is_main_thread();
@ -1343,7 +1349,7 @@ pub fn combine_command_and_autosuggestion(cmdline: &wstr, autosuggestion: &wstr)
full_line
}
impl ReaderData {
impl<'a> Reader<'a> {
/// Return true if the command line has changed and repainting is needed. If `colors` is not
/// null, then also return true if the colors have changed.
fn is_repaint_needed(&self, mcolors: Option<&[HighlightSpec]>) -> bool {
@ -1429,8 +1435,8 @@ impl ReaderData {
/// `reason` is used in FLOG to explain why.
fn paint_layout(&mut self, reason: &wstr) {
FLOGF!(reader_render, "Repainting from %ls", reason);
let data = &self.rendered_layout;
let cmd_line = &self.command_line;
let data = &self.data.rendered_layout;
let cmd_line = &self.data.command_line;
let full_line = if self.conf.in_silent_mode {
wstr::from_char_slice(&[get_obfuscation_read_char()]).repeat(cmd_line.len())
@ -1476,20 +1482,19 @@ impl ReaderData {
let mut indents = parse_util_compute_indents(cmd_line.text());
indents.resize(full_line.len(), 0);
let screen = &mut self.screen;
let pager = &mut self.pager;
let current_page_rendering = &mut self.current_page_rendering;
let parser = &self.parser_ref;
let screen = &mut self.data.screen;
let pager = &mut self.data.pager;
let current_page_rendering = &mut self.data.current_page_rendering;
screen.write(
// Prepend the mode prompt to the left prompt.
&(self.mode_prompt_buff.clone() + &self.left_prompt_buff[..]),
&self.right_prompt_buff,
&(self.data.mode_prompt_buff.clone() + &self.data.left_prompt_buff[..]),
&self.data.right_prompt_buff,
&full_line,
cmd_line.len(),
&colors,
&indents,
data.position,
parser.vars(),
self.parser.vars(),
pager,
current_page_rendering,
data.focused_on_pager,
@ -1785,7 +1790,7 @@ impl ReaderData {
}
}
impl ReaderData {
impl<'a> Reader<'a> {
/// Read a command to execute, respecting input bindings.
/// Return the command, or none if we were asked to cancel (e.g. SIGHUP).
fn readline(&mut self, nchars: Option<NonZeroUsize>) -> Option<WString> {
@ -1797,7 +1802,7 @@ impl ReaderData {
self,
|zelf, new_value| {
std::mem::replace(
&mut zelf.parser().libdata_mut().suppress_fish_trace,
&mut zelf.parser.libdata_mut().suppress_fish_trace,
new_value,
)
},
@ -1856,7 +1861,7 @@ impl ReaderData {
zelf.first_prompt = false;
if !zelf.conf.event.is_empty() {
event::fire_generic(zelf.parser(), zelf.conf.event.to_owned(), vec![]);
event::fire_generic(zelf.parser, zelf.conf.event.to_owned(), vec![]);
}
zelf.exec_prompt();
@ -1924,9 +1929,9 @@ impl ReaderData {
}
fn eval_bind_cmd(&mut self, cmd: &wstr) {
let last_statuses = self.parser().vars().get_last_statuses();
self.parser().eval(cmd, &IoChain::new());
self.parser().set_last_statuses(last_statuses);
let last_statuses = self.parser.vars().get_last_statuses();
self.parser.eval(cmd, &IoChain::new());
self.parser.set_last_statuses(last_statuses);
}
/// Run a sequence of commands from an input binding.
@ -1957,7 +1962,7 @@ impl ReaderData {
let mut event_needing_handling = None;
let limit = std::cmp::min(
self.rls().nchars.map_or(usize::MAX, |nchars| {
usize::from(nchars) - self.command_line.len()
usize::from(nchars) - self.command_line_len()
}),
READAHEAD_MAX,
);
@ -2005,9 +2010,7 @@ impl ReaderData {
event_needing_handling
}
}
impl ReaderData {
// A helper that kicks off syntax highlighting, autosuggestion computing, and repaints.
fn color_suggest_repaint_now(&mut self) {
if self.conf.inputfd == STDIN_FILENO {
@ -2035,7 +2038,7 @@ impl ReaderData {
if self
.rls()
.nchars
.is_some_and(|nchars| usize::from(nchars) <= self.command_line.len())
.is_some_and(|nchars| usize::from(nchars) <= self.command_line_len())
{
// We've already hit the specified character limit.
self.rls_mut().finished = true;
@ -2050,7 +2053,7 @@ impl ReaderData {
if self
.rls()
.nchars
.is_some_and(|nchars| usize::from(nchars) <= self.command_line.len())
.is_some_and(|nchars| usize::from(nchars) <= self.command_line_len())
{
break None;
}
@ -2058,8 +2061,8 @@ impl ReaderData {
// If we ran `exit` anywhere, exit.
self.exit_loop_requested =
self.exit_loop_requested || self.parser().libdata().exit_current_script;
self.parser().libdata_mut().exit_current_script = false;
self.exit_loop_requested || self.parser.libdata().exit_current_script;
self.parser.libdata_mut().exit_current_script = false;
if self.exit_loop_requested {
return ControlFlow::Continue(());
}
@ -2144,7 +2147,22 @@ impl ReaderData {
}
}
impl ReaderData {
impl<'a> Reader<'a> {
// Convenience cover to return the length of the command line.
fn command_line_len(&self) -> usize {
self.data.command_line.len()
}
// Convenience cover over ReaderData::update_buff_pos.
fn update_buff_pos(&mut self, elt: EditableLineTag, new_pos: Option<usize>) -> bool {
self.data.update_buff_pos(elt, new_pos)
}
// Convenience cover over ReaderData::push_edit.
fn push_edit(&mut self, elt: EditableLineTag, edit: Edit) {
self.data.push_edit(elt, edit);
}
fn handle_readline_command(&mut self, c: ReadlineCmd) {
#[allow(non_camel_case_types)]
type rl = ReadlineCmd;
@ -2180,17 +2198,22 @@ impl ReaderData {
}
position
};
if !self.update_buff_pos(self.active_edit_line_tag(), Some(position + 1)) {
if !self
.data
.update_buff_pos(self.active_edit_line_tag(), Some(position + 1))
{
break;
}
}
}
}
rl::BeginningOfBuffer => {
self.update_buff_pos(EditableLineTag::Commandline, Some(0));
self.data
.update_buff_pos(EditableLineTag::Commandline, Some(0));
}
rl::EndOfBuffer => {
self.update_buff_pos(EditableLineTag::Commandline, Some(self.command_line.len()));
self.data
.update_buff_pos(EditableLineTag::Commandline, Some(self.command_line_len()));
}
rl::CancelCommandline => {
if self.command_line.is_empty() {
@ -2198,11 +2221,11 @@ impl ReaderData {
}
self.push_edit(
EditableLineTag::Commandline,
Edit::new(0..self.command_line.len(), L!("").to_owned()),
Edit::new(0..self.command_line_len(), L!("").to_owned()),
);
// Post fish_cancel.
event::fire_generic(self.parser(), L!("fish_cancel").to_owned(), vec![]);
event::fire_generic(self.parser, L!("fish_cancel").to_owned(), vec![]);
}
rl::Cancel => {
// If we last inserted a completion, undo it.
@ -2224,7 +2247,7 @@ impl ReaderData {
}
rl::RepaintMode | rl::ForceRepaint | rl::Repaint => {
self.queued_repaint = false;
self.parser().libdata_mut().is_repaint = true;
self.parser.libdata_mut().is_repaint = true;
if c == rl::RepaintMode {
// Repaint the mode-prompt only if possible.
// This is an optimization basically exclusively for vi-mode, since the prompt
@ -2244,7 +2267,7 @@ impl ReaderData {
self.screen.reset_line(/*repaint_prompt=*/ true);
self.layout_and_repaint(L!("mode"));
}
self.parser().libdata_mut().is_repaint = false;
self.parser.libdata_mut().is_repaint = false;
return;
}
// Else we repaint as normal.
@ -2253,7 +2276,7 @@ impl ReaderData {
self.screen.reset_line(/*repaint_prompt=*/ true);
self.layout_and_repaint(L!("readline"));
self.force_exec_prompt_and_repaint = false;
self.parser().libdata_mut().is_repaint = false;
self.parser.libdata_mut().is_repaint = false;
}
rl::Complete | rl::CompleteAndSearch => {
if !self.conf.complete_ok {
@ -2318,7 +2341,7 @@ impl ReaderData {
let range = begin..end;
if !range.is_empty() {
self.kill(
self.data.kill(
elt,
range,
Kill::Append,
@ -2350,7 +2373,7 @@ impl ReaderData {
}
assert!(end >= begin);
let len = std::cmp::max(end - begin, 1);
self.kill(
self.data.kill(
elt,
end - len..end,
Kill::Prepend,
@ -2399,7 +2422,7 @@ impl ReaderData {
assert!(end >= begin);
if end > begin {
self.kill(
self.data.kill(
elt,
begin..end,
Kill::Append,
@ -2409,7 +2432,8 @@ impl ReaderData {
}
rl::Yank => {
let yank_str = kill_yank();
self.insert_string(self.active_edit_line_tag(), &yank_str);
self.data
.insert_string(self.active_edit_line_tag(), &yank_str);
self.rls_mut().yank_len = yank_str.len();
if self.cursor_end_mode == CursorEndMode::Inclusive {
let (_elt, el) = self.active_edit_line();
@ -2439,7 +2463,7 @@ impl ReaderData {
}
rl::Exit => {
// This is by definition a successful exit, override the status
self.parser()
self.parser
.set_last_statuses(Statuses::just(STATUS_CMD_OK.unwrap()));
self.exit_loop_requested = true;
check_exit_loop_maybe_warning(Some(self));
@ -2452,7 +2476,7 @@ impl ReaderData {
self.delete_char(false);
} else if c == rl::DeleteOrExit && el.is_empty() {
// This is by definition a successful exit, override the status
self.parser()
self.parser
.set_last_statuses(Statuses::just(STATUS_CMD_OK.unwrap()));
self.exit_loop_requested = true;
check_exit_loop_maybe_warning(Some(self));
@ -2461,7 +2485,7 @@ impl ReaderData {
rl::Execute => {
if !self.handle_execute() {
event::fire_generic(
self.parser(),
self.parser,
L!("fish_posterror").to_owned(),
vec![self.command_line.text().to_owned()],
);
@ -2489,12 +2513,12 @@ impl ReaderData {
let was_active_before = self.history_search.active();
if self.history_search.is_at_end() {
let el = &self.command_line;
let el = &self.data.command_line;
if mode == SearchMode::Token {
// Searching by token.
let mut token_range = 0..0;
parse_util_token_extent(el.text(), el.position(), &mut token_range, None);
self.history_search.reset_to_mode(
self.data.history_search.reset_to_mode(
el.text()[token_range.clone()].to_owned(),
self.history.clone(),
SearchMode::Token,
@ -2502,7 +2526,7 @@ impl ReaderData {
);
} else {
// Searching by line.
self.history_search.reset_to_mode(
self.data.history_search.reset_to_mode(
el.text().to_owned(),
self.history.clone(),
mode,
@ -2510,12 +2534,12 @@ impl ReaderData {
);
// Skip the autosuggestion in the history unless it was truncated.
let suggest = &self.autosuggestion.text;
let suggest = &self.data.autosuggestion.text;
if !suggest.is_empty()
&& !self.screen.autosuggestion_is_truncated
&& !self.data.screen.autosuggestion_is_truncated
&& mode != SearchMode::Prefix
{
self.history_search.add_skip(suggest.clone());
self.data.history_search.add_skip(suggest.clone());
}
}
}
@ -2681,7 +2705,7 @@ impl ReaderData {
| rl::BackwardKillBigword
)
);
self.move_word(
self.data.move_word(
self.active_edit_line_tag(),
MoveWordDir::Left,
/*erase=*/ true,
@ -2697,7 +2721,7 @@ impl ReaderData {
} else {
MoveWordStyle::Whitespace
};
self.move_word(
self.data.move_word(
self.active_edit_line_tag(),
MoveWordDir::Right,
/*erase=*/ true,
@ -2719,7 +2743,7 @@ impl ReaderData {
} else {
MoveWordStyle::Whitespace
};
self.move_word(
self.data.move_word(
self.active_edit_line_tag(),
MoveWordDir::Left,
/*erase=*/ false,
@ -2865,7 +2889,8 @@ impl ReaderData {
local_cmd
.as_char_slice_mut()
.swap(el.position(), el.position() - 1);
self.set_command_line_and_position(elt, local_cmd, el.position() + 1);
self.data
.set_command_line_and_position(elt, local_cmd, el.position() + 1);
}
}
rl::TransposeWords => {
@ -3085,9 +3110,9 @@ impl ReaderData {
let mut success = false;
if let Some(target) = self.last_jump_target {
success = self.jump(
self.last_jump_direction,
self.last_jump_precision,
success = self.data.jump(
self.data.last_jump_direction,
self.data.last_jump_precision,
elt,
target,
);
@ -3107,7 +3132,9 @@ impl ReaderData {
};
if let Some(last_target) = self.last_jump_target {
success = self.jump(dir, self.last_jump_precision, elt, last_target);
success = self
.data
.jump(dir, self.data.last_jump_precision, elt, last_target);
}
self.last_jump_direction = original_dir;
@ -3149,13 +3176,13 @@ impl ReaderData {
.write_wstr(L!("\x1B[?1000l"));
}
rl::FocusIn => {
event::fire_generic(self.parser(), L!("fish_focus_in").to_owned(), vec![]);
event::fire_generic(self.parser, L!("fish_focus_in").to_owned(), vec![]);
}
rl::FocusOut => {
event::fire_generic(self.parser(), L!("fish_focus_out").to_owned(), vec![]);
event::fire_generic(self.parser, L!("fish_focus_out").to_owned(), vec![]);
}
rl::ClearScreenAndRepaint => {
self.parser().libdata_mut().is_repaint = true;
self.parser.libdata_mut().is_repaint = true;
let clear = screen_clear();
if !clear.is_empty() {
// Clear the screen if we can.
@ -3171,7 +3198,7 @@ impl ReaderData {
self.screen.reset_line(/*repaint_prompt=*/ true);
self.layout_and_repaint(L!("readline"));
self.force_exec_prompt_and_repaint = false;
self.parser().libdata_mut().is_repaint = false;
self.parser.libdata_mut().is_repaint = false;
}
rl::SelfInsert | rl::SelfInsertNotFirst | rl::FuncAnd | rl::FuncOr => {
// This can be reached via `commandline -f and` etc
@ -3188,7 +3215,7 @@ fn text_ends_in_comment(text: &wstr) -> bool {
.is_some_and(|token| token.type_ == TokenType::comment)
}
impl ReaderData {
impl<'a> Reader<'a> {
// Handle readline_cmd_t::execute. This may mean inserting a newline if the command is
// unfinished. It may also set 'finished' and 'cmd' inside the rls.
// Return true on success, false if we got an error, in which case the caller should fire the
@ -3199,15 +3226,16 @@ impl ReaderData {
// If the user hits return while navigating the pager, it only clears the pager.
if self.is_navigating_pager_contents() {
if self.history_pager_active && self.pager.selected_completion_idx.is_none() {
self.command_line.push_edit(
self.data.command_line.push_edit(
Edit::new(
0..self.command_line.len(),
self.pager.search_field_line.text().to_owned(),
0..self.data.command_line.len(),
self.data.pager.search_field_line.text().to_owned(),
),
/*allow_coalesce=*/ false,
);
self.command_line
.set_position(self.pager.search_field_line.position());
self.data
.command_line
.set_position(self.data.pager.search_field_line.position());
}
self.clear_pager();
return true;
@ -3264,7 +3292,7 @@ impl ReaderData {
self.add_to_history();
self.rls_mut().finished = true;
self.update_buff_pos(elt, Some(self.command_line.len()));
self.update_buff_pos(elt, Some(self.command_line_len()));
true
}
@ -3281,7 +3309,7 @@ impl ReaderData {
// Syntax check before expanding abbreviations. We could consider relaxing this: a string may be
// syntactically invalid but become valid after expanding abbreviations.
if self.conf.syntax_check_ok {
test_res = reader_shell_test(self.parser(), el.text());
test_res = reader_shell_test(self.parser, el.text());
if test_res.is_err_and(|err| err.contains(ParserTestErrorBits::ERROR)) {
return test_res;
}
@ -3294,12 +3322,14 @@ impl ReaderData {
self.super_highlight_me_plenty();
if self.conf.syntax_check_ok {
let el = &self.command_line;
test_res = reader_shell_test(self.parser(), el.text());
test_res = reader_shell_test(self.parser, el.text());
}
}
test_res
}
}
impl ReaderData {
// Ensure we have no pager contents.
fn clear_pager(&mut self) {
self.pager.clear();
@ -3324,10 +3354,12 @@ impl ReaderData {
let col = pager.get_selected_column(&self.current_page_rendering);
!col.is_some_and(|col| col != 0)
}
}
impl<'a> Reader<'a> {
/// Called to update the termsize, including $COLUMNS and $LINES, as necessary.
fn update_termsize(&mut self) {
termsize_update(self.parser());
termsize_update(self.parser);
}
/// Flash the screen. This function changes the color of the current line momentarily.
@ -3782,14 +3814,14 @@ pub fn reader_write_title(
}
}
impl ReaderData {
impl<'a> Reader<'a> {
fn exec_mode_prompt(&mut self) {
self.mode_prompt_buff.clear();
if function::exists(MODE_PROMPT_FUNCTION_NAME, self.parser()) {
if function::exists(MODE_PROMPT_FUNCTION_NAME, self.parser) {
let mut mode_indicator_list = vec![];
exec_subshell(
MODE_PROMPT_FUNCTION_NAME,
self.parser(),
self.parser,
Some(&mut mode_indicator_list),
false,
);
@ -3812,7 +3844,7 @@ impl ReaderData {
self,
|zelf, new_value| {
std::mem::replace(
&mut zelf.parser().libdata_mut().suppress_fish_trace,
&mut zelf.parser.libdata_mut().suppress_fish_trace,
new_value,
)
},
@ -3828,7 +3860,7 @@ impl ReaderData {
let mut zelf = scoped_push_replacer_ctx(
&mut zelf,
|zelf, new_value| {
std::mem::replace(&mut zelf.parser().libdata_mut().is_interactive, new_value)
std::mem::replace(&mut zelf.parser.libdata_mut().is_interactive, new_value)
},
false,
);
@ -3842,14 +3874,14 @@ impl ReaderData {
// 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());
&& !function::exists(&zelf.conf.left_prompt_cmd, zelf.parser);
exec_subshell(
if left_prompt_deleted {
DEFAULT_PROMPT
} else {
&zelf.conf.left_prompt_cmd
},
zelf.parser(),
zelf.parser,
Some(&mut prompt_list),
/*apply_exit_status=*/ false,
);
@ -3857,12 +3889,12 @@ impl ReaderData {
}
if !zelf.conf.right_prompt_cmd.is_empty() {
if function::exists(&zelf.conf.right_prompt_cmd, zelf.parser()) {
if function::exists(&zelf.conf.right_prompt_cmd, zelf.parser) {
// Status is ignored.
let mut prompt_list = vec![];
exec_subshell(
&zelf.conf.right_prompt_cmd,
zelf.parser(),
zelf.parser,
Some(&mut prompt_list),
/*apply_exit_status=*/ false,
);
@ -3877,12 +3909,12 @@ impl ReaderData {
// 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!(""), zelf.parser, false);
// Some prompt may have requested an exit (#8033).
let exit_current_script = zelf.parser().libdata().exit_current_script;
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;
zelf.parser.libdata_mut().exit_current_script = false;
}
}
@ -4013,7 +4045,7 @@ fn get_autosuggestion_performer(
}
}
impl ReaderData {
impl<'a> Reader<'a> {
fn can_autosuggest(&self) -> bool {
// We autosuggest if suppress_autosuggestion is not set, if we're not doing a history search,
// and our command line contains a non-whitespace character.
@ -4031,8 +4063,8 @@ impl ReaderData {
// Called after an autosuggestion has been computed on a background thread.
fn autosuggest_completed(&mut self, result: Autosuggestion) {
assert_is_main_thread();
if result.search_string == self.in_flight_autosuggest_request {
self.in_flight_autosuggest_request.clear();
if result.search_string == self.data.in_flight_autosuggest_request {
self.data.in_flight_autosuggest_request.clear();
}
if result.search_string != self.command_line.text() {
// This autosuggestion is stale.
@ -4041,7 +4073,7 @@ impl ReaderData {
// Maybe load completions for commands discovered by this autosuggestion.
let mut loaded_new = false;
for to_load in &result.needs_load {
if complete_load(to_load, self.parser()) {
if complete_load(to_load, self.parser) {
FLOGF!(
complete,
"Autosuggest found new completions for %ls, restarting",
@ -4069,8 +4101,8 @@ impl ReaderData {
fn update_autosuggestion(&mut self) {
// If we can't autosuggest, just clear it.
if !self.can_autosuggest() {
self.in_flight_autosuggest_request.clear();
self.autosuggestion.clear();
self.data.in_flight_autosuggest_request.clear();
self.data.autosuggestion.clear();
return;
}
@ -4079,7 +4111,7 @@ impl ReaderData {
// the autosuggestion.
// This is also the main mechanism by which readline commands that don't change the command line
// text avoid recomputing the autosuggestion.
let el = &self.command_line;
let el = &self.data.command_line;
if self.autosuggestion.text.len() > el.text().len()
&& if self.autosuggestion.icase {
string_prefixes_string_case_insensitive(el.text(), &self.autosuggestion.text)
@ -4094,19 +4126,19 @@ impl ReaderData {
if el.text() == self.in_flight_autosuggest_request {
return;
}
self.in_flight_autosuggest_request = el.text().to_owned();
self.data.in_flight_autosuggest_request = el.text().to_owned();
// Clear the autosuggestion and kick it off in the background.
FLOG!(reader_render, "Autosuggesting");
self.autosuggestion.clear();
self.data.autosuggestion.clear();
let performer = get_autosuggestion_performer(
self.parser(),
self.parser,
el.text().to_owned(),
el.position(),
self.history.clone(),
);
let canary = Rc::downgrade(&self.canary);
let completion = move |zelf: &mut Self, result| {
let completion = move |zelf: &mut Reader, result| {
if canary.upgrade().is_none() {
return;
}
@ -4139,7 +4171,7 @@ impl ReaderData {
// Accept the autosuggestion.
if full {
// Just take the whole thing.
self.replace_substring(
self.data.replace_substring(
EditableLineTag::Commandline,
0..self.command_line.len(),
self.autosuggestion.text.clone(),
@ -4147,7 +4179,7 @@ impl ReaderData {
} else if single {
let pos = self.command_line.len();
if pos + 1 < self.autosuggestion.text.len() {
self.replace_substring(
self.data.replace_substring(
EditableLineTag::Commandline,
pos..pos,
self.autosuggestion.text[pos..pos + 1].to_owned(),
@ -4165,7 +4197,7 @@ impl ReaderData {
want += 1;
}
let have = self.command_line.len();
self.replace_substring(
self.data.replace_substring(
EditableLineTag::Commandline,
have..have,
self.autosuggestion.text[have..want].to_owned(),
@ -4202,7 +4234,7 @@ fn get_highlight_performer(
}
}
impl ReaderData {
impl<'a> Reader<'a> {
fn highlight_complete(&mut self, result: HighlightResult) {
assert_is_main_thread();
self.in_flight_highlight_request.clear();
@ -4229,9 +4261,9 @@ impl ReaderData {
FLOG!(reader_render, "Highlighting");
let highlight_performer =
get_highlight_performer(self.parser(), &self.command_line, /*io_ok=*/ true);
get_highlight_performer(self.parser, &self.command_line, /*io_ok=*/ true);
let canary = Rc::downgrade(&self.canary);
let completion = move |zelf: &mut Self, result| {
let completion = move |zelf: &mut Reader, result| {
if canary.upgrade().is_none() {
return;
}
@ -4281,7 +4313,7 @@ impl ReaderData {
if !current_highlight_ok {
// We need to do a quick highlight without I/O.
let highlight_no_io =
get_highlight_performer(self.parser(), &self.command_line, /*io_ok=*/ false);
get_highlight_performer(self.parser, &self.command_line, /*io_ok=*/ false);
self.highlight_complete(highlight_no_io());
}
}
@ -4390,7 +4422,7 @@ impl ReaderData {
move || history_pager_search(&history, direction, index, &search_term)
};
let canary = Rc::downgrade(&self.canary);
let completion = move |zelf: &mut Self, result: HistoryPagerResult| {
let completion = move |zelf: &mut Reader, result: HistoryPagerResult| {
if canary.upgrade().is_none() {
return;
}
@ -4617,7 +4649,7 @@ pub fn reader_expand_abbreviation_at_cursor(
None
}
impl ReaderData {
impl<'a> Reader<'a> {
/// Expand abbreviations at the current cursor position, minus the given cursor backtrack. This
/// may change the command line but does NOT repaint it. This is to allow the caller to coalesce
/// repaints.
@ -4627,7 +4659,7 @@ impl ReaderData {
// Try expanding abbreviations.
let cursor_pos = el.position().saturating_sub(cursor_backtrack);
if let Some(replacement) =
reader_expand_abbreviation_at_cursor(el.text(), cursor_pos, self.parser())
reader_expand_abbreviation_at_cursor(el.text(), cursor_pos, self.parser)
{
self.push_edit(elt, Edit::new(replacement.range.into(), replacement.text));
self.update_buff_pos(elt, replacement.cursor);
@ -4870,7 +4902,7 @@ fn reader_shell_test(parser: &Parser, bstr: &wstr) -> Result<(), ParserTestError
res
}
impl ReaderData {
impl<'a> Reader<'a> {
// Import history from older location (config path) if our current history is empty.
fn import_history_if_necessary(&mut self) {
if self.history.is_empty() {
@ -4896,7 +4928,7 @@ impl ReaderData {
}
fn should_add_to_history(&mut self, text: &wstr) -> bool {
let parser = self.parser();
let parser = self.parser;
if !function::exists(L!("fish_should_add_to_history"), parser) {
// Historical behavior, if the command starts with a space we don't save it.
return text.as_char_slice()[0] != ' ';
@ -4942,16 +4974,14 @@ impl ReaderData {
};
self.history.clone().add_pending_with_file_detection(
&text,
&self.parser().variables,
&self.parser.variables,
mode,
);
}
}
}
/// Check if we have background jobs that we have not warned about.
/// If so, print a warning and return true. Otherwise return false.
impl ReaderData {
/// Check if we have background jobs that we have not warned about.
/// If so, print a warning and return true. Otherwise return false.
fn try_warn_on_background_jobs(&mut self) -> bool {
assert_is_main_thread();
// Have we already warned?
@ -4963,7 +4993,7 @@ impl ReaderData {
return false;
}
// Do we have background jobs?
let bg_jobs = jobs_requiring_warning_on_exit(self.parser());
let bg_jobs = jobs_requiring_warning_on_exit(self.parser);
if bg_jobs.is_empty() {
return false;
}
@ -4976,7 +5006,7 @@ impl ReaderData {
/// Check if we should exit the reader loop.
/// Return true if we should exit.
pub fn check_exit_loop_maybe_warning(data: Option<&mut ReaderData>) -> bool {
pub fn check_exit_loop_maybe_warning(data: Option<&mut Reader>) -> bool {
// sighup always forces exit.
if reader_received_sighup() {
return true;
@ -5313,7 +5343,7 @@ fn get_best_rank(comp: &[Completion]) -> u32 {
best_rank
}
impl ReaderData {
impl<'a> Reader<'a> {
/// Compute completions and update the pager and/or commandline as needed.
fn compute_and_apply_completions(&mut self, c: ReadlineCmd) {
assert!(matches!(
@ -5358,7 +5388,7 @@ impl ReaderData {
// wildcard; if that succeeds we don't then apply user completions (#8593).
let mut wc_expanded = WString::new();
match try_expand_wildcard(
self.parser(),
self.parser,
el.text()[token_range.clone()].to_owned(),
position_in_token,
&mut wc_expanded,
@ -5392,7 +5422,7 @@ impl ReaderData {
let (comp, _needs_load) = complete(
cmdsub,
CompletionRequestOptions::normal(),
&self.parser().context(),
&self.parser.context(),
);
self.rls_mut().comp = comp;

View File

@ -7,7 +7,7 @@ use std::time::Duration;
use crate::common::ScopeGuard;
use crate::global_safety::RelaxedAtomicBool;
use crate::parser::Parser;
use crate::reader::{reader_current_data, reader_pop, reader_push, ReaderConfig, ReaderData};
use crate::reader::{reader_pop, reader_push, Reader, ReaderConfig};
use crate::tests::prelude::*;
use crate::threads::{iothread_drain_all, iothread_service_main, Debounce};
use crate::wchar::prelude::*;
@ -49,7 +49,7 @@ fn test_debounce() {
};
let completer = {
let ctx = ctx.clone();
move |_ctx: &mut ReaderData, idx: usize| {
move |_ctx: &mut Reader, idx: usize| {
ctx.completion_ran[idx].store(true);
}
};
@ -61,13 +61,12 @@ fn test_debounce() {
ctx.cv.notify_all();
// Wait until the last completion is done.
reader_push(Parser::principal_parser(), L!(""), ReaderConfig::default());
let mut reader = reader_push(Parser::principal_parser(), L!(""), ReaderConfig::default());
let _pop = ScopeGuard::new((), |()| reader_pop());
let reader_data = reader_current_data().unwrap();
while !ctx.completion_ran.last().unwrap().load() {
iothread_service_main(reader_data);
iothread_service_main(&mut reader);
}
iothread_drain_all(reader_data);
iothread_drain_all(&mut reader);
// Each perform() call may displace an existing queued operation.
// Each operation waits until all are queued.

View File

@ -5,7 +5,7 @@ use crate::common::ENCODE_DIRECT_BASE;
use crate::env::{EnvVar, EnvVarFlags, VarTable};
use crate::env_universal_common::{CallbackDataList, EnvUniversal, UvarFormat};
use crate::parser::Parser;
use crate::reader::{reader_current_data, reader_pop, reader_push, ReaderConfig};
use crate::reader::{reader_pop, reader_push, ReaderConfig};
use crate::tests::prelude::*;
use crate::threads::{iothread_drain_all, iothread_perform};
use crate::wchar::prelude::*;
@ -45,14 +45,14 @@ fn test_universal() {
let _ = std::fs::remove_dir_all("test/fish_uvars_test/");
std::fs::create_dir_all("test/fish_uvars_test/").unwrap();
reader_push(Parser::principal_parser(), L!(""), ReaderConfig::default());
let mut reader = reader_push(Parser::principal_parser(), L!(""), ReaderConfig::default());
let _pop = ScopeGuard::new((), |()| reader_pop());
let threads = 1;
for i in 0..threads {
iothread_perform(move || test_universal_helper(i));
}
iothread_drain_all(reader_current_data().unwrap());
iothread_drain_all(&mut reader);
let mut uvars = EnvUniversal::new();
let mut callbacks = CallbackDataList::new();

View File

@ -7,12 +7,10 @@ use crate::parse_constants::{
};
use crate::parse_util::{parse_util_detect_errors, parse_util_detect_errors_in_argument};
use crate::parser::Parser;
use crate::reader::{
reader_current_data, reader_pop, reader_push, reader_reset_interrupted, ReaderConfig,
};
use crate::reader::{reader_pop, reader_push, reader_reset_interrupted, ReaderConfig};
use crate::signal::{signal_clear_cancel, signal_reset_handlers, signal_set_handlers};
use crate::tests::prelude::*;
use crate::threads::{iothread_drain_all, iothread_perform};
use crate::threads::iothread_perform;
use crate::wchar::prelude::*;
use crate::wcstringutil::join_strings;
use libc::SIGINT;
@ -711,7 +709,6 @@ fn test_1_cancellation(src: &wstr) {
src
);
assert!(res.status.signal_exited() && res.status.signal_code() == SIGINT);
iothread_drain_all(reader_current_data().unwrap());
}
#[test]

View File

@ -2,7 +2,7 @@
//! ported directly from the cpp code so we can use rust threads instead of using pthreads.
use crate::flog::{FloggableDebug, FLOG};
use crate::reader::ReaderData;
use crate::reader::Reader;
use std::marker::PhantomData;
use std::num::NonZeroU64;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
@ -43,7 +43,7 @@ struct ForceSend<T>(T);
unsafe impl<T> Send for ForceSend<T> {}
#[allow(clippy::type_complexity)]
type DebounceCallback = ForceSend<Box<dyn FnOnce(&mut ReaderData) + 'static>>;
type DebounceCallback = ForceSend<Box<dyn FnOnce(&mut Reader) + 'static>>;
/// The queue of [`WorkItem`]s to be executed on the main thread. This is read from in
/// `iothread_service_main()`.
@ -485,13 +485,13 @@ pub fn iothread_port() -> i32 {
NOTIFY_SIGNALLER.read_fd()
}
pub fn iothread_service_main_with_timeout(ctx: &mut ReaderData, timeout: Duration) {
pub fn iothread_service_main_with_timeout(ctx: &mut Reader, timeout: Duration) {
if crate::fd_readable_set::is_fd_readable(iothread_port(), timeout.as_millis() as u64) {
iothread_service_main(ctx);
}
}
pub fn iothread_service_main(ctx: &mut ReaderData) {
pub fn iothread_service_main(ctx: &mut Reader) {
self::assert_is_main_thread();
// Note: the order here is important. We must consume events before handling requests, as
@ -508,7 +508,7 @@ pub fn iothread_service_main(ctx: &mut ReaderData) {
/// Does nasty polling via select(), only used for testing.
#[cfg(test)]
pub(crate) fn iothread_drain_all(ctx: &mut ReaderData) {
pub(crate) fn iothread_drain_all(ctx: &mut Reader) {
while borrow_io_thread_pool()
.shared
.mutex
@ -608,7 +608,7 @@ impl Debounce {
pub fn perform_with_completion<H, R, C>(&self, handler: H, completion: C) -> NonZeroU64
where
H: FnOnce() -> R + 'static + Send,
C: FnOnce(&mut ReaderData, R) + 'static,
C: FnOnce(&mut Reader, R) + 'static,
R: 'static + Send,
{
assert_is_main_thread();