mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-27 05:13:10 +00:00
Port input_common.cpp to input_common.rs
This commit is contained in:
parent
55054a4bae
commit
01b04c802e
1 changed files with 673 additions and 12 deletions
|
@ -1,21 +1,682 @@
|
|||
use crate::common::{is_windows_subsystem_for_linux, read_blocked};
|
||||
use crate::env::{EnvStack, Environment};
|
||||
use crate::fd_readable_set::FdReadableSet;
|
||||
use crate::flog::FLOG;
|
||||
use crate::threads::{iothread_port, iothread_service_main};
|
||||
use crate::universal_notifier::default_notifier;
|
||||
use crate::wchar::prelude::*;
|
||||
use crate::wchar_ffi::WCharToFFI;
|
||||
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};
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum ReadlineCmd {
|
||||
BeginningOfLine,
|
||||
EndOfLine,
|
||||
ForwardChar,
|
||||
BackwardChar,
|
||||
ForwardSingleChar,
|
||||
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,
|
||||
}
|
||||
|
||||
// The range of key codes for inputrc-style keyboard functions.
|
||||
pub const R_END_INPUT_FUNCTIONS: usize = (ReadlineCmd::ReverseRepeatJump as usize) + 1;
|
||||
|
||||
/// Represents an event on the character input stream.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum CharEventType {
|
||||
/// A character was entered.
|
||||
Char(char),
|
||||
|
||||
/// A readline event.
|
||||
Readline(ReadlineCmd),
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
/// 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(Debug, Copy, Clone)]
|
||||
pub enum CharInputStyle {
|
||||
// Insert characters normally.
|
||||
Normal,
|
||||
|
||||
// Insert characters only if the cursor is not at the beginning. Otherwise, discard them.
|
||||
NotFirst,
|
||||
}
|
||||
|
||||
#[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 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 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_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) != 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 is_empty = fish_escape_delay_ms.is_none();
|
||||
let value = fish_escape_delay_ms
|
||||
.map(|s| s.as_string().to_ffi())
|
||||
.unwrap_or(L!("").to_ffi());
|
||||
crate::ffi::update_wait_on_escape_ms_ffi(is_empty, &value);
|
||||
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 fish_sequence_key_delay_ms = vars.get_unless_empty(L!("fish_sequence_key_delay_ms"));
|
||||
let is_empty = fish_sequence_key_delay_ms.is_none();
|
||||
let value = fish_sequence_key_delay_ms
|
||||
.map(|s| s.as_string().to_ffi())
|
||||
.unwrap_or(L!("").to_ffi());
|
||||
crate::ffi::update_wait_on_sequence_key_ms_ffi(is_empty, &value);
|
||||
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();
|
||||
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();
|
||||
}
|
||||
|
||||
ReadbResult::Byte(read_byte) => {
|
||||
if crate::compat::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 sz = unsafe {
|
||||
mbrtowc(
|
||||
std::ptr::addr_of_mut!(res).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.
|
||||
}
|
||||
0 => {
|
||||
// Actual nul char.
|
||||
return CharEvent::from_char('\0');
|
||||
}
|
||||
_ => {
|
||||
// Sequence complete.
|
||||
return CharEvent::from_char(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() {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_push_front_back() {
|
||||
let mut queue = InputEventQueue::new(0);
|
||||
queue.push_front(CharEvent::from_char('a'));
|
||||
queue.push_front(CharEvent::from_char('b'));
|
||||
queue.push_back(CharEvent::from_char('c'));
|
||||
queue.push_back(CharEvent::from_char('d'));
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'b');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'a');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'c');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'd');
|
||||
assert!(queue.try_pop().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_promote_interruptions_to_front() {
|
||||
let mut queue = InputEventQueue::new(0);
|
||||
queue.push_back(CharEvent::from_char('a'));
|
||||
queue.push_back(CharEvent::from_char('b'));
|
||||
queue.push_back(CharEvent::from_readline(ReadlineCmd::Undo));
|
||||
queue.push_back(CharEvent::from_readline(ReadlineCmd::Redo));
|
||||
queue.push_back(CharEvent::from_char('c'));
|
||||
queue.push_back(CharEvent::from_char('d'));
|
||||
queue.promote_interruptions_to_front();
|
||||
|
||||
assert_eq!(queue.try_pop().unwrap().get_readline(), ReadlineCmd::Undo);
|
||||
assert_eq!(queue.try_pop().unwrap().get_readline(), ReadlineCmd::Redo);
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'a');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'b');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'c');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'd');
|
||||
assert!(!queue.has_lookahead());
|
||||
|
||||
queue.push_back(CharEvent::from_char('e'));
|
||||
queue.promote_interruptions_to_front();
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'e');
|
||||
assert!(!queue.has_lookahead());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_insert_front() {
|
||||
let mut queue = InputEventQueue::new(0);
|
||||
queue.push_back(CharEvent::from_char('a'));
|
||||
queue.push_back(CharEvent::from_char('b'));
|
||||
|
||||
let events = vec![
|
||||
CharEvent::from_char('A'),
|
||||
CharEvent::from_char('B'),
|
||||
CharEvent::from_char('C'),
|
||||
];
|
||||
queue.insert_front(events);
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'A');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'B');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'C');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'a');
|
||||
assert_eq!(queue.try_pop().unwrap().get_char(), 'b');
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue