mirror of
https://github.com/fish-shell/fish-shell.git
synced 2024-12-24 10:23:43 +08:00
df09ab598f
This binding is akin to ForwardSingleChar but it is "passive" in that is not intended to affect the meta state of the shell: autocompletions are not accepted if the cursor is at the end of input and it does not have any effect in the completions pager.
687 lines
22 KiB
Rust
687 lines
22 KiB
Rust
use crate::common::{fish_reserved_codepoint, is_windows_subsystem_for_linux, read_blocked};
|
|
use crate::env::{EnvStack, Environment};
|
|
use crate::fd_readable_set::FdReadableSet;
|
|
use crate::flog::FLOG;
|
|
use crate::reader::reader_current_data;
|
|
use crate::threads::{iothread_port, iothread_service_main};
|
|
use crate::universal_notifier::default_notifier;
|
|
use crate::wchar::{encode_byte_to_char, prelude::*};
|
|
use crate::wutil::encoding::{mbrtowc, zero_mbstate};
|
|
use crate::wutil::fish_wcstol;
|
|
use std::collections::VecDeque;
|
|
use std::os::fd::RawFd;
|
|
use std::ptr;
|
|
use std::sync::atomic::{AtomicUsize, Ordering};
|
|
|
|
// The range of key codes for inputrc-style keyboard functions.
|
|
pub const R_END_INPUT_FUNCTIONS: usize = (ReadlineCmd::ReverseRepeatJump as usize) + 1;
|
|
|
|
/// Hackish: the input style, which describes how char events (only) are applied to the command
|
|
/// line. Note this is set only after applying bindings; it is not set from readb().
|
|
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
|
pub enum CharInputStyle {
|
|
// Insert characters normally.
|
|
Normal,
|
|
|
|
// Insert characters only if the cursor is not at the beginning. Otherwise, discard them.
|
|
NotFirst,
|
|
}
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
#[repr(u8)]
|
|
pub enum ReadlineCmd {
|
|
BeginningOfLine,
|
|
EndOfLine,
|
|
ForwardChar,
|
|
BackwardChar,
|
|
ForwardSingleChar,
|
|
ForwardCharPassive,
|
|
ForwardWord,
|
|
BackwardWord,
|
|
ForwardBigword,
|
|
BackwardBigword,
|
|
NextdOrForwardWord,
|
|
PrevdOrBackwardWord,
|
|
HistorySearchBackward,
|
|
HistorySearchForward,
|
|
HistoryPrefixSearchBackward,
|
|
HistoryPrefixSearchForward,
|
|
HistoryPager,
|
|
HistoryPagerDelete,
|
|
DeleteChar,
|
|
BackwardDeleteChar,
|
|
KillLine,
|
|
Yank,
|
|
YankPop,
|
|
Complete,
|
|
CompleteAndSearch,
|
|
PagerToggleSearch,
|
|
BeginningOfHistory,
|
|
EndOfHistory,
|
|
BackwardKillLine,
|
|
KillWholeLine,
|
|
KillInnerLine,
|
|
KillWord,
|
|
KillBigword,
|
|
BackwardKillWord,
|
|
BackwardKillPathComponent,
|
|
BackwardKillBigword,
|
|
HistoryTokenSearchBackward,
|
|
HistoryTokenSearchForward,
|
|
SelfInsert,
|
|
SelfInsertNotFirst,
|
|
TransposeChars,
|
|
TransposeWords,
|
|
UpcaseWord,
|
|
DowncaseWord,
|
|
CapitalizeWord,
|
|
TogglecaseChar,
|
|
TogglecaseSelection,
|
|
Execute,
|
|
BeginningOfBuffer,
|
|
EndOfBuffer,
|
|
RepaintMode,
|
|
Repaint,
|
|
ForceRepaint,
|
|
UpLine,
|
|
DownLine,
|
|
SuppressAutosuggestion,
|
|
AcceptAutosuggestion,
|
|
BeginSelection,
|
|
SwapSelectionStartStop,
|
|
EndSelection,
|
|
KillSelection,
|
|
InsertLineUnder,
|
|
InsertLineOver,
|
|
ForwardJump,
|
|
BackwardJump,
|
|
ForwardJumpTill,
|
|
BackwardJumpTill,
|
|
FuncAnd,
|
|
FuncOr,
|
|
ExpandAbbr,
|
|
DeleteOrExit,
|
|
Exit,
|
|
CancelCommandline,
|
|
Cancel,
|
|
Undo,
|
|
Redo,
|
|
BeginUndoGroup,
|
|
EndUndoGroup,
|
|
RepeatJump,
|
|
DisableMouseTracking,
|
|
// ncurses uses the obvious name
|
|
ClearScreenAndRepaint,
|
|
// NOTE: This one has to be last.
|
|
ReverseRepeatJump,
|
|
}
|
|
|
|
/// Represents an event on the character input stream.
|
|
#[derive(Debug, Clone)]
|
|
pub enum CharEventType {
|
|
/// A character was entered.
|
|
Char(char),
|
|
|
|
/// A readline event.
|
|
Readline(ReadlineCmd),
|
|
|
|
/// A shell command.
|
|
Command(WString),
|
|
|
|
/// A request to change the input mapping mode.
|
|
SetMode(WString),
|
|
|
|
/// end-of-file was reached.
|
|
Eof,
|
|
|
|
/// An event was handled internally, or an interrupt was received. Check to see if the reader
|
|
/// loop should exit.
|
|
CheckExit,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub struct CharEvent {
|
|
pub evt: CharEventType,
|
|
|
|
// The style to use when inserting characters into the command line.
|
|
// todo!("This is only needed if the type is Readline")
|
|
pub input_style: CharInputStyle,
|
|
|
|
/// The sequence of characters in the input mapping which generated this event.
|
|
/// Note that the generic self-insert case does not have any characters, so this would be empty.
|
|
pub seq: WString,
|
|
}
|
|
|
|
impl CharEvent {
|
|
pub fn is_char(&self) -> bool {
|
|
matches!(self.evt, CharEventType::Char(_))
|
|
}
|
|
|
|
pub fn is_eof(&self) -> bool {
|
|
matches!(self.evt, CharEventType::Eof)
|
|
}
|
|
|
|
pub fn is_check_exit(&self) -> bool {
|
|
matches!(self.evt, CharEventType::CheckExit)
|
|
}
|
|
|
|
pub fn is_readline(&self) -> bool {
|
|
matches!(self.evt, CharEventType::Readline(_))
|
|
}
|
|
|
|
pub fn is_readline_or_command(&self) -> bool {
|
|
matches!(
|
|
self.evt,
|
|
CharEventType::Readline(_) | CharEventType::Command(_) | CharEventType::SetMode(_)
|
|
)
|
|
}
|
|
|
|
pub fn get_char(&self) -> char {
|
|
let CharEventType::Char(c) = self.evt else {
|
|
panic!("Not a char type");
|
|
};
|
|
c
|
|
}
|
|
|
|
pub fn maybe_char(&self) -> Option<char> {
|
|
if let CharEventType::Char(c) = self.evt {
|
|
Some(c)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
pub fn get_readline(&self) -> ReadlineCmd {
|
|
let CharEventType::Readline(c) = self.evt else {
|
|
panic!("Not a readline type");
|
|
};
|
|
c
|
|
}
|
|
|
|
pub fn get_command(&self) -> Option<&wstr> {
|
|
match &self.evt {
|
|
CharEventType::Command(c) => Some(c),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn get_mode(&self) -> Option<&wstr> {
|
|
match &self.evt {
|
|
CharEventType::SetMode(m) => Some(m),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
pub fn from_char(c: char) -> CharEvent {
|
|
CharEvent {
|
|
evt: CharEventType::Char(c),
|
|
input_style: CharInputStyle::Normal,
|
|
seq: WString::new(),
|
|
}
|
|
}
|
|
|
|
pub fn from_readline(cmd: ReadlineCmd) -> CharEvent {
|
|
Self::from_readline_seq(cmd, WString::new())
|
|
}
|
|
|
|
pub fn from_readline_seq(cmd: ReadlineCmd, seq: WString) -> CharEvent {
|
|
CharEvent {
|
|
evt: CharEventType::Readline(cmd),
|
|
input_style: CharInputStyle::Normal,
|
|
seq,
|
|
}
|
|
}
|
|
|
|
pub fn from_command(cmd: WString) -> CharEvent {
|
|
CharEvent {
|
|
evt: CharEventType::Command(cmd),
|
|
input_style: CharInputStyle::Normal,
|
|
seq: WString::new(),
|
|
}
|
|
}
|
|
|
|
pub fn from_set_mode(mode: WString) -> CharEvent {
|
|
CharEvent {
|
|
evt: CharEventType::SetMode(mode),
|
|
input_style: CharInputStyle::Normal,
|
|
seq: WString::new(),
|
|
}
|
|
}
|
|
|
|
pub fn from_check_exit() -> CharEvent {
|
|
CharEvent {
|
|
evt: CharEventType::CheckExit,
|
|
input_style: CharInputStyle::Normal,
|
|
seq: WString::new(),
|
|
}
|
|
}
|
|
|
|
pub fn from_eof() -> CharEvent {
|
|
CharEvent {
|
|
evt: CharEventType::Eof,
|
|
input_style: CharInputStyle::Normal,
|
|
seq: WString::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Time in milliseconds to wait for another byte to be available for reading
|
|
/// after \x1B is read before assuming that escape key was pressed, and not an
|
|
/// escape sequence.
|
|
const WAIT_ON_ESCAPE_DEFAULT: usize = 30;
|
|
static WAIT_ON_ESCAPE_MS: AtomicUsize = AtomicUsize::new(WAIT_ON_ESCAPE_DEFAULT);
|
|
|
|
const WAIT_ON_SEQUENCE_KEY_INFINITE: usize = usize::MAX;
|
|
static WAIT_ON_SEQUENCE_KEY_MS: AtomicUsize = AtomicUsize::new(WAIT_ON_SEQUENCE_KEY_INFINITE);
|
|
|
|
/// Internal function used by readch to read one byte.
|
|
/// This calls select() on three fds: input (e.g. stdin), the ioport notifier fd (for main thread
|
|
/// requests), and the uvar notifier. This returns either the byte which was read, or one of the
|
|
/// special values below.
|
|
enum ReadbResult {
|
|
// A byte was successfully read.
|
|
Byte(u8),
|
|
|
|
// The in fd has been closed.
|
|
Eof,
|
|
|
|
// select() was interrupted by a signal.
|
|
Interrupted,
|
|
|
|
// Our uvar notifier reported a change (either through poll() or its fd).
|
|
UvarNotified,
|
|
|
|
// Our ioport reported a change, so service main thread requests.
|
|
IOPortNotified,
|
|
}
|
|
|
|
fn readb(in_fd: RawFd) -> ReadbResult {
|
|
assert!(in_fd >= 0, "Invalid in fd");
|
|
let mut fdset = FdReadableSet::new();
|
|
loop {
|
|
fdset.clear();
|
|
fdset.add(in_fd);
|
|
|
|
// Add the completion ioport.
|
|
let ioport_fd = iothread_port();
|
|
fdset.add(ioport_fd);
|
|
|
|
// Get the uvar notifier fd (possibly none).
|
|
let notifier = default_notifier();
|
|
let notifier_fd = notifier.notification_fd();
|
|
if let Some(notifier_fd) = notifier.notification_fd() {
|
|
fdset.add(notifier_fd);
|
|
}
|
|
|
|
// Here's where we call select().
|
|
let select_res = fdset.check_readable(FdReadableSet::kNoTimeout);
|
|
if select_res < 0 {
|
|
let err = errno::errno().0;
|
|
if err == libc::EINTR || err == libc::EAGAIN {
|
|
// A signal.
|
|
return ReadbResult::Interrupted;
|
|
} else {
|
|
// Some fd was invalid, so probably the tty has been closed.
|
|
return ReadbResult::Eof;
|
|
}
|
|
}
|
|
|
|
// select() did not return an error, so we may have a readable fd.
|
|
// The priority order is: uvars, stdin, ioport.
|
|
// Check to see if we want a universal variable barrier.
|
|
if let Some(notifier_fd) = notifier_fd {
|
|
if fdset.test(notifier_fd) && notifier.notification_fd_became_readable(notifier_fd) {
|
|
return ReadbResult::UvarNotified;
|
|
}
|
|
}
|
|
|
|
// Check stdin.
|
|
if fdset.test(in_fd) {
|
|
let mut arr: [u8; 1] = [0];
|
|
if read_blocked(in_fd, &mut arr) != Ok(1) {
|
|
// The terminal has been closed.
|
|
return ReadbResult::Eof;
|
|
}
|
|
// The common path is to return a u8.
|
|
return ReadbResult::Byte(arr[0]);
|
|
}
|
|
|
|
// Check for iothread completions only if there is no data to be read from the stdin.
|
|
// This gives priority to the foreground.
|
|
if fdset.test(ioport_fd) {
|
|
return ReadbResult::IOPortNotified;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the wait_on_escape_ms value in response to the fish_escape_delay_ms user variable being
|
|
// set.
|
|
pub fn update_wait_on_escape_ms(vars: &EnvStack) {
|
|
let fish_escape_delay_ms = vars.get_unless_empty(L!("fish_escape_delay_ms"));
|
|
let Some(fish_escape_delay_ms) = fish_escape_delay_ms else {
|
|
WAIT_ON_ESCAPE_MS.store(WAIT_ON_ESCAPE_DEFAULT, Ordering::Relaxed);
|
|
return;
|
|
};
|
|
let fish_escape_delay_ms = fish_escape_delay_ms.as_string();
|
|
match fish_wcstol(&fish_escape_delay_ms) {
|
|
Ok(val) if (10..5000).contains(&val) => {
|
|
WAIT_ON_ESCAPE_MS.store(val.try_into().unwrap(), Ordering::Relaxed);
|
|
}
|
|
_ => {
|
|
eprintln!(
|
|
concat!(
|
|
"ignoring fish_escape_delay_ms: value '{}' ",
|
|
"is not an integer or is < 10 or >= 5000 ms"
|
|
),
|
|
fish_escape_delay_ms
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the wait_on_sequence_key_ms value in response to the fish_sequence_key_delay_ms user
|
|
// variable being set.
|
|
pub fn update_wait_on_sequence_key_ms(vars: &EnvStack) {
|
|
let sequence_key_time_ms = vars.get_unless_empty(L!("fish_sequence_key_delay_ms"));
|
|
let Some(sequence_key_time_ms) = sequence_key_time_ms else {
|
|
WAIT_ON_SEQUENCE_KEY_MS.store(WAIT_ON_SEQUENCE_KEY_INFINITE, Ordering::Relaxed);
|
|
return;
|
|
};
|
|
let sequence_key_time_ms = sequence_key_time_ms.as_string();
|
|
match fish_wcstol(&sequence_key_time_ms) {
|
|
Ok(val) if (10..5000).contains(&val) => {
|
|
WAIT_ON_SEQUENCE_KEY_MS.store(val.try_into().unwrap(), Ordering::Relaxed);
|
|
}
|
|
_ => {
|
|
eprintln!(
|
|
concat!(
|
|
"ignoring fish_sequence_key_delay_ms: value '{}' ",
|
|
"is not an integer or is < 10 or >= 5000 ms"
|
|
),
|
|
sequence_key_time_ms
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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()
|
|
}
|
|
|
|
/// Function used by input_readch to read bytes from stdin until enough bytes have been read to
|
|
/// convert them to a wchar_t. Conversion is done using mbrtowc. If a character has previously
|
|
/// been read and then 'unread' using \c input_common_unreadch, that character is returned.
|
|
fn readch(&mut self) -> CharEvent {
|
|
let mut res: char = '\0';
|
|
let mut state = zero_mbstate();
|
|
let mut bytes = [0; 64 * 16];
|
|
let mut num_bytes = 0;
|
|
loop {
|
|
// Do we have something enqueued already?
|
|
// Note this may be initially true, or it may become true through calls to
|
|
// iothread_service_main() or env_universal_barrier() below.
|
|
if let Some(mevt) = self.try_pop() {
|
|
return mevt;
|
|
}
|
|
|
|
// We are going to block; but first allow any override to inject events.
|
|
self.prepare_to_select();
|
|
if let Some(mevt) = self.try_pop() {
|
|
return mevt;
|
|
}
|
|
|
|
let rr = readb(self.get_in_fd());
|
|
match rr {
|
|
ReadbResult::Eof => {
|
|
return CharEvent::from_eof();
|
|
}
|
|
|
|
ReadbResult::Interrupted => {
|
|
// FIXME: here signals may break multibyte sequences.
|
|
self.select_interrupted();
|
|
}
|
|
|
|
ReadbResult::UvarNotified => {
|
|
self.uvar_change_notified();
|
|
}
|
|
|
|
ReadbResult::IOPortNotified => {
|
|
iothread_service_main(reader_current_data().unwrap());
|
|
}
|
|
|
|
ReadbResult::Byte(read_byte) => {
|
|
if crate::libc::MB_CUR_MAX() == 1 {
|
|
// single-byte locale, all values are legal
|
|
// FIXME: this looks wrong, this falsely assumes that
|
|
// the single-byte locale is compatible with Unicode upper-ASCII.
|
|
res = read_byte.into();
|
|
return CharEvent::from_char(res);
|
|
}
|
|
let mut codepoint = u32::from(res);
|
|
let sz = unsafe {
|
|
mbrtowc(
|
|
std::ptr::addr_of_mut!(codepoint).cast(),
|
|
std::ptr::addr_of!(read_byte).cast(),
|
|
1,
|
|
&mut state,
|
|
)
|
|
} as isize;
|
|
match sz {
|
|
-1 => {
|
|
FLOG!(reader, "Illegal input");
|
|
return CharEvent::from_check_exit();
|
|
}
|
|
-2 => {
|
|
// Sequence not yet complete.
|
|
bytes[num_bytes] = read_byte;
|
|
num_bytes += 1;
|
|
continue;
|
|
}
|
|
0 => {
|
|
// Actual nul char.
|
|
return CharEvent::from_char('\0');
|
|
}
|
|
_ => (),
|
|
}
|
|
if let Some(res) = char::from_u32(codepoint) {
|
|
// Sequence complete.
|
|
if !fish_reserved_codepoint(res) {
|
|
return CharEvent::from_char(res);
|
|
}
|
|
}
|
|
bytes[num_bytes] = read_byte;
|
|
num_bytes += 1;
|
|
for &b in &bytes[1..num_bytes] {
|
|
let c = CharEvent::from_char(encode_byte_to_char(b));
|
|
self.push_back(c);
|
|
}
|
|
let res = CharEvent::from_char(encode_byte_to_char(bytes[0]));
|
|
return res;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn readch_timed_esc(&mut self) -> Option<CharEvent> {
|
|
self.readch_timed(WAIT_ON_ESCAPE_MS.load(Ordering::Relaxed))
|
|
}
|
|
|
|
fn readch_timed_sequence_key(&mut self) -> Option<CharEvent> {
|
|
let wait_on_sequence_key_ms = WAIT_ON_SEQUENCE_KEY_MS.load(Ordering::Relaxed);
|
|
if wait_on_sequence_key_ms == WAIT_ON_SEQUENCE_KEY_INFINITE {
|
|
return Some(self.readch());
|
|
}
|
|
self.readch_timed(wait_on_sequence_key_ms)
|
|
}
|
|
|
|
/// Like readch(), except it will wait at most wait_time_ms milliseconds for a
|
|
/// character to be available for reading.
|
|
/// \return None on timeout, the event on success.
|
|
fn readch_timed(&mut self, wait_time_ms: usize) -> Option<CharEvent> {
|
|
if let Some(evt) = self.try_pop() {
|
|
return Some(evt);
|
|
}
|
|
|
|
// We are not prepared to handle a signal immediately; we only want to know if we get input on
|
|
// our fd before the timeout. Use pselect to block all signals; we will handle signals
|
|
// before the next call to readch().
|
|
let mut sigs: libc::sigset_t = unsafe { std::mem::zeroed() };
|
|
unsafe { libc::sigfillset(&mut sigs) };
|
|
|
|
// pselect expects timeouts in nanoseconds.
|
|
const NSEC_PER_MSEC: u64 = 1000 * 1000;
|
|
const NSEC_PER_SEC: u64 = NSEC_PER_MSEC * 1000;
|
|
let wait_nsec: u64 = (wait_time_ms as u64) * NSEC_PER_MSEC;
|
|
let timeout = libc::timespec {
|
|
tv_sec: (wait_nsec / NSEC_PER_SEC).try_into().unwrap(),
|
|
tv_nsec: (wait_nsec % NSEC_PER_SEC).try_into().unwrap(),
|
|
};
|
|
|
|
// We have one fd of interest.
|
|
let mut fdset: libc::fd_set = unsafe { std::mem::zeroed() };
|
|
let in_fd = self.get_in_fd();
|
|
unsafe {
|
|
libc::FD_ZERO(&mut fdset);
|
|
libc::FD_SET(in_fd, &mut fdset);
|
|
};
|
|
let res = unsafe {
|
|
libc::pselect(
|
|
in_fd + 1,
|
|
&mut fdset,
|
|
ptr::null_mut(),
|
|
ptr::null_mut(),
|
|
&timeout,
|
|
&sigs,
|
|
)
|
|
};
|
|
|
|
// Prevent signal starvation on WSL causing the `torn_escapes.py` test to fail
|
|
if is_windows_subsystem_for_linux() {
|
|
// Merely querying the current thread's sigmask is sufficient to deliver a pending signal
|
|
let _ = unsafe { libc::pthread_sigmask(0, ptr::null(), &mut sigs) };
|
|
}
|
|
if res > 0 {
|
|
return Some(self.readch());
|
|
}
|
|
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 corresponding to stdin.
|
|
fn get_in_fd(&self) -> RawFd;
|
|
|
|
/// 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);
|
|
}
|
|
|
|
/// 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);
|
|
}
|
|
|
|
/// 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 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.
|
|
let Some(first): Option<usize> = queue.iter().position(|e| !is_char(e)) else {
|
|
return;
|
|
};
|
|
let last = queue
|
|
.range(first..)
|
|
.position(is_char)
|
|
.map_or(queue.len(), |x| x + first);
|
|
// Move the non-char events to the front, retaining their order.
|
|
let elems: Vec<CharEvent> = queue.drain(first..last).collect();
|
|
for elem in elems.into_iter().rev() {
|
|
queue.push_front(elem);
|
|
}
|
|
}
|
|
|
|
/// Add multiple readline events to the front of the queue of unread characters.
|
|
/// The order of the provided events is not changed, i.e. they are not inserted in reverse
|
|
/// order. That is, the first element in evts will be the first element returned.
|
|
fn insert_front<I>(&mut self, evts: I)
|
|
where
|
|
I: IntoIterator<Item = CharEvent>,
|
|
I::IntoIter: DoubleEndedIterator,
|
|
{
|
|
let queue = self.get_queue_mut();
|
|
let iter = evts.into_iter().rev();
|
|
queue.reserve(iter.size_hint().0);
|
|
for evt in iter {
|
|
queue.push_front(evt);
|
|
}
|
|
}
|
|
|
|
/// Forget all enqueued readline events in the front of the queue.
|
|
fn drop_leading_readline_events(&mut self) {
|
|
let queue = self.get_queue_mut();
|
|
while let Some(evt) = queue.front() {
|
|
if evt.is_readline_or_command() {
|
|
queue.pop_front();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Override point for when we are about to (potentially) block in select(). The default does
|
|
/// nothing.
|
|
fn prepare_to_select(&mut self) {}
|
|
|
|
/// Override point for when when select() is interrupted by a signal. The default does nothing.
|
|
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) {}
|
|
|
|
/// \return if we have any lookahead.
|
|
fn has_lookahead(&self) -> bool {
|
|
!self.get_queue().is_empty()
|
|
}
|
|
}
|
|
|
|
/// A simple, concrete implementation of InputEventQueuer.
|
|
pub struct InputEventQueue {
|
|
queue: VecDeque<CharEvent>,
|
|
in_fd: RawFd,
|
|
}
|
|
|
|
impl InputEventQueue {
|
|
pub fn new(in_fd: RawFd) -> InputEventQueue {
|
|
InputEventQueue {
|
|
queue: VecDeque::new(),
|
|
in_fd,
|
|
}
|
|
}
|
|
}
|
|
|
|
impl InputEventQueuer for InputEventQueue {
|
|
fn get_queue(&self) -> &VecDeque<CharEvent> {
|
|
&self.queue
|
|
}
|
|
|
|
fn get_queue_mut(&mut self) -> &mut VecDeque<CharEvent> {
|
|
&mut self.queue
|
|
}
|
|
|
|
fn get_in_fd(&self) -> RawFd {
|
|
self.in_fd
|
|
}
|
|
}
|