mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-27 05:13:10 +00:00
Migrate the Inputter type to a trait
This is a start on untangling input. Prior to this, a ReaderData and an Inputter would communicate with each other; this is natural in C++ but difficult in Rust because the Reader would own an Inputter and therefore the Inputter could not easily reference the Reader. This was previously "resolved" via unsafe code. Fix this by collapsing Inputter into Reader. Now they're the same object! Migrate Inputter's logic into a trait, so we get some modularity, and then directly implement the remaining input methods on ReaderData.
This commit is contained in:
parent
c9a76bd634
commit
c297df38c7
4 changed files with 357 additions and 348 deletions
402
src/input.rs
402
src/input.rs
|
@ -4,22 +4,19 @@ use crate::env::{Environment, CURSES_INITIALIZED};
|
|||
use crate::event;
|
||||
use crate::flog::FLOG;
|
||||
use crate::input_common::{
|
||||
CharEvent, CharInputStyle, InputEventQueuer, ReadlineCmd, R_END_INPUT_FUNCTIONS,
|
||||
CharEvent, CharInputStyle, InputData, InputEventQueuer, ReadlineCmd, R_END_INPUT_FUNCTIONS,
|
||||
};
|
||||
use crate::key::{self, canonicalize_raw_escapes, ctrl, Key, Modifiers};
|
||||
use crate::parser::Parser;
|
||||
use crate::proc::job_reap;
|
||||
use crate::reader::{
|
||||
reader_reading_interrupted, reader_reset_interrupted, reader_schedule_prompt_repaint,
|
||||
ReaderData,
|
||||
};
|
||||
use crate::signal::signal_clear_cancel;
|
||||
use crate::threads::assert_is_main_thread;
|
||||
use crate::wchar::prelude::*;
|
||||
use once_cell::sync::{Lazy, OnceCell};
|
||||
use std::collections::VecDeque;
|
||||
use std::ffi::CString;
|
||||
use std::os::fd::RawFd;
|
||||
use std::rc::Rc;
|
||||
use std::sync::{
|
||||
atomic::{AtomicU32, Ordering},
|
||||
Mutex, MutexGuard,
|
||||
|
@ -46,11 +43,11 @@ pub enum KeyNameStyle {
|
|||
|
||||
/// Struct representing a keybinding. Returned by input_get_mappings.
|
||||
#[derive(Debug, Clone)]
|
||||
struct InputMapping {
|
||||
pub struct InputMapping {
|
||||
/// Character sequence which generates this event.
|
||||
seq: Vec<Key>,
|
||||
/// Commands that should be evaluated by this mapping.
|
||||
commands: Vec<WString>,
|
||||
pub commands: Vec<WString>,
|
||||
/// We wish to preserve the user-specified order. This is just an incrementing value.
|
||||
specification_order: u32,
|
||||
/// Mode in which this command should be evaluated.
|
||||
|
@ -393,37 +390,19 @@ pub fn init_input() {
|
|||
}
|
||||
}
|
||||
|
||||
pub struct Inputter {
|
||||
in_fd: RawFd,
|
||||
queue: VecDeque<CharEvent>,
|
||||
paste_buffer: Option<Vec<u8>>,
|
||||
// We need a parser to evaluate bindings.
|
||||
parser: Rc<Parser>,
|
||||
input_function_args: Vec<char>,
|
||||
function_status: bool,
|
||||
|
||||
// Transient storage to avoid repeated allocations.
|
||||
event_storage: Vec<CharEvent>,
|
||||
impl InputEventQueuer for ReaderData {
|
||||
fn get_input_data(&self) -> &InputData {
|
||||
&self.input_data
|
||||
}
|
||||
|
||||
impl InputEventQueuer for Inputter {
|
||||
fn get_queue(&self) -> &VecDeque<CharEvent> {
|
||||
&self.queue
|
||||
}
|
||||
|
||||
fn get_queue_mut(&mut self) -> &mut VecDeque<CharEvent> {
|
||||
&mut self.queue
|
||||
}
|
||||
|
||||
/// Return the fd corresponding to stdin.
|
||||
fn get_in_fd(&self) -> RawFd {
|
||||
self.in_fd
|
||||
fn get_input_data_mut(&mut self) -> &mut InputData {
|
||||
&mut self.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();
|
||||
}
|
||||
}
|
||||
|
@ -434,7 +413,7 @@ impl InputEventQueuer for Inputter {
|
|||
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();
|
||||
|
@ -452,19 +431,17 @@ impl InputEventQueuer for Inputter {
|
|||
}
|
||||
|
||||
fn uvar_change_notified(&mut self) {
|
||||
self.parser.sync_uvars_and_fire(true /* always */);
|
||||
self.parser().sync_uvars_and_fire(true /* always */);
|
||||
}
|
||||
|
||||
fn paste_start_buffering(&mut self) {
|
||||
self.paste_buffer = Some(vec![]);
|
||||
self.input_data.paste_buffer = Some(vec![]);
|
||||
self.push_front(CharEvent::from_readline(ReadlineCmd::BeginUndoGroup));
|
||||
}
|
||||
fn paste_is_buffering(&self) -> bool {
|
||||
self.paste_buffer.is_some()
|
||||
}
|
||||
|
||||
fn paste_commit(&mut self) {
|
||||
self.push_front(CharEvent::from_readline(ReadlineCmd::EndUndoGroup));
|
||||
let Some(buffer) = self.paste_buffer.take() else {
|
||||
let Some(buffer) = self.input_data.paste_buffer.take() else {
|
||||
return;
|
||||
};
|
||||
self.push_front(CharEvent::Command(sprintf!(
|
||||
|
@ -472,116 +449,11 @@ impl InputEventQueuer for Inputter {
|
|||
escape(&str2wcstring(&buffer))
|
||||
)));
|
||||
}
|
||||
fn paste_push_char(&mut self, b: u8) {
|
||||
self.paste_buffer.as_mut().unwrap().push(b)
|
||||
}
|
||||
}
|
||||
|
||||
impl Inputter {
|
||||
/// Construct from a parser, and the fd from which to read.
|
||||
pub fn new(parser: Rc<Parser>, in_fd: RawFd) -> Inputter {
|
||||
Inputter {
|
||||
in_fd,
|
||||
queue: VecDeque::new(),
|
||||
paste_buffer: None,
|
||||
parser,
|
||||
input_function_args: Vec::new(),
|
||||
function_status: false,
|
||||
event_storage: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn function_push_arg(&mut self, arg: char) {
|
||||
self.input_function_args.push(arg);
|
||||
}
|
||||
|
||||
pub fn function_pop_arg(&mut self) -> Option<char> {
|
||||
self.input_function_args.pop()
|
||||
}
|
||||
|
||||
fn function_push_args(&mut self, code: ReadlineCmd) {
|
||||
let arity = input_function_arity(code);
|
||||
assert!(
|
||||
self.event_storage.is_empty(),
|
||||
"event_storage should be empty"
|
||||
);
|
||||
let mut skipped = std::mem::take(&mut self.event_storage);
|
||||
for _ in 0..arity {
|
||||
// Skip and queue up any function codes. See issue #2357.
|
||||
let arg: char;
|
||||
loop {
|
||||
let evt = self.readch();
|
||||
if let Some(kevt) = evt.get_key() {
|
||||
if let Some(c) = kevt.key.codepoint_text() {
|
||||
// TODO forward the whole key
|
||||
arg = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
skipped.push(evt);
|
||||
}
|
||||
self.function_push_arg(arg);
|
||||
}
|
||||
|
||||
// Push the function codes back into the input stream.
|
||||
self.insert_front(skipped.drain(..));
|
||||
self.event_storage = skipped;
|
||||
self.event_storage.clear();
|
||||
}
|
||||
|
||||
/// Perform the action of the specified binding.
|
||||
fn mapping_execute(&mut self, m: &InputMapping) {
|
||||
let has_command = m
|
||||
.commands
|
||||
.iter()
|
||||
.any(|cmd| input_function_get_code(cmd).is_none());
|
||||
if has_command {
|
||||
self.push_front(CharEvent::from_check_exit());
|
||||
}
|
||||
for cmd in m.commands.iter().rev() {
|
||||
let evt = match input_function_get_code(cmd) {
|
||||
Some(code) => {
|
||||
self.function_push_args(code);
|
||||
// At this point, the sequence is only used for reinserting the keys into
|
||||
// the event queue for self-insert. Modifiers make no sense here so drop them.
|
||||
CharEvent::from_readline_seq(
|
||||
code,
|
||||
m.seq
|
||||
.iter()
|
||||
.filter(|key| key.modifiers.is_none())
|
||||
.map(|key| key.codepoint)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
None => CharEvent::Command(cmd.clone()),
|
||||
};
|
||||
self.push_front(evt);
|
||||
}
|
||||
// Missing bind mode indicates to not reset the mode (#2871)
|
||||
if let Some(mode) = m.sets_mode.as_ref() {
|
||||
self.push_front(CharEvent::Command(sprintf!(
|
||||
"set --global %s %s",
|
||||
FISH_BIND_MODE_VAR,
|
||||
escape(mode)
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
/// Enqueue a char event to the queue of unread characters that input_readch will return before
|
||||
/// actually reading from fd 0.
|
||||
pub fn queue_char(&mut self, ch: CharEvent) {
|
||||
self.queue.push_back(ch);
|
||||
}
|
||||
|
||||
/// Sets the return status of the most recently executed input function.
|
||||
pub fn function_set_status(&mut self, status: bool) {
|
||||
self.function_status = status;
|
||||
}
|
||||
}
|
||||
|
||||
/// A struct which allows accumulating input events, or returns them to the queue.
|
||||
/// This contains a list of events which have been dequeued, and a current index into that list.
|
||||
struct EventQueuePeeker<'q> {
|
||||
pub struct EventQueuePeeker<'q, Queuer: InputEventQueuer + ?Sized> {
|
||||
/// The list of events which have been dequeued.
|
||||
peeked: Vec<CharEvent>,
|
||||
|
||||
|
@ -594,11 +466,11 @@ struct EventQueuePeeker<'q> {
|
|||
subidx: usize,
|
||||
|
||||
/// The queue from which to read more events.
|
||||
event_queue: &'q mut Inputter,
|
||||
event_queue: &'q mut Queuer,
|
||||
}
|
||||
|
||||
impl EventQueuePeeker<'_> {
|
||||
fn new(event_queue: &mut Inputter) -> EventQueuePeeker {
|
||||
impl<'q, Queuer: InputEventQueuer + ?Sized> EventQueuePeeker<'q, Queuer> {
|
||||
pub fn new(event_queue: &'q mut Queuer) -> Self {
|
||||
EventQueuePeeker {
|
||||
peeked: Vec::new(),
|
||||
had_timeout: false,
|
||||
|
@ -745,24 +617,13 @@ impl EventQueuePeeker<'_> {
|
|||
}
|
||||
|
||||
/// Reset our index back to 0.
|
||||
fn restart(&mut self) {
|
||||
pub fn restart(&mut self) {
|
||||
self.idx = 0;
|
||||
self.subidx = 0;
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EventQueuePeeker<'_> {
|
||||
fn drop(&mut self) {
|
||||
assert!(
|
||||
self.idx == 0 && self.subidx == 0,
|
||||
"Events left on the queue - missing restart or consume?",
|
||||
);
|
||||
self.event_queue.insert_front(self.peeked.drain(self.idx..));
|
||||
}
|
||||
}
|
||||
|
||||
/// Return true if a given `peeker` matches a given sequence of char events given by `str`.
|
||||
fn try_peek_sequence(peeker: &mut EventQueuePeeker, style: &KeyNameStyle, seq: &[Key]) -> bool {
|
||||
/// Return true if this `peeker` matches a given sequence of char events given by `str`.
|
||||
fn try_peek_sequence(&mut self, style: &KeyNameStyle, seq: &[Key]) -> bool {
|
||||
assert!(
|
||||
!seq.is_empty(),
|
||||
"Empty sequence passed to try_peek_sequence"
|
||||
|
@ -772,12 +633,12 @@ fn try_peek_sequence(peeker: &mut EventQueuePeeker, style: &KeyNameStyle, seq: &
|
|||
// If we just read an escape, we need to add a timeout for the next char,
|
||||
// to distinguish between the actual escape key and an "alt"-modifier.
|
||||
let escaped = *style != KeyNameStyle::Plain && prev == Key::from_raw(key::Escape);
|
||||
if !peeker.next_is_char(style, *key, escaped) {
|
||||
if !self.next_is_char(style, *key, escaped) {
|
||||
return false;
|
||||
}
|
||||
prev = *key;
|
||||
}
|
||||
if peeker.subidx != 0 {
|
||||
if self.subidx != 0 {
|
||||
FLOG!(
|
||||
reader,
|
||||
"legacy binding matched prefix of key encoding but did not consume all of it"
|
||||
|
@ -787,17 +648,19 @@ fn try_peek_sequence(peeker: &mut EventQueuePeeker, style: &KeyNameStyle, seq: &
|
|||
true
|
||||
}
|
||||
|
||||
/// Return the first mapping that matches, walking first over the user's mapping list, then the
|
||||
/// preset list.
|
||||
/// Return the first mapping that matches from the given mapping set, walking first over the
|
||||
/// user's mapping list, then the preset list.
|
||||
/// Return none if nothing matches, or if we may have matched a longer sequence but it was
|
||||
/// interrupted by a readline event.
|
||||
impl Inputter {
|
||||
fn find_mapping(vars: &dyn Environment, peeker: &mut EventQueuePeeker) -> Option<InputMapping> {
|
||||
pub fn find_mapping(
|
||||
&mut self,
|
||||
vars: &dyn Environment,
|
||||
ip: &InputMappingSet,
|
||||
) -> Option<InputMapping> {
|
||||
let mut generic: Option<&InputMapping> = None;
|
||||
let bind_mode = input_get_bind_mode(vars);
|
||||
let mut escape: Option<&InputMapping> = None;
|
||||
|
||||
let ip = input_mappings();
|
||||
let ml = ip.mapping_list.iter().chain(ip.preset_mapping_list.iter());
|
||||
for m in ml {
|
||||
if m.mode != bind_mode {
|
||||
|
@ -813,7 +676,7 @@ impl Inputter {
|
|||
}
|
||||
|
||||
// FLOG!(reader, "trying mapping", format!("{:?}", m));
|
||||
if try_peek_sequence(peeker, &m.key_name_style, &m.seq) {
|
||||
if self.try_peek_sequence(&m.key_name_style, &m.seq) {
|
||||
// A binding for just escape should also be deferred
|
||||
// so escape sequences take precedence.
|
||||
if m.seq == vec![Key::from_raw(key::Escape)] {
|
||||
|
@ -824,9 +687,9 @@ impl Inputter {
|
|||
return Some(m.clone());
|
||||
}
|
||||
}
|
||||
peeker.restart();
|
||||
self.restart();
|
||||
}
|
||||
if peeker.char_sequence_interrupted() {
|
||||
if self.char_sequence_interrupted() {
|
||||
// We might have matched a longer sequence, but we were interrupted, e.g. by a signal.
|
||||
FLOG!(reader, "torn sequence, rearranging events");
|
||||
return None;
|
||||
|
@ -834,7 +697,7 @@ impl Inputter {
|
|||
|
||||
if escape.is_some() {
|
||||
// We need to reconsume the escape.
|
||||
peeker.next();
|
||||
self.next();
|
||||
return escape.cloned();
|
||||
}
|
||||
|
||||
|
@ -844,64 +707,21 @@ impl Inputter {
|
|||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn mapping_execute_matching_or_generic(&mut self) {
|
||||
let vars = self.parser.vars_ref();
|
||||
let mut peeker = EventQueuePeeker::new(self);
|
||||
// Check for ordinary mappings.
|
||||
if let Some(mapping) = Self::find_mapping(&*vars, &mut peeker) {
|
||||
FLOG!(
|
||||
reader,
|
||||
format!("Found mapping {:?} from {:?}", &mapping, &peeker.peeked)
|
||||
);
|
||||
peeker.consume();
|
||||
self.mapping_execute(&mapping);
|
||||
return;
|
||||
}
|
||||
peeker.restart();
|
||||
|
||||
if peeker.char_sequence_interrupted() {
|
||||
// This may happen if we received a signal in the middle of an escape sequence or other
|
||||
// multi-char binding. Move these non-char events to the front of the queue, handle them
|
||||
// first, and then later we'll return and try the sequence again. See #8628.
|
||||
peeker.consume();
|
||||
self.promote_interruptions_to_front();
|
||||
return;
|
||||
}
|
||||
|
||||
FLOG!(reader, "no generic found, ignoring char...");
|
||||
let _ = peeker.next();
|
||||
peeker.consume();
|
||||
}
|
||||
|
||||
/// Helper function. Picks through the queue of incoming characters until we get to one that's not a
|
||||
/// readline function.
|
||||
fn read_characters_no_readline(&mut self) -> CharEvent {
|
||||
impl<Queue: InputEventQueuer + ?Sized> Drop for EventQueuePeeker<'_, Queue> {
|
||||
fn drop(&mut self) {
|
||||
assert!(
|
||||
self.event_storage.is_empty(),
|
||||
"event_storage should be empty"
|
||||
self.idx == 0 && self.subidx == 0,
|
||||
"Events left on the queue - missing restart or consume?",
|
||||
);
|
||||
let mut saved_events = std::mem::take(&mut self.event_storage);
|
||||
|
||||
let evt_to_return: CharEvent;
|
||||
loop {
|
||||
let evt = self.readch();
|
||||
if evt.is_readline_or_command() {
|
||||
saved_events.push(evt);
|
||||
} else {
|
||||
evt_to_return = evt;
|
||||
break;
|
||||
self.event_queue.insert_front(self.peeked.drain(self.idx..));
|
||||
}
|
||||
}
|
||||
|
||||
// Restore any readline functions
|
||||
self.insert_front(saved_events.drain(..));
|
||||
self.event_storage = saved_events;
|
||||
self.event_storage.clear();
|
||||
evt_to_return
|
||||
}
|
||||
|
||||
/// Read a key from stdin.
|
||||
/// Support for reading keys from the terminal for the Reader.
|
||||
impl ReaderData {
|
||||
/// Read a key from our fd.
|
||||
pub fn read_char(&mut self) -> CharEvent {
|
||||
// Clear the interrupted flag.
|
||||
reader_reset_interrupted();
|
||||
|
@ -930,8 +750,9 @@ impl Inputter {
|
|||
}
|
||||
ReadlineCmd::FuncAnd | ReadlineCmd::FuncOr => {
|
||||
// If previous function has bad status, skip all functions that follow us.
|
||||
if (!self.function_status && readline_event.cmd == ReadlineCmd::FuncAnd)
|
||||
|| (self.function_status && readline_event.cmd == ReadlineCmd::FuncOr)
|
||||
let fs = self.get_function_status();
|
||||
if (!fs && readline_event.cmd == ReadlineCmd::FuncAnd)
|
||||
|| (fs && readline_event.cmd == ReadlineCmd::FuncOr)
|
||||
{
|
||||
self.drop_leading_readline_events();
|
||||
}
|
||||
|
@ -969,6 +790,135 @@ impl Inputter {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn mapping_execute_matching_or_generic(&mut self) {
|
||||
let vars = self.parser().vars_ref();
|
||||
let mut peeker = EventQueuePeeker::new(self);
|
||||
// Check for ordinary mappings.
|
||||
let ip = input_mappings();
|
||||
if let Some(mapping) = peeker.find_mapping(&*vars, &ip) {
|
||||
FLOG!(
|
||||
reader,
|
||||
format!("Found mapping {:?} from {:?}", &mapping, &peeker.peeked)
|
||||
);
|
||||
peeker.consume();
|
||||
self.mapping_execute(&mapping);
|
||||
return;
|
||||
}
|
||||
std::mem::drop(ip);
|
||||
peeker.restart();
|
||||
|
||||
if peeker.char_sequence_interrupted() {
|
||||
// This may happen if we received a signal in the middle of an escape sequence or other
|
||||
// multi-char binding. Move these non-char events to the front of the queue, handle them
|
||||
// first, and then later we'll return and try the sequence again. See #8628.
|
||||
peeker.consume();
|
||||
self.promote_interruptions_to_front();
|
||||
return;
|
||||
}
|
||||
|
||||
FLOG!(reader, "no generic found, ignoring char...");
|
||||
let _ = peeker.next();
|
||||
peeker.consume();
|
||||
}
|
||||
|
||||
/// Helper function. Picks through the queue of incoming characters until we get to one that's not a
|
||||
/// readline function.
|
||||
fn read_characters_no_readline(&mut self) -> CharEvent {
|
||||
let mut saved_events = std::mem::take(&mut self.get_input_data_mut().event_storage);
|
||||
assert!(saved_events.is_empty(), "event_storage should be empty");
|
||||
|
||||
let evt_to_return: CharEvent;
|
||||
loop {
|
||||
let evt = self.readch();
|
||||
if evt.is_readline_or_command() {
|
||||
saved_events.push(evt);
|
||||
} else {
|
||||
evt_to_return = evt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Restore any readline functions
|
||||
self.insert_front(saved_events.drain(..));
|
||||
saved_events.clear();
|
||||
self.get_input_data_mut().event_storage = saved_events;
|
||||
evt_to_return
|
||||
}
|
||||
|
||||
/// Perform the action of the specified binding.
|
||||
fn mapping_execute(&mut self, m: &InputMapping) {
|
||||
let has_command = m
|
||||
.commands
|
||||
.iter()
|
||||
.any(|cmd| input_function_get_code(cmd).is_none());
|
||||
if has_command {
|
||||
self.push_front(CharEvent::from_check_exit());
|
||||
}
|
||||
for cmd in m.commands.iter().rev() {
|
||||
let evt = match input_function_get_code(cmd) {
|
||||
Some(code) => {
|
||||
self.function_push_args(code);
|
||||
// At this point, the sequence is only used for reinserting the keys into
|
||||
// the event queue for self-insert. Modifiers make no sense here so drop them.
|
||||
CharEvent::from_readline_seq(
|
||||
code,
|
||||
m.seq
|
||||
.iter()
|
||||
.filter(|key| key.modifiers.is_none())
|
||||
.map(|key| key.codepoint)
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
None => CharEvent::Command(cmd.clone()),
|
||||
};
|
||||
self.push_front(evt);
|
||||
}
|
||||
// Missing bind mode indicates to not reset the mode (#2871)
|
||||
if let Some(mode) = m.sets_mode.as_ref() {
|
||||
self.push_front(CharEvent::Command(sprintf!(
|
||||
"set --global %s %s",
|
||||
FISH_BIND_MODE_VAR,
|
||||
escape(mode)
|
||||
)));
|
||||
}
|
||||
}
|
||||
|
||||
fn function_push_arg(&mut self, arg: char) {
|
||||
self.get_input_data_mut().input_function_args.push(arg);
|
||||
}
|
||||
|
||||
pub fn function_pop_arg(&mut self) -> Option<char> {
|
||||
self.get_input_data_mut().input_function_args.pop()
|
||||
}
|
||||
|
||||
fn function_push_args(&mut self, code: ReadlineCmd) {
|
||||
let arity = input_function_arity(code);
|
||||
let mut skipped = std::mem::take(&mut self.get_input_data_mut().event_storage);
|
||||
assert!(skipped.is_empty(), "event_storage should be empty");
|
||||
|
||||
for _ in 0..arity {
|
||||
// Skip and queue up any function codes. See issue #2357.
|
||||
let arg: char;
|
||||
loop {
|
||||
let evt = self.readch();
|
||||
if let Some(kevt) = evt.get_key() {
|
||||
if let Some(c) = kevt.key.codepoint_text() {
|
||||
// TODO forward the whole key
|
||||
arg = c;
|
||||
break;
|
||||
}
|
||||
}
|
||||
skipped.push(evt);
|
||||
}
|
||||
self.function_push_arg(arg);
|
||||
}
|
||||
|
||||
// Push the function codes back into the input stream.
|
||||
self.insert_front(skipped.drain(..));
|
||||
skipped.clear();
|
||||
self.get_input_data_mut().event_storage = skipped;
|
||||
}
|
||||
}
|
||||
|
||||
impl InputMappingSet {
|
||||
|
|
|
@ -508,12 +508,58 @@ fn parse_mask(mask: u32) -> Modifiers {
|
|||
}
|
||||
}
|
||||
|
||||
// A data type used by the input machinery.
|
||||
pub struct InputData {
|
||||
// The file descriptor from which we read input, often stdin.
|
||||
pub in_fd: RawFd,
|
||||
|
||||
// Queue of unread characters.
|
||||
pub queue: VecDeque<CharEvent>,
|
||||
|
||||
// The current paste buffer, if any.
|
||||
pub paste_buffer: Option<Vec<u8>>,
|
||||
|
||||
// The arguments to the most recently invoked input function.
|
||||
pub input_function_args: Vec<char>,
|
||||
|
||||
// The return status of the most recently invoked input function.
|
||||
pub function_status: bool,
|
||||
|
||||
// Transient storage to avoid repeated allocations.
|
||||
pub event_storage: Vec<CharEvent>,
|
||||
}
|
||||
|
||||
impl InputData {
|
||||
/// Construct from the fd from which to read.
|
||||
pub fn new(in_fd: RawFd) -> Self {
|
||||
Self {
|
||||
in_fd,
|
||||
queue: VecDeque::new(),
|
||||
paste_buffer: None,
|
||||
input_function_args: Vec::new(),
|
||||
function_status: false,
|
||||
event_storage: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Enqueue a char event to the queue of unread characters that input_readch will return before
|
||||
/// actually reading from fd 0.
|
||||
pub fn queue_char(&mut self, ch: CharEvent) {
|
||||
self.queue.push_back(ch);
|
||||
}
|
||||
|
||||
/// Sets the return status of the most recently executed input function.
|
||||
pub fn function_set_status(&mut self, status: bool) {
|
||||
self.function_status = status;
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait which knows how to produce a stream of input events.
|
||||
/// Note this is conceptually a "base class" with override points.
|
||||
pub trait InputEventQueuer {
|
||||
/// Return the next event in the queue, or none if the queue is empty.
|
||||
fn try_pop(&mut self) -> Option<CharEvent> {
|
||||
self.get_queue_mut().pop_front()
|
||||
self.get_input_data_mut().queue.pop_front()
|
||||
}
|
||||
|
||||
/// Function used by input_readch to read bytes from stdin until enough bytes have been read to
|
||||
|
@ -1053,12 +1099,14 @@ pub trait InputEventQueuer {
|
|||
None
|
||||
}
|
||||
|
||||
/// Return our queue. These are "abstract" methods to be implemented by concrete types.
|
||||
fn get_queue(&self) -> &VecDeque<CharEvent>;
|
||||
fn get_queue_mut(&mut self) -> &mut VecDeque<CharEvent>;
|
||||
/// Return the fd from which to read.
|
||||
fn get_in_fd(&self) -> RawFd {
|
||||
self.get_input_data().in_fd
|
||||
}
|
||||
|
||||
/// Return the fd corresponding to stdin.
|
||||
fn get_in_fd(&self) -> RawFd;
|
||||
/// Return the input data. This is to be implemented by the concrete type.
|
||||
fn get_input_data(&self) -> &InputData;
|
||||
fn get_input_data_mut(&mut self) -> &mut InputData;
|
||||
|
||||
// Support for "bracketed paste"
|
||||
// The way it works is that we acknowledge our support by printing
|
||||
|
@ -1073,28 +1121,44 @@ pub trait InputEventQueuer {
|
|||
// (though it only supports it since then, it seems to be the last term to gain support).
|
||||
//
|
||||
// See http://thejh.net/misc/website-terminal-copy-paste.
|
||||
fn paste_start_buffering(&mut self);
|
||||
fn paste_is_buffering(&self) -> bool;
|
||||
fn paste_push_char(&mut self, _b: u8) {}
|
||||
fn paste_commit(&mut self);
|
||||
|
||||
fn paste_start_buffering(&mut self) {
|
||||
self.get_input_data_mut().paste_buffer = Some(Vec::new());
|
||||
}
|
||||
|
||||
fn paste_is_buffering(&self) -> bool {
|
||||
self.get_input_data().paste_buffer.is_some()
|
||||
}
|
||||
|
||||
fn paste_push_char(&mut self, b: u8) {
|
||||
self.get_input_data_mut()
|
||||
.paste_buffer
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.push(b)
|
||||
}
|
||||
|
||||
fn paste_commit(&mut self) {
|
||||
self.get_input_data_mut().paste_buffer = None;
|
||||
}
|
||||
|
||||
/// Enqueue a character or a readline function to the queue of unread characters that
|
||||
/// readch will return before actually reading from fd 0.
|
||||
fn push_back(&mut self, ch: CharEvent) {
|
||||
self.get_queue_mut().push_back(ch);
|
||||
self.get_input_data_mut().queue.push_back(ch);
|
||||
}
|
||||
|
||||
/// Add a character or a readline function to the front of the queue of unread characters. This
|
||||
/// will be the next character returned by readch.
|
||||
fn push_front(&mut self, ch: CharEvent) {
|
||||
self.get_queue_mut().push_front(ch);
|
||||
self.get_input_data_mut().queue.push_front(ch);
|
||||
}
|
||||
|
||||
/// Find the first sequence of non-char events, and promote them to the front.
|
||||
fn promote_interruptions_to_front(&mut self) {
|
||||
// Find the first sequence of non-char events.
|
||||
// EOF is considered a char: we don't want to pull EOF in front of real chars.
|
||||
let queue = self.get_queue_mut();
|
||||
let queue = &mut self.get_input_data_mut().queue;
|
||||
let is_char = |evt: &CharEvent| evt.is_char() || evt.is_eof();
|
||||
// Find the index of the first non-char event.
|
||||
// If there's none, we're done.
|
||||
|
@ -1120,7 +1184,7 @@ pub trait InputEventQueuer {
|
|||
I: IntoIterator<Item = CharEvent>,
|
||||
I::IntoIter: DoubleEndedIterator,
|
||||
{
|
||||
let queue = self.get_queue_mut();
|
||||
let queue = &mut self.get_input_data_mut().queue;
|
||||
let iter = evts.into_iter().rev();
|
||||
queue.reserve(iter.size_hint().0);
|
||||
for evt in iter {
|
||||
|
@ -1130,7 +1194,7 @@ pub trait InputEventQueuer {
|
|||
|
||||
/// Forget all enqueued readline events in the front of the queue.
|
||||
fn drop_leading_readline_events(&mut self) {
|
||||
let queue = self.get_queue_mut();
|
||||
let queue = &mut self.get_input_data_mut().queue;
|
||||
while let Some(evt) = queue.front() {
|
||||
if evt.is_readline_or_command() {
|
||||
queue.pop_front();
|
||||
|
@ -1145,46 +1209,43 @@ pub trait InputEventQueuer {
|
|||
fn prepare_to_select(&mut self) {}
|
||||
|
||||
/// Called when select() is interrupted by a signal.
|
||||
fn select_interrupted(&mut self);
|
||||
fn select_interrupted(&mut self) {}
|
||||
|
||||
/// Override point for when when select() is interrupted by the universal variable notifier.
|
||||
/// The default does nothing.
|
||||
fn uvar_change_notified(&mut self) {}
|
||||
|
||||
/// Reset the function status.
|
||||
fn get_function_status(&self) -> bool {
|
||||
self.get_input_data().function_status
|
||||
}
|
||||
|
||||
/// Return if we have any lookahead.
|
||||
fn has_lookahead(&self) -> bool {
|
||||
!self.get_queue().is_empty()
|
||||
!self.get_input_data().queue.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// A simple, concrete implementation of InputEventQueuer.
|
||||
pub struct InputEventQueue {
|
||||
queue: VecDeque<CharEvent>,
|
||||
in_fd: RawFd,
|
||||
is_in_bracketed_paste: bool,
|
||||
data: InputData,
|
||||
}
|
||||
|
||||
impl InputEventQueue {
|
||||
pub fn new(in_fd: RawFd) -> InputEventQueue {
|
||||
InputEventQueue {
|
||||
queue: VecDeque::new(),
|
||||
in_fd,
|
||||
is_in_bracketed_paste: false,
|
||||
data: InputData::new(in_fd),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InputEventQueuer for InputEventQueue {
|
||||
fn get_queue(&self) -> &VecDeque<CharEvent> {
|
||||
&self.queue
|
||||
fn get_input_data(&self) -> &InputData {
|
||||
&self.data
|
||||
}
|
||||
|
||||
fn get_queue_mut(&mut self) -> &mut VecDeque<CharEvent> {
|
||||
&mut self.queue
|
||||
}
|
||||
|
||||
fn get_in_fd(&self) -> RawFd {
|
||||
self.in_fd
|
||||
fn get_input_data_mut(&mut self) -> &mut InputData {
|
||||
&mut self.data
|
||||
}
|
||||
|
||||
fn select_interrupted(&mut self) {
|
||||
|
@ -1195,14 +1256,4 @@ impl InputEventQueuer for InputEventQueue {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn paste_start_buffering(&mut self) {
|
||||
self.is_in_bracketed_paste = true;
|
||||
}
|
||||
fn paste_is_buffering(&self) -> bool {
|
||||
self.is_in_bracketed_paste
|
||||
}
|
||||
fn paste_commit(&mut self) {
|
||||
self.is_in_bracketed_paste = false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -70,10 +70,10 @@ use crate::history::{
|
|||
SearchType,
|
||||
};
|
||||
use crate::input::init_input;
|
||||
use crate::input::Inputter;
|
||||
use crate::input_common::terminal_protocols_disable_ifn;
|
||||
use crate::input_common::IS_TMUX;
|
||||
use crate::input_common::{terminal_protocols_enable_ifn, CharEvent, CharInputStyle, ReadlineCmd};
|
||||
use crate::input_common::{
|
||||
terminal_protocols_disable_ifn, terminal_protocols_enable_ifn, CharEvent, CharInputStyle,
|
||||
InputData, ReadlineCmd, IS_TMUX,
|
||||
};
|
||||
use crate::io::IoChain;
|
||||
use crate::kill::{kill_add, kill_replace, kill_yank, kill_yank_rotate};
|
||||
use crate::libc::MB_CUR_MAX;
|
||||
|
@ -496,8 +496,9 @@ pub struct ReaderData {
|
|||
/// The representation of the current screen contents.
|
||||
screen: Screen,
|
||||
|
||||
/// The source of input events.
|
||||
inputter: Inputter,
|
||||
/// Data associated with input events.
|
||||
/// This is made public so that InputEventQueuer can be implemented on us.
|
||||
pub input_data: InputData,
|
||||
queued_repaint: bool,
|
||||
/// The history.
|
||||
history: Arc<History>,
|
||||
|
@ -880,7 +881,7 @@ pub fn reader_set_autosuggestion_enabled(vars: &dyn Environment) {
|
|||
if data.conf.autosuggest_ok != enable {
|
||||
data.conf.autosuggest_ok = enable;
|
||||
data.force_exec_prompt_and_repaint = true;
|
||||
data.inputter
|
||||
data.input_data
|
||||
.queue_char(CharEvent::from_readline(ReadlineCmd::Repaint));
|
||||
}
|
||||
}
|
||||
|
@ -894,7 +895,7 @@ pub fn reader_schedule_prompt_repaint() {
|
|||
};
|
||||
if !data.force_exec_prompt_and_repaint {
|
||||
data.force_exec_prompt_and_repaint = true;
|
||||
data.inputter
|
||||
data.input_data
|
||||
.queue_char(CharEvent::from_readline(ReadlineCmd::Repaint));
|
||||
}
|
||||
}
|
||||
|
@ -914,7 +915,7 @@ pub fn reader_execute_readline_cmd(ch: CharEvent) {
|
|||
data.queued_repaint = true;
|
||||
}
|
||||
if data.queued_repaint {
|
||||
data.inputter.queue_char(ch);
|
||||
data.input_data.queue_char(ch);
|
||||
return;
|
||||
}
|
||||
if data.rls.is_none() {
|
||||
|
@ -1074,7 +1075,7 @@ fn reader_received_sighup() -> bool {
|
|||
|
||||
impl ReaderData {
|
||||
fn new(parser: ParserRef, history: Arc<History>, conf: ReaderConfig) -> Pin<Box<Self>> {
|
||||
let inputter = Inputter::new(parser.clone(), conf.inputfd);
|
||||
let input_data = InputData::new(conf.inputfd);
|
||||
Pin::new(Box::new(Self {
|
||||
canary: Rc::new(()),
|
||||
parser_ref: parser,
|
||||
|
@ -1090,7 +1091,7 @@ impl ReaderData {
|
|||
first_prompt: true,
|
||||
last_flash: Default::default(),
|
||||
screen: Screen::new(),
|
||||
inputter,
|
||||
input_data,
|
||||
queued_repaint: false,
|
||||
history,
|
||||
history_search: Default::default(),
|
||||
|
@ -1162,7 +1163,7 @@ impl ReaderData {
|
|||
}
|
||||
|
||||
/// Access the parser.
|
||||
fn parser(&self) -> &Parser {
|
||||
pub fn parser(&self) -> &Parser {
|
||||
&self.parser_ref
|
||||
}
|
||||
|
||||
|
@ -1965,7 +1966,7 @@ impl ReaderData {
|
|||
|
||||
while accumulated_chars.len() < limit {
|
||||
terminal_protocols_enable_ifn();
|
||||
let evt = self.inputter.read_char();
|
||||
let evt = self.read_char();
|
||||
let CharEvent::Key(kevt) = &evt else {
|
||||
event_needing_handling = Some(evt);
|
||||
break;
|
||||
|
@ -2607,14 +2608,14 @@ impl ReaderData {
|
|||
} else {
|
||||
self.autosuggestion.clear();
|
||||
}
|
||||
self.inputter.function_set_status(true);
|
||||
self.input_data.function_set_status(true);
|
||||
return;
|
||||
}
|
||||
if !self.history_pager_active {
|
||||
self.inputter.function_set_status(false);
|
||||
self.input_data.function_set_status(false);
|
||||
return;
|
||||
}
|
||||
self.inputter.function_set_status(true);
|
||||
self.input_data.function_set_status(true);
|
||||
if let Some(completion) =
|
||||
self.pager.selected_completion(&self.current_page_rendering)
|
||||
{
|
||||
|
@ -2708,7 +2709,7 @@ impl ReaderData {
|
|||
if c == rl::PrevdOrBackwardWord && self.command_line.is_empty() {
|
||||
self.eval_bind_cmd(L!("prevd"));
|
||||
self.force_exec_prompt_and_repaint = true;
|
||||
self.inputter
|
||||
self.input_data
|
||||
.queue_char(CharEvent::from_readline(ReadlineCmd::Repaint));
|
||||
return;
|
||||
}
|
||||
|
@ -2730,7 +2731,7 @@ impl ReaderData {
|
|||
if c == rl::NextdOrForwardWord && self.command_line.is_empty() {
|
||||
self.eval_bind_cmd(L!("nextd"));
|
||||
self.force_exec_prompt_and_repaint = true;
|
||||
self.inputter
|
||||
self.input_data
|
||||
.queue_char(CharEvent::from_readline(ReadlineCmd::Repaint));
|
||||
return;
|
||||
}
|
||||
|
@ -2840,7 +2841,7 @@ impl ReaderData {
|
|||
let success = !self.autosuggestion.is_empty();
|
||||
self.autosuggestion.clear();
|
||||
// Return true if we had a suggestion to clear.
|
||||
self.inputter.function_set_status(success);
|
||||
self.input_data.function_set_status(success);
|
||||
}
|
||||
rl::AcceptAutosuggestion => {
|
||||
self.accept_autosuggestion(true, false, MoveWordStyle::Punctuation);
|
||||
|
@ -3073,10 +3074,10 @@ impl ReaderData {
|
|||
_ => unreachable!(),
|
||||
};
|
||||
let (elt, _el) = self.active_edit_line();
|
||||
if let Some(target) = self.inputter.function_pop_arg() {
|
||||
if let Some(target) = self.function_pop_arg() {
|
||||
let success = self.jump(direction, precision, elt, target);
|
||||
|
||||
self.inputter.function_set_status(success);
|
||||
self.input_data.function_set_status(success);
|
||||
}
|
||||
}
|
||||
rl::RepeatJump => {
|
||||
|
@ -3092,7 +3093,7 @@ impl ReaderData {
|
|||
);
|
||||
}
|
||||
|
||||
self.inputter.function_set_status(success);
|
||||
self.input_data.function_set_status(success);
|
||||
}
|
||||
rl::ReverseRepeatJump => {
|
||||
let (elt, _el) = self.active_edit_line();
|
||||
|
@ -3111,13 +3112,13 @@ impl ReaderData {
|
|||
|
||||
self.last_jump_direction = original_dir;
|
||||
|
||||
self.inputter.function_set_status(success);
|
||||
self.input_data.function_set_status(success);
|
||||
}
|
||||
rl::ExpandAbbr => {
|
||||
if self.expand_abbreviation_at_cursor(1) {
|
||||
self.inputter.function_set_status(true);
|
||||
self.input_data.function_set_status(true);
|
||||
} else {
|
||||
self.inputter.function_set_status(false);
|
||||
self.input_data.function_set_status(false);
|
||||
}
|
||||
}
|
||||
rl::Undo | rl::Redo => {
|
||||
|
|
|
@ -1,18 +1,29 @@
|
|||
use crate::input::{input_mappings, Inputter, KeyNameStyle, DEFAULT_BIND_MODE};
|
||||
use crate::input_common::{CharEvent, ReadlineCmd};
|
||||
use crate::env::EnvStack;
|
||||
use crate::input::{EventQueuePeeker, InputMappingSet, KeyNameStyle, DEFAULT_BIND_MODE};
|
||||
use crate::input_common::{CharEvent, InputData, InputEventQueuer};
|
||||
use crate::key::Key;
|
||||
use crate::parser::Parser;
|
||||
use crate::tests::prelude::*;
|
||||
use crate::wchar::prelude::*;
|
||||
use std::rc::Rc;
|
||||
|
||||
struct TestInputEventQueuer {
|
||||
input_data: InputData,
|
||||
}
|
||||
|
||||
impl InputEventQueuer for TestInputEventQueuer {
|
||||
fn get_input_data(&self) -> &InputData {
|
||||
&self.input_data
|
||||
}
|
||||
fn get_input_data_mut(&mut self) -> &mut InputData {
|
||||
&mut self.input_data
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[serial]
|
||||
fn test_input() {
|
||||
let _cleanup = test_init();
|
||||
use crate::env::EnvStack;
|
||||
let parser = Parser::new(Rc::new(EnvStack::new()), false);
|
||||
let mut input = Inputter::new(parser, libc::STDIN_FILENO);
|
||||
let vars = Rc::new(EnvStack::new());
|
||||
let mut input = TestInputEventQueuer {
|
||||
input_data: InputData::new(i32::MAX), // value doesn't matter since we don't read from it
|
||||
};
|
||||
// Ensure sequences are order independent. Here we add two bindings where the first is a prefix
|
||||
// of the second, and then emit the second key list. The second binding should be invoked, not
|
||||
// the first!
|
||||
|
@ -22,9 +33,8 @@ fn test_input() {
|
|||
|
||||
let default_mode = || DEFAULT_BIND_MODE.to_owned();
|
||||
|
||||
{
|
||||
let mut input_mapping = input_mappings();
|
||||
input_mapping.add1(
|
||||
let mut input_mappings = InputMappingSet::default();
|
||||
input_mappings.add1(
|
||||
prefix_binding,
|
||||
KeyNameStyle::Plain,
|
||||
WString::from_str("up-line"),
|
||||
|
@ -32,7 +42,7 @@ fn test_input() {
|
|||
None,
|
||||
true,
|
||||
);
|
||||
input_mapping.add1(
|
||||
input_mappings.add1(
|
||||
desired_binding.clone(),
|
||||
KeyNameStyle::Plain,
|
||||
WString::from_str("down-line"),
|
||||
|
@ -40,18 +50,15 @@ fn test_input() {
|
|||
None,
|
||||
true,
|
||||
);
|
||||
}
|
||||
|
||||
// Push the desired binding to the queue.
|
||||
for c in desired_binding {
|
||||
input.queue_char(CharEvent::from_key(c));
|
||||
input.input_data.queue_char(CharEvent::from_key(c));
|
||||
}
|
||||
|
||||
// Now test.
|
||||
let evt = input.read_char();
|
||||
if !evt.is_readline() {
|
||||
panic!("Event is not a readline");
|
||||
} else if evt.get_readline() != ReadlineCmd::DownLine {
|
||||
panic!("Expected to read char down_line");
|
||||
}
|
||||
let mut peeker = EventQueuePeeker::new(&mut input);
|
||||
let mapping = peeker.find_mapping(&*vars, &input_mappings);
|
||||
assert!(mapping.is_some());
|
||||
assert!(mapping.unwrap().commands == ["down-line"]);
|
||||
peeker.restart();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue