Add remaining input FFI bits and port builtin_bind

This implements input and input_common FFI pieces in input_ffi.rs, and
simultaneously ports bind.rs. This was done as a single commit because
builtin_bind would have required a substantial amount of work to use the input
ffi.
This commit is contained in:
ridiculousfish 2023-12-17 15:41:14 -08:00
parent 7ffb62d1d9
commit 8190e3419d
22 changed files with 1092 additions and 2645 deletions

View File

@ -99,7 +99,6 @@ endif()
# List of sources for builtin functions.
set(FISH_BUILTIN_SRCS
src/builtins/bind.cpp
src/builtins/commandline.cpp
)
# List of other sources.
@ -115,8 +114,6 @@ set(FISH_SRCS
src/fish_version.cpp
src/flog.cpp
src/highlight.cpp
src/input_common.cpp
src/input.cpp
src/output.cpp
src/parse_util.cpp
src/path.cpp

View File

@ -90,6 +90,7 @@ fn main() {
"fish-rust/src/future_feature_flags.rs",
"fish-rust/src/highlight.rs",
"fish-rust/src/history.rs",
"fish-rust/src/input_ffi.rs",
"fish-rust/src/io.rs",
"fish-rust/src/job_group.rs",
"fish-rust/src/kill.rs",

View File

@ -1,6 +1,19 @@
//! Implementation of the bind builtin.
use super::prelude::*;
use crate::common::{
escape, escape_string, str2wcstring, valid_var_name, EscapeFlags, EscapeStringStyle,
};
use crate::highlight::{colorize, highlight_shell};
use crate::input::{
input_function_get_names, input_mappings, input_terminfo_get_name, input_terminfo_get_names,
input_terminfo_get_sequence, InputMappingSet,
};
use crate::nix::isatty;
use nix::errno::Errno;
use std::sync::MutexGuard;
const DEFAULT_BIND_MODE: &wstr = L!("default");
const BIND_INSERT: c_int = 0;
const BIND_ERASE: c_int = 1;
@ -19,10 +32,553 @@ struct Options {
have_preset: bool,
preset: bool,
mode: c_int,
bind_mode: &'static wstr,
sets_bind_mode: &'static wstr,
bind_mode: WString,
sets_bind_mode: WString,
}
impl Options {
fn new() -> Options {
Options {
all: false,
bind_mode_given: false,
list_modes: false,
print_help: false,
silent: false,
use_terminfo: false,
have_user: false,
user: false,
have_preset: false,
preset: false,
mode: BIND_INSERT,
bind_mode: DEFAULT_BIND_MODE.to_owned(),
sets_bind_mode: WString::new(),
}
}
}
struct BuiltinBind {
/// Note that BuiltinBind holds the singleton lock.
/// It must not call out to anything which can execute fish shell code or attempt to acquire the
/// lock again.
input_mappings: MutexGuard<'static, InputMappingSet>,
opts: Options,
}
impl BuiltinBind {
fn new() -> BuiltinBind {
BuiltinBind {
input_mappings: input_mappings(),
opts: Options::new(),
}
}
/// List a single key binding.
/// Returns false if no binding with that sequence and mode exists.
fn list_one(
&self,
seq: &wstr,
bind_mode: &wstr,
user: bool,
parser: &Parser,
streams: &mut IoStreams,
) -> bool {
let mut ecmds = Vec::new();
let mut sets_mode = WString::new();
let mut out = WString::new();
if !self
.input_mappings
.get(seq, bind_mode, &mut ecmds, user, &mut sets_mode)
{
return false;
}
out.push_str("bind");
// Append the mode flags if applicable.
if !user {
out.push_str(" --preset");
}
if bind_mode != DEFAULT_BIND_MODE {
out.push_str(" -M ");
out.push_utfstr(&escape(bind_mode));
}
if !sets_mode.is_empty() && sets_mode != bind_mode {
out.push_str(" -m ");
out.push_utfstr(&escape(&sets_mode));
}
// Append the name.
if let Some(tname) = input_terminfo_get_name(seq) {
// Note that we show -k here because we have an input key name.
out.push_str(" -k ");
out.push_utfstr(&tname);
} else {
// No key name, so no -k; we show the escape sequence directly.
let eseq = escape(seq);
out.push(' ');
out.push_utfstr(&eseq);
}
// Now show the list of commands.
for ecmd in ecmds {
out.push(' ');
out.push_utfstr(&escape(&ecmd));
}
out.push('\n');
if !streams.out_is_redirected && isatty(libc::STDOUT_FILENO) {
let mut colors = Vec::new();
highlight_shell(&out, &mut colors, &parser.context(), false, None);
let colored = colorize(&out, &colors, parser.vars());
streams.out.append(str2wcstring(&colored));
} else {
streams.out.append(out);
}
true
}
// Overload with both kinds of bindings.
// Returns false only if neither exists.
fn list_one_user_andor_preset(
&self,
seq: &wstr,
bind_mode: &wstr,
user: bool,
preset: bool,
parser: &Parser,
streams: &mut IoStreams,
) -> bool {
let mut retval = false;
if preset {
retval |= self.list_one(seq, bind_mode, false, parser, streams);
}
if user {
retval |= self.list_one(seq, bind_mode, true, parser, streams);
}
retval
}
/// List all current key bindings.
fn list(&self, bind_mode: Option<&wstr>, user: bool, parser: &Parser, streams: &mut IoStreams) {
let lst = self.input_mappings.get_names(user);
for binding in lst {
if bind_mode.is_some() && bind_mode.unwrap() != binding.mode {
continue;
}
self.list_one(&binding.seq, &binding.mode, user, parser, streams);
}
}
/// Print terminfo key binding names to string buffer used for standard output.
///
/// \param all if set, all terminfo key binding names will be printed. If not set, only ones that
/// are defined for this terminal are printed.
fn key_names(&self, all: bool, streams: &mut IoStreams) {
let names = input_terminfo_get_names(!all);
for name in names {
streams.out.appendln(name);
}
}
/// Print all the special key binding functions to string buffer used for standard output.
fn function_names(&self, streams: &mut IoStreams) {
let names = input_function_get_names();
for name in names {
streams.out.appendln(name);
}
}
/// Wraps input_terminfo_get_sequence(), appending the correct error messages as needed.
fn get_terminfo_sequence(&self, seq: &wstr, streams: &mut IoStreams) -> Option<WString> {
let mut tseq = WString::new();
if input_terminfo_get_sequence(seq, &mut tseq) {
return Some(tseq);
}
let err = Errno::last();
if !self.opts.silent {
let eseq = escape_string(seq, EscapeStringStyle::Script(EscapeFlags::NO_PRINTABLES));
if err == Errno::ENOENT {
streams.err.append(wgettext_fmt!(
"%ls: No key with name '%ls' found\n",
"bind",
eseq
));
} else if err == Errno::EILSEQ {
streams.err.append(wgettext_fmt!(
"%ls: Key with name '%ls' does not have any mapping\n",
"bind",
eseq
));
} else {
streams.err.append(wgettext_fmt!(
"%ls: Unknown error trying to bind to key named '%ls'\n",
"bind",
eseq
));
}
}
None
}
/// Add specified key binding.
fn add(
&mut self,
seq: &wstr,
cmds: &[&wstr],
mode: WString,
sets_mode: WString,
terminfo: bool,
user: bool,
streams: &mut IoStreams,
) -> bool {
let cmds = cmds.iter().map(|&s| s.to_owned()).collect();
if terminfo {
if let Some(seq2) = self.get_terminfo_sequence(seq, streams) {
self.input_mappings.add(seq2, cmds, mode, sets_mode, user);
} else {
return true;
}
} else {
self.input_mappings
.add(seq.to_owned(), cmds, mode, sets_mode, user)
}
false
}
/// Erase specified key bindings
///
/// @param seq
/// an array of all key bindings to erase
/// @param all
/// if specified, _all_ key bindings will be erased
/// @param mode
/// if specified, only bindings from that mode will be erased. If not given
/// and @c all is @c false, @c DEFAULT_BIND_MODE will be used.
/// @param use_terminfo
/// Whether to look use terminfo -k name
///
fn erase(
&mut self,
seq: &[&wstr],
all: bool,
mode: Option<&wstr>,
use_terminfo: bool,
user: bool,
streams: &mut IoStreams,
) -> bool {
if all {
self.input_mappings.clear(mode, user);
return false;
}
let mut res = false;
let mode = mode.unwrap_or(DEFAULT_BIND_MODE);
for s in seq {
if use_terminfo {
if let Some(seq2) = self.get_terminfo_sequence(s, streams) {
self.input_mappings.erase(&seq2, mode, user);
} else {
res = true;
}
} else {
self.input_mappings.erase(s, mode, user);
}
}
res
}
fn insert(
&mut self,
optind: usize,
argv: &[&wstr],
parser: &Parser,
streams: &mut IoStreams,
) -> bool {
let argc = argv.len();
let cmd = argv[0];
let arg_count = argc - optind;
if arg_count < 2 {
// If we get both or neither preset/user, we list both.
if !self.opts.have_preset && !self.opts.have_user {
self.opts.preset = true;
self.opts.user = true;
}
} else {
// Inserting both on the other hand makes no sense.
if self.opts.have_preset && self.opts.have_user {
streams.err.append(wgettext_fmt!(
BUILTIN_ERR_COMBO2_EXCLUSIVE,
cmd,
"--preset",
"--user"
));
return true;
}
}
if arg_count == 0 {
// We don't overload this with user and def because we want them to be grouped.
// First the presets, then the users (because of scrolling).
let bind_mode = if self.opts.bind_mode_given {
Some(self.opts.bind_mode.as_utfstr())
} else {
None
};
if self.opts.preset {
self.list(bind_mode, false, parser, streams);
}
if self.opts.user {
self.list(bind_mode, true, parser, streams);
}
} else if arg_count == 1 {
let seq = if self.opts.use_terminfo {
let Some(seq2) = self.get_terminfo_sequence(argv[optind], streams) else {
// get_terminfo_sequence already printed the error.
return true;
};
seq2
} else {
argv[optind].to_owned()
};
if !self.list_one_user_andor_preset(
&seq,
&self.opts.bind_mode,
self.opts.user,
self.opts.preset,
parser,
streams,
) {
let eseq = escape_string(
argv[optind],
EscapeStringStyle::Script(EscapeFlags::NO_PRINTABLES),
);
if !self.opts.silent {
if self.opts.use_terminfo {
streams.err.append(wgettext_fmt!(
"%ls: No binding found for key '%ls'\n",
cmd,
eseq
));
} else {
streams.err.append(wgettext_fmt!(
"%ls: No binding found for sequence '%ls'\n",
cmd,
eseq
));
}
}
return true;
}
} else {
// Actually insert!
if self.add(
argv[optind],
&argv[optind + 1..],
self.opts.bind_mode.to_owned(),
self.opts.sets_bind_mode.to_owned(),
self.opts.use_terminfo,
self.opts.user,
streams,
) {
return true;
}
}
false
}
/// List all current bind modes.
fn list_modes(&mut self, streams: &mut IoStreams) {
// List all known modes, even if they are only in preset bindings.
let lst = self.input_mappings.get_names(true);
let preset_lst = self.input_mappings.get_names(false);
// Extract the bind modes, uniqueize, and sort.
let mut modes: Vec<WString> = lst.into_iter().chain(preset_lst).map(|m| m.mode).collect();
modes.sort_unstable();
modes.dedup();
for mode in modes {
streams.out.appendln(mode);
}
}
}
fn parse_cmd_opts(
opts: &mut Options,
optind: &mut usize,
argv: &mut [&wstr],
parser: &Parser,
streams: &mut IoStreams,
) -> Option<i32> {
let cmd = argv[0];
let short_options = L!(":aehkKfM:Lm:s");
const long_options: &[woption] = &[
wopt(L!("all"), no_argument, 'a'),
wopt(L!("erase"), no_argument, 'e'),
wopt(L!("function-names"), no_argument, 'f'),
wopt(L!("help"), no_argument, 'h'),
wopt(L!("key"), no_argument, 'k'),
wopt(L!("key-names"), no_argument, 'K'),
wopt(L!("list-modes"), no_argument, 'L'),
wopt(L!("mode"), required_argument, 'M'),
wopt(L!("preset"), no_argument, 'p'),
wopt(L!("sets-mode"), required_argument, 'm'),
wopt(L!("silent"), no_argument, 's'),
wopt(L!("user"), no_argument, 'u'),
];
let mut w = wgetopter_t::new(short_options, long_options, argv);
while let Some(c) = w.wgetopt_long() {
match c {
'a' => opts.all = true,
'e' => opts.mode = BIND_ERASE,
'f' => opts.mode = BIND_FUNCTION_NAMES,
'h' => opts.print_help = true,
'k' => opts.use_terminfo = true,
'K' => opts.mode = BIND_KEY_NAMES,
'L' => {
opts.list_modes = true;
return STATUS_CMD_OK;
}
'M' => {
if !valid_var_name(w.woptarg.unwrap()) {
streams.err.append(wgettext_fmt!(
BUILTIN_ERR_BIND_MODE,
cmd,
w.woptarg.unwrap()
));
return STATUS_INVALID_ARGS;
}
opts.bind_mode = w.woptarg.unwrap().to_owned();
opts.bind_mode_given = true;
}
'm' => {
if !valid_var_name(w.woptarg.unwrap()) {
streams.err.append(wgettext_fmt!(
BUILTIN_ERR_BIND_MODE,
cmd,
w.woptarg.unwrap()
));
return STATUS_INVALID_ARGS;
}
opts.sets_bind_mode = w.woptarg.unwrap().to_owned();
}
'p' => {
opts.have_preset = true;
opts.preset = true;
}
's' => opts.silent = true,
'u' => {
opts.have_user = true;
opts.user = true;
}
':' => {
builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1], true);
return STATUS_INVALID_ARGS;
}
'?' => {
builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1], true);
return STATUS_INVALID_ARGS;
}
_ => {
panic!("unexpected retval from wgetopt_long")
}
}
}
*optind = w.woptind;
return STATUS_CMD_OK;
}
impl BuiltinBind {
/// The bind builtin, used for setting character sequences.
pub fn bind(
&mut self,
parser: &Parser,
streams: &mut IoStreams,
argv: &mut [&wstr],
) -> Option<c_int> {
let cmd = argv[0];
let mut optind = 0;
let retval = parse_cmd_opts(&mut self.opts, &mut optind, argv, parser, streams);
if retval != STATUS_CMD_OK {
return retval;
}
if self.opts.list_modes {
self.list_modes(streams);
return STATUS_CMD_OK;
}
if self.opts.print_help {
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}
// Default to user mode
if !self.opts.have_preset && !self.opts.have_user {
self.opts.user = true;
}
match self.opts.mode {
BIND_ERASE => {
// TODO: satisfy the borrow checker here.
let storage;
let bind_mode = if self.opts.bind_mode_given {
storage = self.opts.bind_mode.clone();
Some(storage.as_utfstr())
} else {
None
};
// If we get both, we erase both.
if self.opts.user {
if self.erase(
&argv[optind..],
self.opts.all,
bind_mode,
self.opts.use_terminfo,
true, /* user */
streams,
) {
return STATUS_CMD_ERROR;
}
}
if self.opts.preset {
if self.erase(
&argv[optind..],
self.opts.all,
bind_mode,
self.opts.use_terminfo,
false, /* user */
streams,
) {
return STATUS_CMD_ERROR;
}
}
}
BIND_INSERT => {
if self.insert(optind, argv, parser, streams) {
return STATUS_CMD_ERROR;
}
}
BIND_KEY_NAMES => self.key_names(self.opts.all, streams),
BIND_FUNCTION_NAMES => self.function_names(streams),
_ => {
streams
.err
.append(wgettext_fmt!("%ls: Invalid state\n", cmd));
return STATUS_CMD_ERROR;
}
}
STATUS_CMD_OK
}
}
pub fn bind(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Option<c_int> {
run_builtin_ffi(crate::ffi::builtin_bind, parser, streams, args)
BuiltinBind::new().bind(parser, streams, args)
}

View File

@ -13,6 +13,7 @@ use crate::event::Event;
use crate::ffi;
use crate::flog::FLOG;
use crate::global_safety::RelaxedAtomicBool;
use crate::input::init_input;
use crate::nix::{geteuid, getpid, isatty};
use crate::null_terminated_array::OwningNullTerminatedArray;
use crate::path::{
@ -722,7 +723,7 @@ pub fn env_init(paths: Option<&ConfigPaths>, do_uvars: bool, default_paths: bool
// Allow changes to variables to produce events.
env_dispatch_init(vars);
ffi::init_input();
init_input();
// Complain about invalid config paths.
// HACK: Assume the defaults are correct (in practice this is only --no-config anyway).

View File

@ -25,8 +25,6 @@ include_cpp! {
#include "flog.h"
#include "function.h"
#include "io.h"
#include "input_common.h"
#include "input.h"
#include "parse_constants.h"
#include "parser.h"
#include "parse_util.h"
@ -38,7 +36,6 @@ include_cpp! {
#include "tokenizer.h"
#include "wutil.h"
#include "builtins/bind.h"
#include "builtins/commandline.h"
safety!(unsafe_ffi)
@ -54,18 +51,14 @@ include_cpp! {
generate!("reader_read_ffi")
generate!("fish_is_unwinding_for_exit")
generate!("restore_term_mode")
generate!("update_wait_on_escape_ms_ffi")
generate!("read_generation_count")
generate!("set_flog_output_file_ffi")
generate!("flog_setlinebuf_ffi")
generate!("activate_flog_categories_by_pattern")
generate!("restore_term_foreground_process_group_for_exit")
generate!("builtin_bind")
generate!("builtin_commandline")
generate!("init_input")
generate!("shell_modes_ffi")
generate!("log_extra_to_flog_file")
@ -96,7 +89,6 @@ include_cpp! {
generate!("reader_change_history")
generate!("reader_change_cursor_selection_mode")
generate!("reader_set_autosuggestion_enabled_ffi")
generate!("update_wait_on_sequence_key_ms_ffi")
}
/// Allow wcharz_t to be "into" wstr.

View File

@ -12,96 +12,11 @@ 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;
pub const R_END_INPUT_FUNCTIONS: usize = (ReadlineCmd::ReverseRepeatJump.repr as usize) + 1;
// TODO: move CharInputStyle and ReadlineCmd here once they no longer must be exposed to C++.
pub use crate::input_ffi::{CharInputStyle, ReadlineCmd};
/// Represents an event on the character input stream.
#[derive(Debug, Copy, Clone)]
@ -120,17 +35,6 @@ pub enum CharEventType {
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,

267
fish-rust/src/input_ffi.rs Normal file
View File

@ -0,0 +1,267 @@
use crate::ffi::wcstring_list_ffi_t;
use crate::input::*;
use crate::input_common::*;
use crate::parser::ParserRefFFI;
use crate::threads::CppCallback;
use crate::wchar::prelude::*;
use crate::wchar_ffi::AsWstr;
use crate::wchar_ffi::WCharToFFI;
use cxx::CxxWString;
pub use ffi::{CharInputStyle, ReadlineCmd};
use std::pin::Pin;
// Returns the code, or -1 on failure.
fn input_function_get_code_ffi(name: &CxxWString) -> i32 {
if let Some(code) = input_function_get_code(name.as_wstr()) {
code.repr as i32
} else {
-1
}
}
fn char_event_from_readline_ffi(cmd: ReadlineCmd) -> Box<CharEvent> {
Box::new(CharEvent::from_readline(cmd))
}
fn char_event_from_char_ffi(c: u8) -> Box<CharEvent> {
Box::new(CharEvent::from_char(c.into()))
}
fn make_inputter_ffi(parser: &ParserRefFFI, in_fd: i32) -> Box<Inputter> {
Box::new(Inputter::new(parser.0.clone(), in_fd))
}
fn make_input_event_queue_ffi(in_fd: i32) -> Box<InputEventQueue> {
Box::new(InputEventQueue::new(in_fd))
}
fn input_terminfo_get_name_ffi(seq: &CxxWString, out: Pin<&mut CxxWString>) -> bool {
let Some(name) = input_terminfo_get_name(seq.as_wstr()) else {
return false;
};
out.push_chars(name.as_char_slice());
true
}
impl Inputter {
#[allow(clippy::boxed_local)]
fn queue_char_ffi(&mut self, ch: Box<CharEvent>) {
self.queue_char(*ch);
}
fn read_char_ffi(&mut self, command_handler: &cxx::SharedPtr<CppCallback>) -> Box<CharEvent> {
let mut rust_handler = |cmds: &[WString]| {
let ffi_cmds = cmds.to_ffi();
command_handler.invoke_with_param(ffi_cmds.as_ref().unwrap() as *const _ as *const u8);
};
let mhandler = if !command_handler.is_null() {
Some(&mut rust_handler as &mut CommandHandler)
} else {
None
};
Box::new(self.read_char(mhandler))
}
fn function_pop_arg_ffi(&mut self) -> u32 {
self.function_pop_arg().into()
}
}
impl CharEvent {
fn get_char_ffi(&self) -> u32 {
self.get_char().into()
}
fn get_input_style_ffi(&self) -> CharInputStyle {
self.input_style
}
}
impl InputEventQueue {
// Returns Box<CharEvent>::into_raw(), or nullptr if None.
fn readch_timed_esc_ffi(&mut self) -> *mut CharEvent {
match self.readch_timed_esc() {
Some(ch) => Box::into_raw(Box::new(ch)),
None => std::ptr::null_mut(),
}
}
}
#[cxx::bridge]
mod ffi {
/// 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().
#[cxx_name = "char_input_style_t"]
#[repr(u8)]
#[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,
}
#[cxx_name = "readline_cmd_t"]
#[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,
}
extern "C++" {
include!("parser.h");
include!("reader.h");
include!("callback.h");
type wcstring_list_ffi_t = super::wcstring_list_ffi_t;
type ParserRef = crate::parser::ParserRefFFI;
#[rust_name = "CppCallback"]
type callback_t = crate::threads::CppCallback;
}
extern "Rust" {
fn init_input();
#[cxx_name = "input_function_get_code"]
fn input_function_get_code_ffi(name: &CxxWString) -> i32;
#[cxx_name = "input_terminfo_get_name"]
fn input_terminfo_get_name_ffi(seq: &CxxWString, out: Pin<&mut CxxWString>) -> bool;
}
extern "Rust" {
type CharEvent;
#[cxx_name = "char_event_from_readline"]
fn char_event_from_readline_ffi(cmd: ReadlineCmd) -> Box<CharEvent>;
#[cxx_name = "char_event_from_char"]
fn char_event_from_char_ffi(c: u8) -> Box<CharEvent>;
fn is_char(&self) -> bool;
fn is_readline(&self) -> bool;
fn is_check_exit(&self) -> bool;
fn is_eof(&self) -> bool;
#[cxx_name = "get_char"]
fn get_char_ffi(&self) -> u32;
#[cxx_name = "get_input_style"]
fn get_input_style_ffi(&self) -> CharInputStyle;
fn get_readline(&self) -> ReadlineCmd;
}
extern "Rust" {
type Inputter;
#[cxx_name = "make_inputter"]
fn make_inputter_ffi(parser: &ParserRef, in_fd: i32) -> Box<Inputter>;
#[cxx_name = "queue_char"]
fn queue_char_ffi(&mut self, ch: Box<CharEvent>);
fn queue_readline(&mut self, cmd: ReadlineCmd);
#[cxx_name = "read_char"]
fn read_char_ffi(&mut self, command_handler: &SharedPtr<CppCallback>) -> Box<CharEvent>;
fn function_set_status(&mut self, status: bool);
#[cxx_name = "function_pop_arg"]
fn function_pop_arg_ffi(&mut self) -> u32;
}
extern "Rust" {
type InputEventQueue;
#[cxx_name = "make_input_event_queue"]
fn make_input_event_queue_ffi(in_fd: i32) -> Box<InputEventQueue>;
#[cxx_name = "readch_timed_esc"]
fn readch_timed_esc_ffi(&mut self) -> *mut CharEvent;
}
}

View File

@ -71,6 +71,7 @@ mod highlight;
mod history;
mod input;
mod input_common;
mod input_ffi;
mod io;
mod job_group;
mod kill;

View File

@ -102,6 +102,7 @@ mod ffi {
}
}
pub use ffi::CppCallback;
unsafe impl Send for ffi::CppCallback {}
unsafe impl Sync for ffi::CppCallback {}

View File

@ -1,521 +0,0 @@
// Implementation of the bind builtin.
#include "config.h" // IWYU pragma: keep
#include "bind.h"
#include <unistd.h>
#include <cerrno>
#include <set>
#include <string>
#include <vector>
#include "../builtin.h"
#include "../common.h"
#include "../env.h"
#include "../fallback.h" // IWYU pragma: keep
#include "../highlight.h"
#include "../input.h"
#include "../io.h"
#include "../maybe.h"
#include "../parser.h"
#include "../wgetopt.h"
#include "../wutil.h" // IWYU pragma: keep
#include "builtins/shared.rs.h"
enum { BIND_INSERT, BIND_ERASE, BIND_KEY_NAMES, BIND_FUNCTION_NAMES };
struct bind_cmd_opts_t {
bool all = false;
bool bind_mode_given = false;
bool list_modes = false;
bool print_help = false;
bool silent = false;
bool use_terminfo = false;
bool have_user = false;
bool user = false;
bool have_preset = false;
bool preset = false;
int mode = BIND_INSERT;
const wchar_t *bind_mode = DEFAULT_BIND_MODE;
const wchar_t *sets_bind_mode = L"";
};
namespace {
class builtin_bind_t {
public:
maybe_t<int> builtin_bind(const parser_t &parser, io_streams_t &streams, const wchar_t **argv);
builtin_bind_t() : input_mappings_(input_mappings()) {}
private:
bind_cmd_opts_t *opts;
/// Note that builtin_bind_t holds the singleton lock.
/// It must not call out to anything which can execute fish shell code or attempt to acquire the
/// lock again.
acquired_lock<input_mapping_set_t> input_mappings_;
void list(const wchar_t *bind_mode, bool user, const parser_t &parser, io_streams_t &streams);
void key_names(bool all, io_streams_t &streams);
void function_names(io_streams_t &streams);
bool add(const wcstring &seq, const wchar_t *const *cmds, size_t cmds_len, const wchar_t *mode,
const wchar_t *sets_mode, bool terminfo, bool user, io_streams_t &streams);
bool erase(const wchar_t *const *seq, bool all, const wchar_t *mode, bool use_terminfo,
bool user, io_streams_t &streams);
bool get_terminfo_sequence(const wcstring &seq, wcstring *out_seq, io_streams_t &streams) const;
bool insert(int optind, int argc, const wchar_t **argv, const parser_t &parser,
io_streams_t &streams);
void list_modes(io_streams_t &streams);
bool list_one(const wcstring &seq, const wcstring &bind_mode, bool user, const parser_t &parser,
io_streams_t &streams);
bool list_one(const wcstring &seq, const wcstring &bind_mode, bool user, bool preset,
const parser_t &parser, io_streams_t &streams);
};
/// List a single key binding.
/// Returns false if no binding with that sequence and mode exists.
bool builtin_bind_t::list_one(const wcstring &seq, const wcstring &bind_mode, bool user,
const parser_t &parser, io_streams_t &streams) {
std::vector<wcstring> ecmds;
wcstring sets_mode, out;
if (!input_mappings_->get(seq, bind_mode, &ecmds, user, &sets_mode)) {
return false;
}
out.append(L"bind");
// Append the mode flags if applicable.
if (!user) {
out.append(L" --preset");
}
if (bind_mode != DEFAULT_BIND_MODE) {
out.append(L" -M ");
out.append(escape_string(bind_mode));
}
if (!sets_mode.empty() && sets_mode != bind_mode) {
out.append(L" -m ");
out.append(escape_string(sets_mode));
}
// Append the name.
wcstring tname;
if (input_terminfo_get_name(seq, &tname)) {
// Note that we show -k here because we have an input key name.
out.append(L" -k ");
out.append(tname);
} else {
// No key name, so no -k; we show the escape sequence directly.
const wcstring eseq = escape_string(seq);
out.append(L" ");
out.append(eseq);
}
// Now show the list of commands.
for (const auto &ecmd : ecmds) {
out.push_back(' ');
out.append(escape_string(ecmd));
}
out.push_back(L'\n');
if (!streams.out_is_redirected() && isatty(STDOUT_FILENO)) {
auto ffi_colors = highlight_shell_ffi(out, *parser_context(parser), false, {});
auto ffi_colored = colorize(out, *ffi_colors, parser.vars());
std::string colored{ffi_colored.begin(), ffi_colored.end()};
streams.out()->append(str2wcstring(std::move(colored)));
} else {
streams.out()->append(out);
}
return true;
}
// Overload with both kinds of bindings.
// Returns false only if neither exists.
bool builtin_bind_t::list_one(const wcstring &seq, const wcstring &bind_mode, bool user,
bool preset, const parser_t &parser, io_streams_t &streams) {
bool retval = false;
if (preset) {
retval |= list_one(seq, bind_mode, false, parser, streams);
}
if (user) {
retval |= list_one(seq, bind_mode, true, parser, streams);
}
return retval;
}
/// List all current key bindings.
void builtin_bind_t::list(const wchar_t *bind_mode, bool user, const parser_t &parser,
io_streams_t &streams) {
const std::vector<input_mapping_name_t> lst = input_mappings_->get_names(user);
for (const input_mapping_name_t &binding : lst) {
if (bind_mode && bind_mode != binding.mode) {
continue;
}
list_one(binding.seq, binding.mode, user, parser, streams);
}
}
/// Print terminfo key binding names to string buffer used for standard output.
///
/// \param all if set, all terminfo key binding names will be printed. If not set, only ones that
/// are defined for this terminal are printed.
void builtin_bind_t::key_names(bool all, io_streams_t &streams) {
const std::vector<wcstring> names = input_terminfo_get_names(!all);
for (const wcstring &name : names) {
streams.out()->append(name);
streams.out()->push(L'\n');
}
}
/// Print all the special key binding functions to string buffer used for standard output.
void builtin_bind_t::function_names(io_streams_t &streams) {
std::vector<wcstring> names = input_function_get_names();
for (const auto &name : names) {
auto seq = name.c_str();
streams.out()->append(format_string(L"%ls\n", seq));
}
}
/// Wraps input_terminfo_get_sequence(), appending the correct error messages as needed.
bool builtin_bind_t::get_terminfo_sequence(const wcstring &seq, wcstring *out_seq,
io_streams_t &streams) const {
if (input_terminfo_get_sequence(seq, out_seq)) {
return true;
}
wcstring eseq = escape_string(seq, ESCAPE_NO_PRINTABLES);
if (!opts->silent) {
if (errno == ENOENT) {
streams.err()->append(
format_string(_(L"%ls: No key with name '%ls' found\n"), L"bind", eseq.c_str()));
} else if (errno == EILSEQ) {
streams.err()->append(format_string(
_(L"%ls: Key with name '%ls' does not have any mapping\n"), L"bind", eseq.c_str()));
} else {
streams.err()->append(
format_string(_(L"%ls: Unknown error trying to bind to key named '%ls'\n"), L"bind",
eseq.c_str()));
}
}
return false;
}
/// Add specified key binding.
bool builtin_bind_t::add(const wcstring &seq, const wchar_t *const *cmds, size_t cmds_len,
const wchar_t *mode, const wchar_t *sets_mode, bool terminfo, bool user,
io_streams_t &streams) {
if (terminfo) {
wcstring seq2;
if (get_terminfo_sequence(seq, &seq2, streams)) {
input_mappings_->add(seq2, cmds, cmds_len, mode, sets_mode, user);
} else {
return true;
}
} else {
input_mappings_->add(seq, cmds, cmds_len, mode, sets_mode, user);
}
return false;
}
/// Erase specified key bindings
///
/// @param seq
/// an array of all key bindings to erase
/// @param all
/// if specified, _all_ key bindings will be erased
/// @param mode
/// if specified, only bindings from that mode will be erased. If not given
/// and @c all is @c false, @c DEFAULT_BIND_MODE will be used.
/// @param use_terminfo
/// Whether to look use terminfo -k name
///
bool builtin_bind_t::erase(const wchar_t *const *seq, bool all, const wchar_t *mode,
bool use_terminfo, bool user, io_streams_t &streams) {
if (all) {
input_mappings_->clear(mode, user);
return false;
}
bool res = false;
if (mode == nullptr) mode = DEFAULT_BIND_MODE; //!OCLINT(parameter reassignment)
while (*seq) {
if (use_terminfo) {
wcstring seq2;
if (get_terminfo_sequence(*seq++, &seq2, streams)) {
input_mappings_->erase(seq2, mode, user);
} else {
res = true;
}
} else {
input_mappings_->erase(*seq++, mode, user);
}
}
return res;
}
bool builtin_bind_t::insert(int optind, int argc, const wchar_t **argv, const parser_t &parser,
io_streams_t &streams) {
const wchar_t *cmd = argv[0];
int arg_count = argc - optind;
if (arg_count < 2) {
// If we get both or neither preset/user, we list both.
if (!opts->have_preset && !opts->have_user) {
opts->preset = true;
opts->user = true;
}
} else {
// Inserting both on the other hand makes no sense.
if (opts->have_preset && opts->have_user) {
streams.err()->append(
format_string(BUILTIN_ERR_COMBO2_EXCLUSIVE, cmd, L"--preset", "--user"));
return true;
}
}
if (arg_count == 0) {
// We don't overload this with user and def because we want them to be grouped.
// First the presets, then the users (because of scrolling).
if (opts->preset) {
list(opts->bind_mode_given ? opts->bind_mode : nullptr, false, parser, streams);
}
if (opts->user) {
list(opts->bind_mode_given ? opts->bind_mode : nullptr, true, parser, streams);
}
} else if (arg_count == 1) {
wcstring seq;
if (opts->use_terminfo) {
if (!get_terminfo_sequence(argv[optind], &seq, streams)) {
// get_terminfo_sequence already printed the error.
return true;
}
} else {
seq = argv[optind];
}
if (!list_one(seq, opts->bind_mode, opts->user, opts->preset, parser, streams)) {
wcstring eseq = escape_string(argv[optind], ESCAPE_NO_PRINTABLES);
if (!opts->silent) {
if (opts->use_terminfo) {
streams.err()->append(format_string(_(L"%ls: No binding found for key '%ls'\n"),
cmd, eseq.c_str()));
} else {
streams.err()->append(format_string(
_(L"%ls: No binding found for sequence '%ls'\n"), cmd, eseq.c_str()));
}
}
return true;
}
} else {
// Actually insert!
if (add(argv[optind], argv + (optind + 1), argc - (optind + 1), opts->bind_mode,
opts->sets_bind_mode, opts->use_terminfo, opts->user, streams)) {
return true;
}
}
return false;
}
/// List all current bind modes.
void builtin_bind_t::list_modes(io_streams_t &streams) {
// List all known modes, even if they are only in preset bindings.
const std::vector<input_mapping_name_t> lst = input_mappings_->get_names(true);
const std::vector<input_mapping_name_t> preset_lst = input_mappings_->get_names(false);
// A set accomplishes two things for us here:
// - It removes duplicates (no twenty "default" entries).
// - It sorts it, which makes it nicer on the user.
std::set<wcstring> modes;
for (const input_mapping_name_t &binding : lst) {
modes.insert(binding.mode);
}
for (const input_mapping_name_t &binding : preset_lst) {
modes.insert(binding.mode);
}
for (const auto &mode : modes) {
streams.out()->append(format_string(L"%ls\n", mode.c_str()));
}
}
static int parse_cmd_opts(bind_cmd_opts_t &opts, int *optind, //!OCLINT(high ncss method)
int argc, const wchar_t **argv, const parser_t &parser,
io_streams_t &streams) {
const wchar_t *cmd = argv[0];
static const wchar_t *const short_options = L":aehkKfM:Lm:s";
static const struct woption long_options[] = {{L"all", no_argument, 'a'},
{L"erase", no_argument, 'e'},
{L"function-names", no_argument, 'f'},
{L"help", no_argument, 'h'},
{L"key", no_argument, 'k'},
{L"key-names", no_argument, 'K'},
{L"list-modes", no_argument, 'L'},
{L"mode", required_argument, 'M'},
{L"preset", no_argument, 'p'},
{L"sets-mode", required_argument, 'm'},
{L"silent", no_argument, 's'},
{L"user", no_argument, 'u'},
{}};
int opt;
wgetopter_t w;
while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, nullptr)) != -1) {
switch (opt) {
case L'a': {
opts.all = true;
break;
}
case L'e': {
opts.mode = BIND_ERASE;
break;
}
case L'f': {
opts.mode = BIND_FUNCTION_NAMES;
break;
}
case L'h': {
opts.print_help = true;
break;
}
case L'k': {
opts.use_terminfo = true;
break;
}
case L'K': {
opts.mode = BIND_KEY_NAMES;
break;
}
case L'L': {
opts.list_modes = true;
return STATUS_CMD_OK;
}
case L'M': {
if (!valid_var_name(w.woptarg)) {
streams.err()->append(format_string(BUILTIN_ERR_BIND_MODE, cmd, w.woptarg));
return STATUS_INVALID_ARGS;
}
opts.bind_mode = w.woptarg;
opts.bind_mode_given = true;
break;
}
case L'm': {
if (!valid_var_name(w.woptarg)) {
streams.err()->append(format_string(BUILTIN_ERR_BIND_MODE, cmd, w.woptarg));
return STATUS_INVALID_ARGS;
}
opts.sets_bind_mode = w.woptarg;
break;
}
case L'p': {
opts.have_preset = true;
opts.preset = true;
break;
}
case L's': {
opts.silent = true;
break;
}
case L'u': {
opts.have_user = true;
opts.user = true;
break;
}
case ':': {
builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1], true);
return STATUS_INVALID_ARGS;
}
case L'?': {
builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1], true);
return STATUS_INVALID_ARGS;
}
default: {
DIE("unexpected retval from wgetopt_long");
}
}
}
*optind = w.woptind;
return STATUS_CMD_OK;
}
} // namespace
/// The bind builtin, used for setting character sequences.
maybe_t<int> builtin_bind_t::builtin_bind(const parser_t &parser, io_streams_t &streams,
const wchar_t **argv) {
const wchar_t *cmd = argv[0];
int argc = builtin_count_args(argv);
bind_cmd_opts_t opts;
this->opts = &opts;
int optind;
int retval = parse_cmd_opts(opts, &optind, argc, argv, parser, streams);
if (retval != STATUS_CMD_OK) return retval;
if (opts.list_modes) {
list_modes(streams);
return STATUS_CMD_OK;
}
if (opts.print_help) {
builtin_print_help(parser, streams, cmd);
return STATUS_CMD_OK;
}
// Default to user mode
if (!opts.have_preset && !opts.have_user) opts.user = true;
switch (opts.mode) {
case BIND_ERASE: {
const wchar_t *bind_mode = opts.bind_mode_given ? opts.bind_mode : nullptr;
// If we get both, we erase both.
if (opts.user) {
if (erase(&argv[optind], opts.all, bind_mode, opts.use_terminfo, /* user */ true,
streams)) {
return STATUS_CMD_ERROR;
}
}
if (opts.preset) {
if (erase(&argv[optind], opts.all, bind_mode, opts.use_terminfo, /* user */ false,
streams)) {
return STATUS_CMD_ERROR;
}
}
break;
}
case BIND_INSERT: {
if (insert(optind, argc, argv, parser, streams)) {
return STATUS_CMD_ERROR;
}
break;
}
case BIND_KEY_NAMES: {
key_names(opts.all, streams);
break;
}
case BIND_FUNCTION_NAMES: {
function_names(streams);
break;
}
default: {
streams.err()->append(format_string(_(L"%ls: Invalid state\n"), cmd));
return STATUS_CMD_ERROR;
}
}
return STATUS_CMD_OK;
}
int builtin_bind(const void *_parser, void *_streams, void *_argv) {
const auto &parser = *static_cast<const parser_t *>(_parser);
auto &streams = *static_cast<io_streams_t *>(_streams);
auto argv = static_cast<const wchar_t **>(_argv);
builtin_bind_t bind;
return *bind.builtin_bind(parser, streams, argv);
}

View File

@ -1,14 +0,0 @@
// Prototypes for executing builtin_bind function.
#ifndef FISH_BUILTIN_BIND_H
#define FISH_BUILTIN_BIND_H
#include "../maybe.h"
struct Parser;
struct IoStreams;
using parser_t = Parser;
using io_streams_t = IoStreams;
int builtin_bind(const void *parser, void *streams, void *argv);
#endif

View File

@ -11,8 +11,6 @@
#include "../builtin.h"
#include "../common.h"
#include "../fallback.h" // IWYU pragma: keep
#include "../input.h"
#include "../input_common.h"
#include "../io.h"
#include "../maybe.h"
#include "../parse_constants.h"
@ -24,6 +22,7 @@
#include "../wgetopt.h"
#include "../wutil.h" // IWYU pragma: keep
#include "builtins/shared.rs.h"
#include "input_ffi.rs.h"
/// Which part of the comandbuffer are we operating on.
enum {
@ -305,10 +304,12 @@ int builtin_commandline(const void *_parser, void *_streams, void *_argv) {
using rl = readline_cmd_t;
for (i = w.woptind; i < argc; i++) {
if (auto mc = input_function_get_code(argv[i])) {
int mci = input_function_get_code(argv[i]);
if (mci >= 0) {
readline_cmd_t mc = static_cast<readline_cmd_t>(mci);
// Don't enqueue a repaint if we're currently in the middle of one,
// because that's an infinite loop.
if (mc == rl::repaint_mode || mc == rl::force_repaint || mc == rl::repaint) {
if (mc == rl::RepaintMode || mc == rl::ForceRepaint || mc == rl::Repaint) {
if (ld.is_repaint()) continue;
}
@ -317,11 +318,11 @@ int builtin_commandline(const void *_parser, void *_streams, void *_argv) {
// insert/replace operations into readline functions with associated data, so that
// all queued `commandline` operations - including buffer modifications - are
// executed in order
if (mc == rl::begin_undo_group || mc == rl::end_undo_group) {
reader_handle_command(*mc);
if (mc == rl::BeginUndoGroup || mc == rl::EndUndoGroup) {
reader_handle_command(mc);
} else {
// Inserts the readline function at the back of the queue.
reader_queue_ch(*mc);
reader_queue_ch(char_event_from_readline(mc));
}
} else {
streams.err()->append(

View File

@ -1,10 +1,8 @@
#include "builtin.h"
#include "builtins/bind.h"
#include "builtins/commandline.h"
#include "event.h"
#include "fds.h"
#include "highlight.h"
#include "input.h"
#include "parse_util.h"
#include "reader.h"
#include "screen.h"
@ -20,7 +18,6 @@ void mark_as_used(const parser_t& parser, env_stack_t& env_stack) {
expand_tilde(s, env_stack);
get_history_variable_text_ffi({});
highlight_spec_t{};
init_input();
reader_change_cursor_selection_mode(cursor_selection_mode_t::exclusive);
reader_change_history({});
reader_read_ffi({}, {}, {});
@ -34,6 +31,5 @@ void mark_as_used(const parser_t& parser, env_stack_t& env_stack) {
term_copy_modes();
unsetenv_lock({});
builtin_bind({}, {}, {});
builtin_commandline({}, {}, {});
}

View File

@ -27,8 +27,7 @@
#include "ffi_baggage.h"
#include "ffi_init.rs.h"
#include "fish_version.h"
#include "input.h"
#include "input_common.h"
#include "input_ffi.rs.h"
#include "maybe.h"
#include "parser.h"
#include "print_help.rs.h"
@ -85,7 +84,7 @@ static maybe_t<wcstring> sequence_name(wchar_t wc) {
for (size_t i = 0; i < recent_chars.size(); i++) {
wcstring out_name;
wcstring seq = str2wcstring(recent_chars.substr(i));
if (input_terminfo_get_name(seq, &out_name)) {
if (input_terminfo_get_name(seq, out_name)) {
return out_name;
}
}
@ -230,18 +229,21 @@ static double output_elapsed_time(double prev_tstamp, bool first_char_seen, bool
static void process_input(bool continuous_mode, bool verbose) {
bool first_char_seen = false;
double prev_tstamp = 0.0;
input_event_queue_t queue;
auto queue = make_input_event_queue(STDIN_FILENO);
std::vector<wchar_t> bind_chars;
std::fwprintf(stderr, L"Press a key:\n");
while (!check_exit_loop_maybe_warning(nullptr)) {
maybe_t<char_event_t> evt{};
maybe_t<rust::Box<char_event_t>> evt{};
if (reader_test_and_clear_interrupted()) {
evt = char_event_t{shell_modes.c_cc[VINTR]};
evt = char_event_from_char(shell_modes.c_cc[VINTR]);
} else {
evt = queue.readch_timed_esc();
char_event_t *evt_raw = queue->readch_timed_esc();
if (evt_raw) {
evt = rust::Box<char_event_t>::from_raw(evt_raw);
}
if (!evt || !evt->is_char()) {
}
if (!evt || !(*evt)->is_char()) {
output_bind_command(bind_chars);
if (first_char_seen && !continuous_mode) {
return;
@ -249,7 +251,7 @@ static void process_input(bool continuous_mode, bool verbose) {
continue;
}
wchar_t wc = evt->get_char();
wchar_t wc = (*evt)->get_char();
prev_tstamp = output_elapsed_time(prev_tstamp, first_char_seen, verbose);
// Hack for #3189. Do not suggest \c@ as the binding for nul, because a string containing
// nul cannot be passed to builtin_bind since it uses C strings. We'll output the name of

View File

@ -73,8 +73,7 @@
#include "global_safety.h"
#include "highlight.h"
#include "history.h"
#include "input.h"
#include "input_common.h"
#include "input_ffi.rs.h"
#include "io.h"
#include "iothread.h"
#include "kill.rs.h"

View File

@ -1,973 +0,0 @@
// Functions for reading a character of input from stdin.
#include "config.h"
#include <errno.h>
#if HAVE_TERM_H
#include <curses.h> // IWYU pragma: keep
#include <term.h>
#elif HAVE_NCURSES_TERM_H
#include <ncurses/term.h>
#endif
#include <termios.h>
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "common.h"
#include "env.h"
#include "event.h"
#include "fallback.h" // IWYU pragma: keep
#include "flog.h"
#include "global_safety.h"
#include "input.h"
#include "input_common.h"
#include "parser.h"
#include "proc.h"
#include "reader.h"
#include "signals.h" // IWYU pragma: keep
#include "threads.rs.h"
#include "wutil.h" // IWYU pragma: keep
/// A name for our own key mapping for nul.
static const wchar_t *k_nul_mapping_name = L"nul";
/// Struct representing a keybinding. Returned by input_get_mappings.
struct input_mapping_t {
/// Character sequence which generates this event.
wcstring seq;
/// Commands that should be evaluated by this mapping.
std::vector<wcstring> commands;
/// We wish to preserve the user-specified order. This is just an incrementing value.
unsigned int specification_order;
/// Mode in which this command should be evaluated.
wcstring mode;
/// New mode that should be switched to after command evaluation.
wcstring sets_mode;
input_mapping_t(wcstring s, std::vector<wcstring> c, wcstring m, wcstring sm)
: seq(std::move(s)), commands(std::move(c)), mode(std::move(m)), sets_mode(std::move(sm)) {
static unsigned int s_last_input_map_spec_order = 0;
specification_order = ++s_last_input_map_spec_order;
}
/// \return true if this is a generic mapping, i.e. acts as a fallback.
bool is_generic() const { return seq.empty(); }
};
/// A struct representing the mapping from a terminfo key name to a terminfo character sequence.
struct terminfo_mapping_t {
// name of key
const wchar_t *name;
// character sequence generated on keypress, or none if there was no mapping.
maybe_t<std::string> seq;
terminfo_mapping_t(const wchar_t *name, const char *s) : name(name) {
if (s) seq.emplace(s);
}
terminfo_mapping_t(const wchar_t *name, std::string s) : name(name), seq(std::move(s)) {}
};
static constexpr size_t input_function_count = R_END_INPUT_FUNCTIONS;
/// Input function metadata. This list should be kept in sync with the key code list in
/// input_common.h.
struct input_function_metadata_t {
const wchar_t *name;
readline_cmd_t code;
};
/// A static mapping of all readline commands as strings to their readline_cmd_t equivalent.
/// Keep this list sorted alphabetically!
static constexpr const input_function_metadata_t input_function_metadata[] = {
// NULL makes it unusable - this is specially inserted when we detect mouse input
{L"", readline_cmd_t::disable_mouse_tracking},
{L"accept-autosuggestion", readline_cmd_t::accept_autosuggestion},
{L"and", readline_cmd_t::func_and},
{L"backward-bigword", readline_cmd_t::backward_bigword},
{L"backward-char", readline_cmd_t::backward_char},
{L"backward-delete-char", readline_cmd_t::backward_delete_char},
{L"backward-jump", readline_cmd_t::backward_jump},
{L"backward-jump-till", readline_cmd_t::backward_jump_till},
{L"backward-kill-bigword", readline_cmd_t::backward_kill_bigword},
{L"backward-kill-line", readline_cmd_t::backward_kill_line},
{L"backward-kill-path-component", readline_cmd_t::backward_kill_path_component},
{L"backward-kill-word", readline_cmd_t::backward_kill_word},
{L"backward-word", readline_cmd_t::backward_word},
{L"begin-selection", readline_cmd_t::begin_selection},
{L"begin-undo-group", readline_cmd_t::begin_undo_group},
{L"beginning-of-buffer", readline_cmd_t::beginning_of_buffer},
{L"beginning-of-history", readline_cmd_t::beginning_of_history},
{L"beginning-of-line", readline_cmd_t::beginning_of_line},
{L"cancel", readline_cmd_t::cancel},
{L"cancel-commandline", readline_cmd_t::cancel_commandline},
{L"capitalize-word", readline_cmd_t::capitalize_word},
{L"clear-screen", readline_cmd_t::clear_screen_and_repaint},
{L"complete", readline_cmd_t::complete},
{L"complete-and-search", readline_cmd_t::complete_and_search},
{L"delete-char", readline_cmd_t::delete_char},
{L"delete-or-exit", readline_cmd_t::delete_or_exit},
{L"down-line", readline_cmd_t::down_line},
{L"downcase-word", readline_cmd_t::downcase_word},
{L"end-of-buffer", readline_cmd_t::end_of_buffer},
{L"end-of-history", readline_cmd_t::end_of_history},
{L"end-of-line", readline_cmd_t::end_of_line},
{L"end-selection", readline_cmd_t::end_selection},
{L"end-undo-group", readline_cmd_t::end_undo_group},
{L"execute", readline_cmd_t::execute},
{L"exit", readline_cmd_t::exit},
{L"expand-abbr", readline_cmd_t::expand_abbr},
{L"force-repaint", readline_cmd_t::force_repaint},
{L"forward-bigword", readline_cmd_t::forward_bigword},
{L"forward-char", readline_cmd_t::forward_char},
{L"forward-jump", readline_cmd_t::forward_jump},
{L"forward-jump-till", readline_cmd_t::forward_jump_till},
{L"forward-single-char", readline_cmd_t::forward_single_char},
{L"forward-word", readline_cmd_t::forward_word},
{L"history-pager", readline_cmd_t::history_pager},
{L"history-pager-delete", readline_cmd_t::history_pager_delete},
{L"history-prefix-search-backward", readline_cmd_t::history_prefix_search_backward},
{L"history-prefix-search-forward", readline_cmd_t::history_prefix_search_forward},
{L"history-search-backward", readline_cmd_t::history_search_backward},
{L"history-search-forward", readline_cmd_t::history_search_forward},
{L"history-token-search-backward", readline_cmd_t::history_token_search_backward},
{L"history-token-search-forward", readline_cmd_t::history_token_search_forward},
{L"insert-line-over", readline_cmd_t::insert_line_over},
{L"insert-line-under", readline_cmd_t::insert_line_under},
{L"kill-bigword", readline_cmd_t::kill_bigword},
{L"kill-inner-line", readline_cmd_t::kill_inner_line},
{L"kill-line", readline_cmd_t::kill_line},
{L"kill-selection", readline_cmd_t::kill_selection},
{L"kill-whole-line", readline_cmd_t::kill_whole_line},
{L"kill-word", readline_cmd_t::kill_word},
{L"nextd-or-forward-word", readline_cmd_t::nextd_or_forward_word},
{L"or", readline_cmd_t::func_or},
{L"pager-toggle-search", readline_cmd_t::pager_toggle_search},
{L"prevd-or-backward-word", readline_cmd_t::prevd_or_backward_word},
{L"redo", readline_cmd_t::redo},
{L"repaint", readline_cmd_t::repaint},
{L"repaint-mode", readline_cmd_t::repaint_mode},
{L"repeat-jump", readline_cmd_t::repeat_jump},
{L"repeat-jump-reverse", readline_cmd_t::reverse_repeat_jump},
{L"self-insert", readline_cmd_t::self_insert},
{L"self-insert-notfirst", readline_cmd_t::self_insert_notfirst},
{L"suppress-autosuggestion", readline_cmd_t::suppress_autosuggestion},
{L"swap-selection-start-stop", readline_cmd_t::swap_selection_start_stop},
{L"togglecase-char", readline_cmd_t::togglecase_char},
{L"togglecase-selection", readline_cmd_t::togglecase_selection},
{L"transpose-chars", readline_cmd_t::transpose_chars},
{L"transpose-words", readline_cmd_t::transpose_words},
{L"undo", readline_cmd_t::undo},
{L"up-line", readline_cmd_t::up_line},
{L"upcase-word", readline_cmd_t::upcase_word},
{L"yank", readline_cmd_t::yank},
{L"yank-pop", readline_cmd_t::yank_pop},
};
ASSERT_SORTED_BY_NAME(input_function_metadata);
static_assert(sizeof(input_function_metadata) / sizeof(input_function_metadata[0]) ==
input_function_count,
"input_function_metadata size mismatch with input_common. Did you forget to update "
"input_function_metadata?");
// Keep this function for debug purposes
// See 031b265
wcstring describe_char(wint_t c) {
if (c < R_END_INPUT_FUNCTIONS) {
return format_string(L"%02x (%ls)", c, input_function_metadata[c].name);
}
return format_string(L"%02x", c);
}
using mapping_list_t = std::vector<input_mapping_t>;
input_mapping_set_t::input_mapping_set_t() = default;
input_mapping_set_t::~input_mapping_set_t() = default;
acquired_lock<input_mapping_set_t> input_mappings() {
static owning_lock<input_mapping_set_t> s_mappings{input_mapping_set_t()};
return s_mappings.acquire();
}
/// Terminfo map list.
static latch_t<std::vector<terminfo_mapping_t>> s_terminfo_mappings;
/// \return the input terminfo.
static std::vector<terminfo_mapping_t> create_input_terminfo();
/// Return the current bind mode.
static wcstring input_get_bind_mode(const environment_t &vars) {
auto mode = vars.get(FISH_BIND_MODE_VAR);
return mode ? mode->as_string() : DEFAULT_BIND_MODE;
}
/// Set the current bind mode.
static void input_set_bind_mode(const parser_t &parser, const wcstring &bm) {
// Only set this if it differs to not execute variable handlers all the time.
// modes may not be empty - empty is a sentinel value meaning to not change the mode
assert(!bm.empty());
if (input_get_bind_mode(env_stack_t{parser.vars_boxed()}) != bm) {
// Must send events here - see #6653.
parser.set_var_and_fire(FISH_BIND_MODE_VAR, ENV_GLOBAL, wcstring_list_ffi_t{{bm}});
}
}
/// Returns the arity of a given input function.
static int input_function_arity(readline_cmd_t function) {
switch (function) {
case readline_cmd_t::forward_jump:
case readline_cmd_t::backward_jump:
case readline_cmd_t::forward_jump_till:
case readline_cmd_t::backward_jump_till:
return 1;
default:
return 0;
}
}
/// Helper function to compare the lengths of sequences.
static bool length_is_greater_than(const input_mapping_t &m1, const input_mapping_t &m2) {
return m1.seq.size() > m2.seq.size();
}
static bool specification_order_is_less_than(const input_mapping_t &m1, const input_mapping_t &m2) {
return m1.specification_order < m2.specification_order;
}
/// Inserts an input mapping at the correct position. We sort them in descending order by length, so
/// that we test longer sequences first.
static void input_mapping_insert_sorted(mapping_list_t &ml, input_mapping_t new_mapping) {
auto loc = std::lower_bound(ml.begin(), ml.end(), new_mapping, length_is_greater_than);
ml.insert(loc, std::move(new_mapping));
}
/// Adds an input mapping.
void input_mapping_set_t::add(wcstring sequence, const wchar_t *const *commands,
size_t commands_len, const wchar_t *mode, const wchar_t *sets_mode,
bool user) {
assert(commands && mode && sets_mode && "Null parameter");
// Clear cached mappings.
all_mappings_cache_.reset();
// Remove existing mappings with this sequence.
const std::vector<wcstring> commands_vector(commands, commands + commands_len);
mapping_list_t &ml = user ? mapping_list_ : preset_mapping_list_;
for (input_mapping_t &m : ml) {
if (m.seq == sequence && m.mode == mode) {
m.commands = commands_vector;
m.sets_mode = sets_mode;
return;
}
}
// Add a new mapping, using the next order.
input_mapping_t new_mapping =
input_mapping_t(std::move(sequence), commands_vector, mode, sets_mode);
input_mapping_insert_sorted(ml, std::move(new_mapping));
}
void input_mapping_set_t::add(wcstring sequence, const wchar_t *command, const wchar_t *mode,
const wchar_t *sets_mode, bool user) {
input_mapping_set_t::add(std::move(sequence), &command, 1, mode, sets_mode, user);
}
/// Set up arrays used by readch to detect escape sequences for special keys and perform related
/// initializations for our input subsystem.
void init_input() {
ASSERT_IS_MAIN_THREAD();
if (s_terminfo_mappings.is_set()) return;
s_terminfo_mappings = create_input_terminfo();
auto input_mapping = input_mappings();
// If we have no keybindings, add a few simple defaults.
if (input_mapping->preset_mapping_list_.empty()) {
input_mapping->add(L"", L"self-insert", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
input_mapping->add(L"\n", L"execute", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
input_mapping->add(L"\r", L"execute", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
input_mapping->add(L"\t", L"complete", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
input_mapping->add(L"\x3", L"cancel-commandline", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE,
false);
input_mapping->add(L"\x4", L"exit", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
input_mapping->add(L"\x5", L"bind", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
// ctrl-s
input_mapping->add(L"\x13", L"pager-toggle-search", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE,
false);
// ctrl-u
input_mapping->add(L"\x15", L"backward-kill-line", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE,
false);
// del/backspace
input_mapping->add(L"\x7f", L"backward-delete-char", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE,
false);
// Arrows - can't have functions, so *-or-search isn't available.
input_mapping->add(L"\x1B[A", L"up-line", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
input_mapping->add(L"\x1B[B", L"down-line", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
input_mapping->add(L"\x1B[C", L"forward-char", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
input_mapping->add(L"\x1B[D", L"backward-char", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE,
false);
// emacs-style ctrl-p/n/b/f
input_mapping->add(L"\x10", L"up-line", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
input_mapping->add(L"\x0e", L"down-line", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
input_mapping->add(L"\x02", L"backward-char", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
input_mapping->add(L"\x06", L"forward-char", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
}
}
inputter_t::inputter_t(const parser_t &parser, int in)
: input_event_queue_t(in), parser_(parser.shared()) {}
void inputter_t::prepare_to_select() /* override */ {
// Fire any pending events and reap stray processes, including printing exit status messages.
auto &parser = this->parser_->deref();
event_fire_delayed(parser);
if (job_reap(parser, true)) reader_schedule_prompt_repaint();
}
void inputter_t::select_interrupted() /* override */ {
// Readline commands may be bound to \cc which also sets the cancel flag.
// See #6937, #8125.
signal_clear_cancel();
// Fire any pending events and reap stray processes, including printing exit status messages.
auto &parser = this->parser_->deref();
event_fire_delayed(parser);
if (job_reap(parser, true)) reader_schedule_prompt_repaint();
// Tell the reader an event occurred.
if (reader_reading_interrupted()) {
auto vintr = shell_modes.c_cc[VINTR];
if (vintr != 0) {
this->push_front(char_event_t{vintr});
}
return;
}
this->push_front(char_event_t{char_event_type_t::check_exit});
}
void inputter_t::uvar_change_notified() /* override */ {
this->parser_->deref().sync_uvars_and_fire(true /* always */);
}
void inputter_t::function_push_arg(wchar_t arg) { input_function_args_.push_back(arg); }
wchar_t inputter_t::function_pop_arg() {
assert(!input_function_args_.empty() && "function_pop_arg underflow");
auto result = input_function_args_.back();
input_function_args_.pop_back();
return result;
}
void inputter_t::function_push_args(readline_cmd_t code) {
int arity = input_function_arity(code);
assert(event_storage_.empty() && "event_storage_ should be empty");
auto &skipped = event_storage_;
for (int i = 0; i < arity; i++) {
// Skip and queue up any function codes. See issue #2357.
wchar_t arg{};
for (;;) {
auto evt = this->readch();
if (evt.is_char()) {
arg = evt.get_char();
break;
}
skipped.push_back(evt);
}
function_push_arg(arg);
}
// Push the function codes back into the input stream.
this->insert_front(skipped.begin(), skipped.end());
event_storage_.clear();
}
/// Perform the action of the specified binding. allow_commands controls whether fish commands
/// should be executed, or should be deferred until later.
void inputter_t::mapping_execute(const input_mapping_t &m,
const command_handler_t &command_handler) {
// has_functions: there are functions that need to be put on the input queue
// has_commands: there are shell commands that need to be evaluated
bool has_commands = false, has_functions = false;
for (const wcstring &cmd : m.commands) {
if (input_function_get_code(cmd)) {
has_functions = true;
} else {
has_commands = true;
}
if (has_functions && has_commands) {
break;
}
}
// !has_functions && !has_commands: only set bind mode
if (!has_commands && !has_functions) {
if (!m.sets_mode.empty()) input_set_bind_mode(parser_->deref(), m.sets_mode);
return;
}
if (has_commands && !command_handler) {
// We don't want to run commands yet. Put the characters back and return check_exit.
this->insert_front(m.seq.cbegin(), m.seq.cend());
this->push_front(char_event_type_t::check_exit);
return; // skip the input_set_bind_mode
} else if (has_functions && !has_commands) {
// Functions are added at the head of the input queue.
for (auto it = m.commands.rbegin(), end = m.commands.rend(); it != end; ++it) {
readline_cmd_t code = input_function_get_code(*it).value();
function_push_args(code);
this->push_front(char_event_t(code, m.seq));
}
} else if (has_commands && !has_functions) {
// Execute all commands.
//
// FIXME(snnw): if commands add stuff to input queue (e.g. commandline -f execute), we won't
// see that until all other commands have also been run.
command_handler(m.commands);
this->push_front(char_event_type_t::check_exit);
} else {
// Invalid binding, mixed commands and functions. We would need to execute these one by
// one.
this->push_front(char_event_type_t::check_exit);
}
// Empty bind mode indicates to not reset the mode (#2871)
if (!m.sets_mode.empty()) input_set_bind_mode(parser_->deref(), m.sets_mode);
}
void inputter_t::queue_char(const char_event_t &ch) {
if (ch.is_readline()) {
function_push_args(ch.get_readline());
}
this->push_back(ch);
}
/// A class 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.
class event_queue_peeker_t {
public:
explicit event_queue_peeker_t(input_event_queue_t &event_queue) : event_queue_(event_queue) {}
/// \return the next event.
char_event_t next() {
assert(idx_ <= peeked_.size() && "Index must not be larger than dequeued event count");
if (idx_ == peeked_.size()) {
auto event = event_queue_.readch();
peeked_.push_back(event);
}
return peeked_.at(idx_++);
}
/// Check if the next event is the given character. This advances the index on success only.
/// If \p timed is set, then return false if this (or any other) character had a timeout.
bool next_is_char(wchar_t c, bool escaped = false) {
assert(idx_ <= peeked_.size() && "Index must not be larger than dequeued event count");
// See if we had a timeout already.
if (escaped && had_timeout_) {
return false;
}
// Grab a new event if we have exhausted what we have already peeked.
// Use either readch or readch_timed, per our param.
if (idx_ == peeked_.size()) {
char_event_t newevt{L'\0'};
if (!escaped) {
if (auto mevt = event_queue_.readch_timed_sequence_key()) {
newevt = mevt.acquire();
} else {
had_timeout_ = true;
return false;
}
} else if (auto mevt = event_queue_.readch_timed_esc()) {
newevt = mevt.acquire();
} else {
had_timeout_ = true;
return false;
}
peeked_.push_back(newevt);
}
// Now we have peeked far enough; check the event.
// If it matches the char, then increment the index.
if (peeked_.at(idx_).maybe_char() == c) {
idx_++;
return true;
}
return false;
}
/// \return the current index.
size_t len() const { return idx_; }
/// Consume all events up to the current index.
/// Remaining events are returned to the queue.
void consume() {
event_queue_.insert_front(peeked_.cbegin() + idx_, peeked_.cend());
peeked_.clear();
idx_ = 0;
}
/// Test if any of our peeked events are readline or check_exit.
bool char_sequence_interrupted() const {
for (const auto &evt : peeked_) {
if (evt.is_readline() || evt.is_check_exit()) return true;
}
return false;
}
/// Reset our index back to 0.
void restart() { idx_ = 0; }
~event_queue_peeker_t() {
assert(idx_ == 0 && "Events left on the queue - missing restart or consume?");
consume();
}
private:
/// The list of events which have been dequeued.
std::vector<char_event_t> peeked_{};
/// If set, then some previous timed event timed out.
bool had_timeout_{false};
/// The current index. This never exceeds peeked_.size().
size_t idx_{0};
/// The queue from which to read more events.
input_event_queue_t &event_queue_;
};
/// Try reading a mouse-tracking CSI sequence, using the given \p peeker.
/// Events are left on the peeker and the caller must restart or consume it.
/// \return true if matched, false if not.
static bool have_mouse_tracking_csi(event_queue_peeker_t *peeker) {
// Maximum length of any CSI is NPAR (which is nominally 16), although this does not account for
// user input intermixed with pseudo input generated by the tty emulator.
// Check for the CSI first.
if (!peeker->next_is_char(L'\x1b') || !peeker->next_is_char(L'[', true /* escaped */)) {
return false;
}
auto next = peeker->next().maybe_char();
size_t length = 0;
if (next == L'M') {
// Generic X10 or modified VT200 sequence. It doesn't matter which, they're both 6 chars
// (although in mode 1005, the characters may be unicode and not necessarily just one byte
// long) reporting the button that was clicked and its location.
length = 6;
} else if (next == L'<') {
// Extended (SGR/1006) mouse reporting mode, with semicolon-separated parameters for button
// code, Px, and Py, ending with 'M' for button press or 'm' for button release.
while (true) {
next = peeker->next().maybe_char();
if (next == L'M' || next == L'm') {
// However much we've read, we've consumed the CSI in its entirety.
length = peeker->len();
break;
}
if (peeker->len() >= 16) {
// This is likely a malformed mouse-reporting CSI but we can't do anything about it.
return false;
}
}
} else if (next == L't') {
// VT200 button released in mouse highlighting mode at valid text location. 5 chars.
length = 5;
} else if (next == L'T') {
// VT200 button released in mouse highlighting mode past end-of-line. 9 characters.
length = 9;
} else {
return false;
}
// Consume however many characters it takes to prevent the mouse tracking sequence from reaching
// the prompt, dependent on the class of mouse reporting as detected above.
while (peeker->len() < length) {
(void)peeker->next();
}
return true;
}
/// \return true if a given \p peeker matches a given sequence of char events given by \p str.
static bool try_peek_sequence(event_queue_peeker_t *peeker, const wcstring &str) {
assert(!str.empty() && "Empty string passed to try_peek_sequence");
wchar_t prev = L'\0';
for (wchar_t c : str) {
// 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.
bool escaped = prev == L'\x1B';
if (!peeker->next_is_char(c, escaped)) {
return false;
}
prev = c;
}
return true;
}
/// \return the first mapping that matches, 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.
maybe_t<input_mapping_t> inputter_t::find_mapping(event_queue_peeker_t *peeker) {
const input_mapping_t *generic = nullptr;
env_stack_t vars{parser_->deref().vars_boxed()};
const wcstring bind_mode = input_get_bind_mode(vars);
const input_mapping_t *escape = nullptr;
auto ml = input_mappings()->all_mappings();
for (const auto &m : *ml) {
if (m.mode != bind_mode) {
continue;
}
// Defer generic mappings until the end.
if (m.is_generic()) {
if (!generic) generic = &m;
continue;
}
if (try_peek_sequence(peeker, m.seq)) {
// A binding for just escape should also be deferred
// so escape sequences take precedence.
if (m.seq == L"\x1B") {
if (!escape) {
escape = &m;
}
} else {
return m;
}
}
peeker->restart();
}
if (peeker->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();
}
if (escape) {
// We need to reconsume the escape.
peeker->next();
return *escape;
}
return generic ? maybe_t<input_mapping_t>(*generic) : none();
}
void inputter_t::mapping_execute_matching_or_generic(const command_handler_t &command_handler) {
event_queue_peeker_t peeker(*this);
// Check for mouse-tracking CSI before mappings to prevent the generic mapping handler from
// taking over.
if (have_mouse_tracking_csi(&peeker)) {
// fish recognizes but does not actually support mouse reporting. We never turn it on, and
// it's only ever enabled if a program we spawned enabled it and crashed or forgot to turn
// it off before exiting. We swallow the events to prevent garbage from piling up at the
// prompt, but don't do anything further with the received codes. To prevent this from
// breaking user interaction with the tty emulator, wasting CPU, and adding latency to the
// event queue, we turn off mouse reporting here.
//
// Since this is only called when we detect an incoming mouse reporting payload, we know the
// terminal emulator supports the xterm ANSI extensions for mouse reporting and can safely
// issue this without worrying about termcap.
FLOGF(reader, "Disabling mouse tracking");
// We can't/shouldn't directly manipulate stdout from `input.cpp`, so request the execution
// of a helper function to disable mouse tracking.
// writembs(outputter_t::stdoutput(), "\x1B[?1000l");
peeker.consume();
this->push_front(char_event_t(readline_cmd_t::disable_mouse_tracking, L""));
return;
}
peeker.restart();
// Check for ordinary mappings.
if (auto mapping = find_mapping(&peeker)) {
peeker.consume();
mapping_execute(*mapping, command_handler);
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();
this->promote_interruptions_to_front();
return;
}
FLOGF(reader, L"no generic found, ignoring char...");
auto evt = peeker.next();
peeker.consume();
}
/// Helper function. Picks through the queue of incoming characters until we get to one that's not a
/// readline function.
char_event_t inputter_t::read_characters_no_readline() {
assert(event_storage_.empty() && "saved_events_storage should be empty");
auto &saved_events = event_storage_;
char_event_t evt_to_return{0};
for (;;) {
auto evt = this->readch();
if (evt.is_readline()) {
saved_events.push_back(evt);
} else {
evt_to_return = evt;
break;
}
}
// Restore any readline functions
this->insert_front(saved_events.cbegin(), saved_events.cend());
event_storage_.clear();
return evt_to_return;
}
char_event_t inputter_t::read_char(const command_handler_t &command_handler) {
// Clear the interrupted flag.
reader_reset_interrupted();
// Search for sequence in mapping tables.
while (true) {
auto evt = this->readch();
if (evt.is_readline()) {
switch (evt.get_readline()) {
case readline_cmd_t::self_insert:
case readline_cmd_t::self_insert_notfirst: {
// Typically self-insert is generated by the generic (empty) binding.
// However if it is generated by a real sequence, then insert that sequence.
this->insert_front(evt.seq.cbegin(), evt.seq.cend());
// Issue #1595: ensure we only insert characters, not readline functions. The
// common case is that this will be empty.
char_event_t res = read_characters_no_readline();
// Hackish: mark the input style.
res.input_style = evt.get_readline() == readline_cmd_t::self_insert_notfirst
? char_input_style_t::notfirst
: char_input_style_t::normal;
return res;
}
case readline_cmd_t::func_and:
case readline_cmd_t::func_or: {
// If previous function has bad status, we want to skip all functions that
// follow us.
if ((evt.get_readline() == readline_cmd_t::func_and) != function_status_) {
drop_leading_readline_events();
}
continue;
}
default: {
return evt;
}
}
} else if (evt.is_eof()) {
// If we have EOF, we need to immediately quit.
// There's no need to go through the input functions.
return evt;
} else if (evt.is_check_exit()) {
// Allow the reader to check for exit conditions.
return evt;
} else {
assert(evt.is_char() && "Should be char event");
this->push_front(evt);
mapping_execute_matching_or_generic(command_handler);
// Regarding allow_commands, we're in a loop, but if a fish command is executed,
// check_exit is unread, so the next pass through the loop we'll break out and return
// it.
}
}
}
std::vector<input_mapping_name_t> input_mapping_set_t::get_names(bool user) const {
// Sort the mappings by the user specification order, so we can return them in the same order
// that the user specified them in.
std::vector<input_mapping_t> local_list = user ? mapping_list_ : preset_mapping_list_;
std::sort(local_list.begin(), local_list.end(), specification_order_is_less_than);
std::vector<input_mapping_name_t> result;
result.reserve(local_list.size());
for (const auto &m : local_list) {
result.push_back((input_mapping_name_t){m.seq, m.mode});
}
return result;
}
void input_mapping_set_t::clear(const wchar_t *mode, bool user) {
all_mappings_cache_.reset();
mapping_list_t &ml = user ? mapping_list_ : preset_mapping_list_;
auto should_erase = [=](const input_mapping_t &m) { return mode == nullptr || mode == m.mode; };
ml.erase(std::remove_if(ml.begin(), ml.end(), should_erase), ml.end());
}
bool input_mapping_set_t::erase(const wcstring &sequence, const wcstring &mode, bool user) {
// Clear cached mappings.
all_mappings_cache_.reset();
bool result = false;
mapping_list_t &ml = user ? mapping_list_ : preset_mapping_list_;
for (auto it = ml.begin(), end = ml.end(); it != end; ++it) {
if (sequence == it->seq && mode == it->mode) {
ml.erase(it);
result = true;
break;
}
}
return result;
}
bool input_mapping_set_t::get(const wcstring &sequence, const wcstring &mode,
std::vector<wcstring> *out_cmds, bool user,
wcstring *out_sets_mode) const {
bool result = false;
const auto &ml = user ? mapping_list_ : preset_mapping_list_;
for (const input_mapping_t &m : ml) {
if (sequence == m.seq && mode == m.mode) {
*out_cmds = m.commands;
*out_sets_mode = m.sets_mode;
result = true;
break;
}
}
return result;
}
std::shared_ptr<const mapping_list_t> input_mapping_set_t::all_mappings() {
// Populate the cache if needed.
if (!all_mappings_cache_) {
mapping_list_t all_mappings = mapping_list_;
all_mappings.insert(all_mappings.end(), preset_mapping_list_.begin(),
preset_mapping_list_.end());
all_mappings_cache_ = std::make_shared<const mapping_list_t>(std::move(all_mappings));
}
return all_mappings_cache_;
}
/// Create a list of terminfo mappings.
static std::vector<terminfo_mapping_t> create_input_terminfo() {
assert(CURSES_INITIALIZED);
if (!cur_term) return {}; // setupterm() failed so we can't referency any key definitions
#define TERMINFO_ADD(key) \
{ (L## #key) + 4, key }
return {
TERMINFO_ADD(key_a1), TERMINFO_ADD(key_a3), TERMINFO_ADD(key_b2),
TERMINFO_ADD(key_backspace), TERMINFO_ADD(key_beg), TERMINFO_ADD(key_btab),
TERMINFO_ADD(key_c1), TERMINFO_ADD(key_c3), TERMINFO_ADD(key_cancel),
TERMINFO_ADD(key_catab), TERMINFO_ADD(key_clear), TERMINFO_ADD(key_close),
TERMINFO_ADD(key_command), TERMINFO_ADD(key_copy), TERMINFO_ADD(key_create),
TERMINFO_ADD(key_ctab), TERMINFO_ADD(key_dc), TERMINFO_ADD(key_dl), TERMINFO_ADD(key_down),
TERMINFO_ADD(key_eic), TERMINFO_ADD(key_end), TERMINFO_ADD(key_enter),
TERMINFO_ADD(key_eol), TERMINFO_ADD(key_eos), TERMINFO_ADD(key_exit), TERMINFO_ADD(key_f0),
TERMINFO_ADD(key_f1), TERMINFO_ADD(key_f2), TERMINFO_ADD(key_f3), TERMINFO_ADD(key_f4),
TERMINFO_ADD(key_f5), TERMINFO_ADD(key_f6), TERMINFO_ADD(key_f7), TERMINFO_ADD(key_f8),
TERMINFO_ADD(key_f9), TERMINFO_ADD(key_f10), TERMINFO_ADD(key_f11), TERMINFO_ADD(key_f12),
TERMINFO_ADD(key_f13), TERMINFO_ADD(key_f14), TERMINFO_ADD(key_f15), TERMINFO_ADD(key_f16),
TERMINFO_ADD(key_f17), TERMINFO_ADD(key_f18), TERMINFO_ADD(key_f19), TERMINFO_ADD(key_f20),
// Note key_f21 through key_f63 are available but no actual keyboard supports them.
TERMINFO_ADD(key_find), TERMINFO_ADD(key_help), TERMINFO_ADD(key_home),
TERMINFO_ADD(key_ic), TERMINFO_ADD(key_il), TERMINFO_ADD(key_left), TERMINFO_ADD(key_ll),
TERMINFO_ADD(key_mark), TERMINFO_ADD(key_message), TERMINFO_ADD(key_move),
TERMINFO_ADD(key_next), TERMINFO_ADD(key_npage), TERMINFO_ADD(key_open),
TERMINFO_ADD(key_options), TERMINFO_ADD(key_ppage), TERMINFO_ADD(key_previous),
TERMINFO_ADD(key_print), TERMINFO_ADD(key_redo), TERMINFO_ADD(key_reference),
TERMINFO_ADD(key_refresh), TERMINFO_ADD(key_replace), TERMINFO_ADD(key_restart),
TERMINFO_ADD(key_resume), TERMINFO_ADD(key_right), TERMINFO_ADD(key_save),
TERMINFO_ADD(key_sbeg), TERMINFO_ADD(key_scancel), TERMINFO_ADD(key_scommand),
TERMINFO_ADD(key_scopy), TERMINFO_ADD(key_screate), TERMINFO_ADD(key_sdc),
TERMINFO_ADD(key_sdl), TERMINFO_ADD(key_select), TERMINFO_ADD(key_send),
TERMINFO_ADD(key_seol), TERMINFO_ADD(key_sexit), TERMINFO_ADD(key_sf),
TERMINFO_ADD(key_sfind), TERMINFO_ADD(key_shelp), TERMINFO_ADD(key_shome),
TERMINFO_ADD(key_sic), TERMINFO_ADD(key_sleft), TERMINFO_ADD(key_smessage),
TERMINFO_ADD(key_smove), TERMINFO_ADD(key_snext), TERMINFO_ADD(key_soptions),
TERMINFO_ADD(key_sprevious), TERMINFO_ADD(key_sprint), TERMINFO_ADD(key_sr),
TERMINFO_ADD(key_sredo), TERMINFO_ADD(key_sreplace), TERMINFO_ADD(key_sright),
TERMINFO_ADD(key_srsume), TERMINFO_ADD(key_ssave), TERMINFO_ADD(key_ssuspend),
TERMINFO_ADD(key_stab), TERMINFO_ADD(key_sundo), TERMINFO_ADD(key_suspend),
TERMINFO_ADD(key_undo), TERMINFO_ADD(key_up),
// We introduce our own name for the string containing only the nul character - see
// #3189. This can typically be generated via control-space.
terminfo_mapping_t(k_nul_mapping_name, std::string{'\0'})};
#undef TERMINFO_ADD
}
bool input_terminfo_get_sequence(const wcstring &name, wcstring *out_seq) {
assert(s_terminfo_mappings.is_set());
for (const terminfo_mapping_t &m : *s_terminfo_mappings) {
if (name == m.name) {
// Found the mapping.
if (!m.seq) {
errno = EILSEQ;
return false;
} else {
*out_seq = str2wcstring(*m.seq);
return true;
}
}
}
errno = ENOENT;
return false;
}
bool input_terminfo_get_name(const wcstring &seq, wcstring *out_name) {
assert(s_terminfo_mappings.is_set());
for (const terminfo_mapping_t &m : *s_terminfo_mappings) {
if (m.seq && seq == str2wcstring(*m.seq)) {
out_name->assign(m.name);
return true;
}
}
return false;
}
std::vector<wcstring> input_terminfo_get_names(bool skip_null) {
assert(s_terminfo_mappings.is_set());
std::vector<wcstring> result;
const auto &mappings = *s_terminfo_mappings;
result.reserve(mappings.size());
for (const terminfo_mapping_t &m : mappings) {
if (skip_null && !m.seq) {
continue;
}
result.emplace_back(m.name);
}
return result;
}
const std::vector<wcstring> &input_function_get_names() {
// The list and names of input functions are hard-coded and never change
static std::vector<wcstring> result = ([&]() {
std::vector<wcstring> result;
result.reserve(input_function_count);
for (const auto &md : input_function_metadata) {
if (md.name[0]) {
result.push_back(md.name);
}
}
return result;
})();
return result;
}
maybe_t<readline_cmd_t> input_function_get_code(const wcstring &name) {
// `input_function_metadata` is required to be kept in asciibetical order, making it OK to do
// a binary search for the matching name.
if (const input_function_metadata_t *md = get_by_sorted_name(name, input_function_metadata)) {
return md->code;
}
return none();
}

View File

@ -1,162 +0,0 @@
// Functions for reading a character of input from stdin, using the inputrc information for key
// bindings.
#ifndef FISH_INPUT_H
#define FISH_INPUT_H
#include <stddef.h>
#include <unistd.h>
#include <functional>
#include <memory>
#include <vector>
#include "common.h"
#include "input_common.h"
#include "maybe.h"
#include "parser.h"
#define FISH_BIND_MODE_VAR L"fish_bind_mode"
#define DEFAULT_BIND_MODE L"default"
class event_queue_peeker_t;
wcstring describe_char(wint_t c);
/// Set up arrays used by readch to detect escape sequences for special keys and perform related
/// initializations for our input subsystem.
void init_input();
struct input_mapping_t;
class inputter_t final : private input_event_queue_t {
public:
/// Construct from a parser, and the fd from which to read.
explicit inputter_t(const parser_t &parser, int in = STDIN_FILENO);
/// Read a character from stdin. Try to convert some escape sequences into character constants,
/// but do not permanently block the escape character.
///
/// This is performed in the same way vim does it, i.e. if an escape character is read, wait for
/// more input for a short time (a few milliseconds). If more input is available, it is assumed
/// to be an escape sequence for a special character (such as an arrow key), and readch attempts
/// to parse it. If no more input follows after the escape key, it is assumed to be an actual
/// escape key press, and is returned as such.
///
/// \p command_handler is used to run commands. If empty (in the std::function sense), when a
/// character is encountered that would invoke a fish command, it is unread and
/// char_event_type_t::check_exit is returned. Note the handler is not stored.
using command_handler_t = std::function<void(const std::vector<wcstring> &)>;
char_event_t read_char(const command_handler_t &command_handler = {});
/// Enqueue a char event to the queue of unread characters that input_readch will return before
/// actually reading from fd 0.
void queue_char(const char_event_t &ch);
/// Sets the return status of the most recently executed input function.
void function_set_status(bool status) { function_status_ = status; }
/// Pop an argument from the function argument stack.
wchar_t function_pop_arg();
private:
// Called right before potentially blocking in select().
void prepare_to_select() override;
// Called when select() is interrupted by a signal.
void select_interrupted() override;
// Called when we are notified of a uvar change.
void uvar_change_notified() override;
void function_push_arg(wchar_t arg);
void function_push_args(readline_cmd_t code);
void mapping_execute(const input_mapping_t &m, const command_handler_t &command_handler);
void mapping_execute_matching_or_generic(const command_handler_t &command_handler);
maybe_t<input_mapping_t> find_mapping(event_queue_peeker_t *peeker);
char_event_t read_characters_no_readline();
#if INCLUDE_RUST_HEADERS
// We need a parser to evaluate bindings.
const rust::Box<ParserRef> parser_;
#endif
std::vector<wchar_t> input_function_args_{};
bool function_status_{false};
// Transient storage to avoid repeated allocations.
std::vector<char_event_t> event_storage_{};
};
struct input_mapping_name_t {
wcstring seq;
wcstring mode;
};
/// The input mapping set is the set of mappings from character sequences to commands.
class input_mapping_set_t {
friend acquired_lock<input_mapping_set_t> input_mappings();
friend void init_input();
using mapping_list_t = std::vector<input_mapping_t>;
mapping_list_t mapping_list_;
mapping_list_t preset_mapping_list_;
std::shared_ptr<const mapping_list_t> all_mappings_cache_;
input_mapping_set_t();
public:
~input_mapping_set_t();
/// Erase all bindings.
void clear(const wchar_t *mode = nullptr, bool user = true);
/// Erase binding for specified key sequence.
bool erase(const wcstring &sequence, const wcstring &mode = DEFAULT_BIND_MODE,
bool user = true);
/// Gets the command bound to the specified key sequence in the specified mode. Returns true if
/// it exists, false if not.
bool get(const wcstring &sequence, const wcstring &mode, std::vector<wcstring> *out_cmds,
bool user, wcstring *out_sets_mode) const;
/// Returns all mapping names and modes.
std::vector<input_mapping_name_t> get_names(bool user = true) const;
/// Add a key mapping from the specified sequence to the specified command.
///
/// \param sequence the sequence to bind
/// \param command an input function that will be run whenever the key sequence occurs
void add(wcstring sequence, const wchar_t *command, const wchar_t *mode = DEFAULT_BIND_MODE,
const wchar_t *sets_mode = DEFAULT_BIND_MODE, bool user = true);
void add(wcstring sequence, const wchar_t *const *commands, size_t commands_len,
const wchar_t *mode = DEFAULT_BIND_MODE, const wchar_t *sets_mode = DEFAULT_BIND_MODE,
bool user = true);
/// \return a snapshot of the list of input mappings.
std::shared_ptr<const mapping_list_t> all_mappings();
};
/// Access the singleton input mapping set.
acquired_lock<input_mapping_set_t> input_mappings();
/// Return the sequence for the terminfo variable of the specified name.
///
/// If no terminfo variable of the specified name could be found, return false and set errno to
/// ENOENT. If the terminfo variable does not have a value, return false and set errno to EILSEQ.
bool input_terminfo_get_sequence(const wcstring &name, wcstring *out_seq);
/// Return the name of the terminfo variable with the specified sequence, in out_name. Returns true
/// if found, false if not found.
bool input_terminfo_get_name(const wcstring &seq, wcstring *out_name);
/// Return a list of all known terminfo names.
std::vector<wcstring> input_terminfo_get_names(bool skip_null);
/// Returns the input function code for the given input function name.
maybe_t<readline_cmd_t> input_function_get_code(const wcstring &name);
/// Returns a list of all existing input function names.
const std::vector<wcstring> &input_function_get_names(void);
#endif

View File

@ -1,341 +0,0 @@
// Implementation file for the low level input library.
#include "config.h"
#include <errno.h>
#include <pthread.h> // IWYU pragma: keep
#include <signal.h>
#include <stdio.h>
#include <sys/types.h>
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#include <algorithm>
#include <climits>
#include <cstdlib>
#include <cstring>
#include <cwchar>
#include <deque>
#include <utility>
#include "common.h"
#include "env.h"
#include "env_universal_common.h"
#include "fallback.h" // IWYU pragma: keep
#include "fd_readable_set.rs.h"
#include "fds.h"
#include "flog.h"
#include "input_common.h"
#include "iothread.h"
#include "wutil.h"
/// 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.
#define WAIT_ON_ESCAPE_DEFAULT 30
static int wait_on_escape_ms = WAIT_ON_ESCAPE_DEFAULT;
#define WAIT_ON_SEQUENCE_KEY_INFINITE (-1)
static int wait_on_sequence_key_ms = WAIT_ON_SEQUENCE_KEY_INFINITE;
input_event_queue_t::input_event_queue_t(int in) : in_(in) {}
/// 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 {
// The in fd has been closed.
readb_eof = -1,
// select() was interrupted by a signal.
readb_interrupted = -2,
// Our uvar notifier reported a change (either through poll() or its fd).
readb_uvar_notified = -3,
// Our ioport reported a change, so service main thread requests.
readb_ioport_notified = -4,
};
using readb_result_t = int;
static readb_result_t readb(int in_fd) {
assert(in_fd >= 0 && "Invalid in fd");
auto notifier_box = default_notifier();
const auto& notifier = *notifier_box;
auto fdset_box = new_fd_readable_set();
fd_readable_set_t& fdset = *fdset_box;
for (;;) {
fdset.clear();
fdset.add(in_fd);
// Add the completion ioport.
int ioport_fd = iothread_port();
fdset.add(ioport_fd);
// Get the uvar notifier fd (possibly none).
int notifier_fd = notifier.notification_fd();
fdset.add(notifier_fd);
// Here's where we call select().
int select_res = fdset.check_readable(kNoTimeout);
if (select_res < 0) {
if (errno == EINTR || errno == EAGAIN) {
// A signal.
return readb_interrupted;
} else {
// Some fd was invalid, so probably the tty has been closed.
return readb_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.
// This may come about through readability, or through a call to poll().
if (fdset.test(notifier_fd) && notifier.notification_fd_became_readable(notifier_fd)) {
return readb_uvar_notified;
}
// Check stdin.
if (fdset.test(in_fd)) {
unsigned char arr[1];
if (read_blocked(in_fd, arr, 1) != 1) {
// The terminal has been closed.
return readb_eof;
}
// The common path is to return a (non-negative) char.
return static_cast<int>(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 readb_ioport_notified;
}
}
}
// Update the wait_on_escape_ms value in response to the fish_escape_delay_ms user variable being
// set.
void update_wait_on_escape_ms(const environment_t& vars) {
auto escape_time_ms = vars.get_unless_empty(L"fish_escape_delay_ms");
if (!escape_time_ms) {
wait_on_escape_ms = WAIT_ON_ESCAPE_DEFAULT;
return;
}
long tmp = fish_wcstol(escape_time_ms->as_string().c_str());
if (errno || tmp < 10 || tmp >= 5000) {
std::fwprintf(stderr,
L"ignoring fish_escape_delay_ms: value '%ls' "
L"is not an integer or is < 10 or >= 5000 ms\n",
escape_time_ms->as_string().c_str());
} else {
wait_on_escape_ms = static_cast<int>(tmp);
}
}
void update_wait_on_escape_ms_ffi(bool empty, const wcstring& fish_escape_delay_ms) {
if (empty) {
wait_on_escape_ms = WAIT_ON_ESCAPE_DEFAULT;
return;
}
long tmp = fish_wcstol(fish_escape_delay_ms.c_str());
if (errno || tmp < 10 || tmp >= 5000) {
std::fwprintf(stderr,
L"ignoring fish_escape_delay_ms: value '%ls' "
L"is not an integer or is < 10 or >= 5000 ms\n",
fish_escape_delay_ms.c_str());
} else {
wait_on_escape_ms = static_cast<int>(tmp);
}
}
// Update the wait_on_sequence_key_ms value in response to the fish_sequence_key_delay_ms user
// variable being set.
void update_wait_on_sequence_key_ms(const environment_t& vars) {
auto sequence_key_time_ms = vars.get_unless_empty(L"fish_sequence_key_delay_ms");
if (!sequence_key_time_ms) {
wait_on_sequence_key_ms = WAIT_ON_SEQUENCE_KEY_INFINITE;
return;
}
long tmp = fish_wcstol(sequence_key_time_ms->as_string().c_str());
if (errno || tmp < 10 || tmp >= 5000) {
std::fwprintf(stderr,
L"ignoring fish_sequence_key_delay_ms: value '%ls' "
L"is not an integer or is < 10 or >= 5000 ms\n",
sequence_key_time_ms->as_string().c_str());
} else {
wait_on_sequence_key_ms = static_cast<int>(tmp);
}
}
void update_wait_on_sequence_key_ms_ffi(bool empty, const wcstring& fish_sequence_key_delay_ms) {
if (empty) {
wait_on_sequence_key_ms = WAIT_ON_SEQUENCE_KEY_INFINITE;
return;
}
long tmp = fish_wcstol(fish_sequence_key_delay_ms.c_str());
if (errno || tmp < 10 || tmp >= 5000) {
std::fwprintf(stderr,
L"ignoring fish_sequence_key_delay_ms: value '%ls' "
L"is not an integer or is < 10 or >= 5000 ms\n",
fish_sequence_key_delay_ms.c_str());
} else {
wait_on_sequence_key_ms = static_cast<int>(tmp);
}
}
maybe_t<char_event_t> input_event_queue_t::try_pop() {
if (queue_.empty()) {
return none();
}
auto result = std::move(queue_.front());
queue_.pop_front();
return result;
}
char_event_t input_event_queue_t::readch() {
wchar_t res{};
mbstate_t state = {};
for (;;) {
// 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 (auto mevt = try_pop()) {
return mevt.acquire();
}
// We are going to block; but first allow any override to inject events.
this->prepare_to_select();
if (auto mevt = try_pop()) {
return mevt.acquire();
}
readb_result_t rr = readb(in_);
switch (rr) {
case readb_eof:
return char_event_type_t::eof;
case readb_interrupted:
// FIXME: here signals may break multibyte sequences.
this->select_interrupted();
break;
case readb_uvar_notified:
this->uvar_change_notified();
break;
case readb_ioport_notified:
iothread_service_main();
break;
default: {
assert(rr >= 0 && rr <= UCHAR_MAX &&
"Read byte out of bounds - missing error case?");
char read_byte = static_cast<char>(static_cast<unsigned char>(rr));
if (MB_CUR_MAX == 1) {
// single-byte locale, all values are legal
res = read_byte;
return res;
}
size_t sz = std::mbrtowc(&res, &read_byte, 1, &state);
switch (sz) {
case static_cast<size_t>(-1):
std::memset(&state, '\0', sizeof(state));
FLOG(reader, L"Illegal input");
return char_event_type_t::check_exit;
case static_cast<size_t>(-2):
// Sequence not yet complete.
break;
case 0:
// Actual nul char.
return 0;
default:
// Sequence complete.
return res;
}
break;
}
}
}
}
maybe_t<char_event_t> input_event_queue_t::readch_timed_esc() {
return readch_timed(wait_on_escape_ms);
}
maybe_t<char_event_t> input_event_queue_t::readch_timed_sequence_key() {
if (wait_on_sequence_key_ms == WAIT_ON_SEQUENCE_KEY_INFINITE) {
return readch();
}
return readch_timed(wait_on_sequence_key_ms);
}
maybe_t<char_event_t> input_event_queue_t::readch_timed(const int wait_time_ms) {
if (auto evt = try_pop()) {
return 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().
sigset_t sigs;
sigfillset(&sigs);
// pselect expects timeouts in nanoseconds.
const uint64_t nsec_per_msec = 1000 * 1000;
const uint64_t nsec_per_sec = nsec_per_msec * 1000;
const uint64_t wait_nsec = wait_time_ms * nsec_per_msec;
struct timespec timeout;
timeout.tv_sec = (wait_nsec) / nsec_per_sec;
timeout.tv_nsec = (wait_nsec) % nsec_per_sec;
// We have one fd of interest.
fd_set fdset;
FD_ZERO(&fdset);
FD_SET(in_, &fdset);
int res = pselect(in_ + 1, &fdset, nullptr, nullptr, &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
pthread_sigmask(0, nullptr, &sigs);
}
if (res > 0) {
return readch();
}
return none();
}
void input_event_queue_t::push_back(const char_event_t& ch) { queue_.push_back(ch); }
void input_event_queue_t::push_front(const char_event_t& ch) { queue_.push_front(ch); }
void input_event_queue_t::promote_interruptions_to_front() {
// 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.
auto is_char = [](const char_event_t& ch) { return ch.is_char() || ch.is_eof(); };
auto first = std::find_if_not(queue_.begin(), queue_.end(), is_char);
auto last = std::find_if(first, queue_.end(), is_char);
std::rotate(queue_.begin(), first, last);
}
void input_event_queue_t::drop_leading_readline_events() {
queue_.erase(queue_.begin(),
std::find_if(queue_.begin(), queue_.end(),
[](const char_event_t& evt) { return !evt.is_readline(); }));
}
void input_event_queue_t::prepare_to_select() {}
void input_event_queue_t::select_interrupted() {}
void input_event_queue_t::uvar_change_notified() {}
input_event_queue_t::~input_event_queue_t() = default;

View File

@ -1,263 +0,0 @@
// Header file for the low level input library.
#ifndef INPUT_COMMON_H
#define INPUT_COMMON_H
#include <unistd.h>
#include <cstdint>
#include <deque>
#include <string>
#include <utility>
#include "common.h"
#include "env.h"
#include "maybe.h"
enum class readline_cmd_t {
beginning_of_line,
end_of_line,
forward_char,
backward_char,
forward_single_char,
forward_word,
backward_word,
forward_bigword,
backward_bigword,
nextd_or_forward_word,
prevd_or_backward_word,
history_search_backward,
history_search_forward,
history_prefix_search_backward,
history_prefix_search_forward,
history_pager,
history_pager_delete,
delete_char,
backward_delete_char,
kill_line,
yank,
yank_pop,
complete,
complete_and_search,
pager_toggle_search,
beginning_of_history,
end_of_history,
backward_kill_line,
kill_whole_line,
kill_inner_line,
kill_word,
kill_bigword,
backward_kill_word,
backward_kill_path_component,
backward_kill_bigword,
history_token_search_backward,
history_token_search_forward,
self_insert,
self_insert_notfirst,
transpose_chars,
transpose_words,
upcase_word,
downcase_word,
capitalize_word,
togglecase_char,
togglecase_selection,
execute,
beginning_of_buffer,
end_of_buffer,
repaint_mode,
repaint,
force_repaint,
up_line,
down_line,
suppress_autosuggestion,
accept_autosuggestion,
begin_selection,
swap_selection_start_stop,
end_selection,
kill_selection,
insert_line_under,
insert_line_over,
forward_jump,
backward_jump,
forward_jump_till,
backward_jump_till,
func_and,
func_or,
expand_abbr,
delete_or_exit,
exit,
cancel_commandline,
cancel,
undo,
redo,
begin_undo_group,
end_undo_group,
repeat_jump,
disable_mouse_tracking,
// ncurses uses the obvious name
clear_screen_and_repaint,
// NOTE: This one has to be last.
reverse_repeat_jump
};
// The range of key codes for inputrc-style keyboard functions.
enum { R_END_INPUT_FUNCTIONS = static_cast<int>(readline_cmd_t::reverse_repeat_jump) + 1 };
/// Represents an event on the character input stream.
enum class char_event_type_t : uint8_t {
/// A character was entered.
charc,
/// A readline event.
readline,
/// 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.
check_exit,
};
/// 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().
enum class char_input_style_t : uint8_t {
// Insert characters normally.
normal,
// Insert characters only if the cursor is not at the beginning. Otherwise, discard them.
notfirst,
};
class char_event_t {
union {
/// Set if the type is charc.
wchar_t c;
/// Set if the type is readline.
readline_cmd_t rl;
} v_{};
public:
/// The type of event.
char_event_type_t type;
/// The style to use when inserting characters into the command line.
char_input_style_t input_style{char_input_style_t::normal};
/// 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.
wcstring seq{};
bool is_char() const { return type == char_event_type_t::charc; }
bool is_eof() const { return type == char_event_type_t::eof; }
bool is_check_exit() const { return type == char_event_type_t::check_exit; }
bool is_readline() const { return type == char_event_type_t::readline; }
wchar_t get_char() const {
assert(type == char_event_type_t::charc && "Not a char type");
return v_.c;
}
maybe_t<wchar_t> maybe_char() const {
if (type == char_event_type_t::charc) {
return v_.c;
} else {
return none();
}
}
readline_cmd_t get_readline() const {
assert(type == char_event_type_t::readline && "Not a readline type");
return v_.rl;
}
/* implicit */ char_event_t(wchar_t c) : type(char_event_type_t::charc) { v_.c = c; }
/* implicit */ char_event_t(readline_cmd_t rl, wcstring seq = {})
: type(char_event_type_t::readline), seq(std::move(seq)) {
v_.rl = rl;
}
/* implicit */ char_event_t(char_event_type_t type) : type(type) {
assert(type != char_event_type_t::charc && type != char_event_type_t::readline &&
"Cannot create a char event with this constructor");
}
};
/// Adjust the escape timeout.
void update_wait_on_escape_ms(const environment_t &vars);
void update_wait_on_escape_ms_ffi(bool empty, const wcstring &fish_escape_delay_ms);
void update_wait_on_sequence_key_ms(const environment_t &vars);
void update_wait_on_sequence_key_ms_ffi(bool empty, const wcstring &fish_sequence_key_delay_ms);
/// A class which knows how to produce a stream of input events.
/// This is a base class; you may subclass it for its override points.
class input_event_queue_t {
public:
/// Construct from a file descriptor \p in, and an interrupt handler \p handler.
explicit input_event_queue_t(int in = STDIN_FILENO);
/// 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.
char_event_t readch();
/// Like readch(), except it will wait at most WAIT_ON_ESCAPE milliseconds for a
/// character to be available for reading.
/// \return none on timeout, the event on success.
maybe_t<char_event_t> readch_timed(const int wait_time_ms);
maybe_t<char_event_t> readch_timed_esc();
maybe_t<char_event_t> readch_timed_sequence_key();
/// Enqueue a character or a readline function to the queue of unread characters that
/// readch will return before actually reading from fd 0.
void push_back(const char_event_t &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.
void push_front(const char_event_t &ch);
/// Find the first sequence of non-char events, and promote them to the front.
void promote_interruptions_to_front();
/// Add multiple characters or 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.
template <typename Iterator>
void insert_front(const Iterator begin, const Iterator end) {
queue_.insert(queue_.begin(), begin, end);
}
/// Forget all enqueued readline events in the front of the queue.
void drop_leading_readline_events();
/// Override point for when we are about to (potentially) block in select(). The default does
/// nothing.
virtual void prepare_to_select();
/// Override point for when when select() is interrupted by a signal. The default does nothing.
virtual void select_interrupted();
/// Override point for when when select() is interrupted by the universal variable notifier.
/// The default does nothing.
virtual void uvar_change_notified();
virtual ~input_event_queue_t();
private:
/// \return if we have any lookahead.
bool has_lookahead() const { return !queue_.empty(); }
/// \return the next event in the queue, or none if the queue is empty.
maybe_t<char_event_t> try_pop();
int in_{0};
std::deque<char_event_t> queue_;
};
#endif

View File

@ -16,6 +16,7 @@
struct Parser;
using parser_t = Parser;
struct ParserRef;
#if INCLUDE_RUST_HEADERS
#include "parser.rs.h"

View File

@ -48,6 +48,7 @@
#include "abbrs.h"
#include "ast.h"
#include "callback.h"
#include "color.h"
#include "common.h"
#include "complete.h"
@ -64,8 +65,7 @@
#include "global_safety.h"
#include "highlight.h"
#include "history.h"
#include "input.h"
#include "input_common.h"
#include "input_ffi.rs.h"
#include "io.h"
#include "iothread.h"
#include "kill.rs.h"
@ -568,7 +568,7 @@ class reader_data_t : public std::enable_shared_from_this<reader_data_t> {
rust::Box<Screen> screen;
/// The source of input events.
inputter_t inputter;
rust::Box<Inputter> inputter;
/// The history.
maybe_t<rust::Box<HistorySharedPtr>> history{};
/// The history search.
@ -687,7 +687,7 @@ class reader_data_t : public std::enable_shared_from_this<reader_data_t> {
command_line_box(new_editable_line()),
command_line(*command_line_box),
screen(new_screen()),
inputter(parser_ref->deref(), conf.in),
inputter(make_inputter(*parser_ref, conf.in)),
history(hist.clone()) {}
void update_buff_pos(editable_line_t *el, maybe_t<size_t> new_pos = none_t());
@ -725,10 +725,10 @@ class reader_data_t : public std::enable_shared_from_this<reader_data_t> {
bool newv);
void run_input_command_scripts(const std::vector<wcstring> &cmds);
maybe_t<char_event_t> read_normal_chars(readline_loop_state_t &rls);
maybe_t<rust::Box<char_event_t>> read_normal_chars(readline_loop_state_t &rls);
void handle_readline_command(readline_cmd_t cmd, readline_loop_state_t &rls);
// Handle readline_cmd_t::execute. This may mean inserting a newline if the command is
// 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
// error event.
@ -1557,71 +1557,71 @@ void restore_term_mode() {
static bool command_ends_paging(readline_cmd_t c, bool focused_on_search_field) {
using rl = readline_cmd_t;
switch (c) {
case rl::history_prefix_search_backward:
case rl::history_prefix_search_forward:
case rl::history_search_backward:
case rl::history_search_forward:
case rl::history_token_search_backward:
case rl::history_token_search_forward:
case rl::accept_autosuggestion:
case rl::delete_or_exit:
case rl::cancel_commandline:
case rl::cancel: {
case rl::HistoryPrefixSearchBackward:
case rl::HistoryPrefixSearchForward:
case rl::HistorySearchBackward:
case rl::HistorySearchForward:
case rl::HistoryTokenSearchBackward:
case rl::HistoryTokenSearchForward:
case rl::AcceptAutosuggestion:
case rl::DeleteOrExit:
case rl::CancelCommandline:
case rl::Cancel: {
// These commands always end paging.
return true;
}
case rl::complete:
case rl::complete_and_search:
case rl::history_pager:
case rl::backward_char:
case rl::forward_char:
case rl::forward_single_char:
case rl::up_line:
case rl::down_line:
case rl::repaint:
case rl::suppress_autosuggestion:
case rl::beginning_of_history:
case rl::end_of_history: {
case rl::Complete:
case rl::CompleteAndSearch:
case rl::HistoryPager:
case rl::BackwardChar:
case rl::ForwardChar:
case rl::ForwardSingleChar:
case rl::UpLine:
case rl::DownLine:
case rl::Repaint:
case rl::SuppressAutosuggestion:
case rl::BeginningOfHistory:
case rl::EndOfHistory: {
// These commands never end paging.
return false;
}
case rl::execute: {
case rl::Execute: {
// execute does end paging, but only executes if it was not paging. So it's handled
// specially.
return false;
}
case rl::beginning_of_line:
case rl::end_of_line:
case rl::forward_word:
case rl::backward_word:
case rl::forward_bigword:
case rl::backward_bigword:
case rl::nextd_or_forward_word:
case rl::prevd_or_backward_word:
case rl::delete_char:
case rl::backward_delete_char:
case rl::kill_line:
case rl::yank:
case rl::yank_pop:
case rl::backward_kill_line:
case rl::kill_whole_line:
case rl::kill_inner_line:
case rl::kill_word:
case rl::kill_bigword:
case rl::backward_kill_word:
case rl::backward_kill_path_component:
case rl::backward_kill_bigword:
case rl::self_insert:
case rl::self_insert_notfirst:
case rl::transpose_chars:
case rl::transpose_words:
case rl::upcase_word:
case rl::downcase_word:
case rl::capitalize_word:
case rl::beginning_of_buffer:
case rl::end_of_buffer:
case rl::undo:
case rl::redo:
case rl::BeginningOfLine:
case rl::EndOfLine:
case rl::ForwardWord:
case rl::BackwardWord:
case rl::ForwardBigword:
case rl::BackwardBigword:
case rl::NextdOrForwardWord:
case rl::PrevdOrBackwardWord:
case rl::DeleteChar:
case rl::BackwardDeleteChar:
case rl::KillLine:
case rl::Yank:
case rl::YankPop:
case rl::BackwardKillLine:
case rl::KillWholeLine:
case rl::KillInnerLine:
case rl::KillWord:
case rl::KillBigword:
case rl::BackwardKillWord:
case rl::BackwardKillPathComponent:
case rl::BackwardKillBigword:
case rl::SelfInsert:
case rl::SelfInsertNotFirst:
case rl::TransposeChars:
case rl::TransposeWords:
case rl::UpcaseWord:
case rl::DowncaseWord:
case rl::CapitalizeWord:
case rl::BeginningOfBuffer:
case rl::EndOfBuffer:
case rl::Undo:
case rl::Redo:
// These commands operate on the search field if that's where the focus is.
return !focused_on_search_field;
default:
@ -1632,16 +1632,16 @@ static bool command_ends_paging(readline_cmd_t c, bool focused_on_search_field)
/// Indicates if the given command ends the history search.
static bool command_ends_history_search(readline_cmd_t c) {
switch (c) {
case readline_cmd_t::history_prefix_search_backward:
case readline_cmd_t::history_prefix_search_forward:
case readline_cmd_t::history_search_backward:
case readline_cmd_t::history_search_forward:
case readline_cmd_t::history_token_search_backward:
case readline_cmd_t::history_token_search_forward:
case readline_cmd_t::beginning_of_history:
case readline_cmd_t::end_of_history:
case readline_cmd_t::repaint:
case readline_cmd_t::force_repaint:
case readline_cmd_t::HistoryPrefixSearchBackward:
case readline_cmd_t::HistoryPrefixSearchForward:
case readline_cmd_t::HistorySearchBackward:
case readline_cmd_t::HistorySearchForward:
case readline_cmd_t::HistoryTokenSearchBackward:
case readline_cmd_t::HistoryTokenSearchForward:
case readline_cmd_t::BeginningOfHistory:
case readline_cmd_t::EndOfHistory:
case readline_cmd_t::Repaint:
case readline_cmd_t::ForceRepaint:
return false;
default:
return true;
@ -2833,7 +2833,7 @@ void reader_set_autosuggestion_enabled(const env_stack_t &vars) {
if (data->conf.autosuggest_ok != enable) {
data->conf.autosuggest_ok = enable;
data->force_exec_prompt_and_repaint = true;
data->inputter.queue_char(readline_cmd_t::repaint);
data->inputter->queue_readline(readline_cmd_t::Repaint);
}
}
}
@ -2845,7 +2845,7 @@ void reader_set_autosuggestion_enabled_ffi(bool enable) {
if (data->conf.autosuggest_ok != enable) {
data->conf.autosuggest_ok = enable;
data->force_exec_prompt_and_repaint = true;
data->inputter.queue_char(readline_cmd_t::repaint);
data->inputter->queue_readline(readline_cmd_t::Repaint);
}
}
}
@ -2992,7 +2992,7 @@ void reader_data_t::apply_commandline_state_changes() {
}
void reader_data_t::compute_and_apply_completions(readline_cmd_t c, readline_loop_state_t &rls) {
assert((c == readline_cmd_t::complete || c == readline_cmd_t::complete_and_search) &&
assert((c == readline_cmd_t::Complete || c == readline_cmd_t::CompleteAndSearch) &&
"Invalid command");
editable_line_t *el = &command_line;
@ -3074,7 +3074,7 @@ void reader_data_t::compute_and_apply_completions(readline_cmd_t c, readline_loo
rls.complete_did_insert = handle_completions(*rls.comp, token_begin - buff, token_end - buff);
// Show the search field if requested and if we printed a list of completions.
if (c == readline_cmd_t::complete_and_search && !rls.complete_did_insert && !pager.empty()) {
if (c == readline_cmd_t::CompleteAndSearch && !rls.complete_did_insert && !pager.empty()) {
pager.set_search_field_shown(true);
select_completion_in_direction(selection_motion_t::next);
}
@ -3250,16 +3250,16 @@ void reader_data_t::run_input_command_scripts(const std::vector<wcstring> &cmds)
/// Read normal characters, inserting them into the command line.
/// \return the next unhandled event.
maybe_t<char_event_t> reader_data_t::read_normal_chars(readline_loop_state_t &rls) {
maybe_t<char_event_t> event_needing_handling{};
maybe_t<rust::Box<char_event_t>> reader_data_t::read_normal_chars(readline_loop_state_t &rls) {
maybe_t<rust::Box<char_event_t>> event_needing_handling{};
wcstring accumulated_chars;
size_t limit = std::min(rls.nchars - command_line.size(), READAHEAD_MAX);
using command_handler_t = inputter_t::command_handler_t;
command_handler_t normal_handler = [this](const std::vector<wcstring> &cmds) {
this->run_input_command_scripts(cmds);
};
command_handler_t empty_handler = {};
shared_ptr<callback_t> normal_handler_ffi =
std::make_shared<callback_t>([this](const void *param) -> void * {
const auto *list = static_cast<const wcstring_list_ffi_t *>(param);
this->run_input_command_scripts(list->vals);
return nullptr;
});
// 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
@ -3267,16 +3267,16 @@ maybe_t<char_event_t> reader_data_t::read_normal_chars(readline_loop_state_t &rl
uint64_t last_exec_count = exec_count();
while (accumulated_chars.size() < limit) {
bool allow_commands = (accumulated_chars.empty());
auto evt = inputter.read_char(allow_commands ? normal_handler : empty_handler);
if (!event_is_normal_char(evt) || !poll_fd_readable(conf.in)) {
auto evt = inputter->read_char(allow_commands ? normal_handler_ffi : nullptr);
if (!event_is_normal_char(*evt) || !poll_fd_readable(conf.in)) {
event_needing_handling = std::move(evt);
break;
} else if (evt.input_style == char_input_style_t::notfirst && accumulated_chars.empty() &&
active_edit_line()->position() == 0) {
} else if (evt->get_input_style() == char_input_style_t::NotFirst &&
accumulated_chars.empty() && active_edit_line()->position() == 0) {
// The cursor is at the beginning and nothing is accumulated, so skip this character.
continue;
} else {
accumulated_chars.push_back(evt.get_char());
accumulated_chars.push_back(evt->get_char());
}
if (last_exec_count != exec_count()) {
@ -3312,14 +3312,14 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
using rl = readline_cmd_t;
switch (c) {
// Go to beginning of line.
case rl::beginning_of_line: {
case rl::BeginningOfLine: {
editable_line_t *el = active_edit_line();
while (el->position() > 0 && el->text()->at(el->position() - 1) != L'\n') {
update_buff_pos(el, el->position() - 1);
}
break;
}
case rl::end_of_line: {
case rl::EndOfLine: {
editable_line_t *el = active_edit_line();
if (el->position() < el->size()) {
auto text = *el->text();
@ -3332,15 +3332,15 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
case rl::beginning_of_buffer: {
case rl::BeginningOfBuffer: {
update_buff_pos(&command_line, 0);
break;
}
case rl::end_of_buffer: {
case rl::EndOfBuffer: {
update_buff_pos(&command_line, command_line.size());
break;
}
case rl::cancel_commandline: {
case rl::CancelCommandline: {
if (!command_line.empty()) {
outputter_t &outp = stdoutput();
// Move cursor to the end of the line.
@ -3367,7 +3367,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
case rl::cancel: {
case rl::Cancel: {
// If we last inserted a completion, undo it.
// This doesn't apply if the completion was selected via the pager
// (in which case the last command is "execute" or similar,
@ -3375,14 +3375,14 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
//
// Also paging is already cancelled above.
if (rls.complete_did_insert &&
(rls.last_cmd == rl::complete || rls.last_cmd == rl::complete_and_search)) {
(rls.last_cmd == rl::Complete || rls.last_cmd == rl::CompleteAndSearch)) {
editable_line_t *el = active_edit_line();
el->undo();
update_buff_pos(el);
}
break;
}
case rl::repaint_mode: {
case rl::RepaintMode: {
// Repaint the mode-prompt only if possible.
// This is an optimization basically exclusively for vi-mode, since the prompt
// may sometimes take a while but when switching the mode all we care about is the
@ -3408,8 +3408,8 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
// Else we repaint as normal.
__fallthrough__
}
case rl::force_repaint:
case rl::repaint: {
case rl::ForceRepaint:
case rl::Repaint: {
parser().libdata_pods_mut().is_repaint = true;
exec_prompt();
screen->reset_line(true /* redraw prompt */);
@ -3418,17 +3418,17 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
parser().libdata_pods_mut().is_repaint = false;
break;
}
case rl::complete:
case rl::complete_and_search: {
case rl::Complete:
case rl::CompleteAndSearch: {
if (!conf.complete_ok) break;
if (is_navigating_pager_contents() ||
(!rls.comp->empty() && !rls.complete_did_insert && rls.last_cmd == rl::complete)) {
(!rls.comp->empty() && !rls.complete_did_insert && rls.last_cmd == rl::Complete)) {
// The user typed complete more than once in a row. If we are not yet fully
// disclosed, then become so; otherwise cycle through our available completions.
if (current_page_rendering->remaining_to_disclose() > 0) {
pager.set_fully_disclosed();
} else {
select_completion_in_direction(c == rl::complete ? selection_motion_t::next
select_completion_in_direction(c == rl::Complete ? selection_motion_t::next
: selection_motion_t::prev);
}
} else {
@ -3437,7 +3437,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
case rl::pager_toggle_search: {
case rl::PagerToggleSearch: {
if (history_pager_active) {
fill_history_pager(history_pager_invocation_t::Advance,
history_search_direction_t::Forward);
@ -3454,7 +3454,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
case rl::kill_line: {
case rl::KillLine: {
editable_line_t *el = active_edit_line();
auto text = *el->text();
const wchar_t *buff = text.c_str();
@ -3467,11 +3467,11 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
size_t len = end - begin;
if (len) {
kill(el, begin - buff, len, KILL_APPEND, rls.last_cmd != rl::kill_line);
kill(el, begin - buff, len, KILL_APPEND, rls.last_cmd != rl::KillLine);
}
break;
}
case rl::backward_kill_line: {
case rl::BackwardKillLine: {
editable_line_t *el = active_edit_line();
if (el->position() == 0) {
break;
@ -3491,12 +3491,12 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
assert(end >= begin);
size_t len = std::max<size_t>(end - begin, 1);
begin = end - len;
kill(el, begin - buff, len, KILL_PREPEND, rls.last_cmd != rl::backward_kill_line);
kill(el, begin - buff, len, KILL_PREPEND, rls.last_cmd != rl::BackwardKillLine);
break;
}
case rl::kill_whole_line: // We match the emacs behavior here: "kills the entire line
case rl::KillWholeLine: // We match the emacs behavior here: "kills the entire line
// including the following newline".
case rl::kill_inner_line: // Do not kill the following newline
case rl::KillInnerLine: // Do not kill the following newline
{
editable_line_t *el = active_edit_line();
auto text = *el->text();
@ -3515,7 +3515,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
size_t end = el->position();
for (;; end++) {
if (buff[end] == L'\0') {
if (c == rl::kill_whole_line && begin > 0) {
if (c == rl::KillWholeLine && begin > 0) {
// We are on the last line. Delete the newline in the beginning to clear
// this line.
begin--;
@ -3523,7 +3523,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
break;
}
if (buff[end] == L'\n') {
if (c == rl::kill_whole_line) {
if (c == rl::KillWholeLine) {
end++;
}
break;
@ -3537,13 +3537,13 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
case rl::yank: {
case rl::Yank: {
wcstring yank_str = std::move(*kill_yank());
insert_string(active_edit_line(), yank_str);
rls.yank_len = yank_str.size();
break;
}
case rl::yank_pop: {
case rl::YankPop: {
if (rls.yank_len) {
editable_line_t *el = active_edit_line();
wcstring yank_str = std::move(*kill_yank_rotate());
@ -3556,25 +3556,25 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
case rl::backward_delete_char: {
case rl::BackwardDeleteChar: {
delete_char();
break;
}
case rl::exit: {
case rl::Exit: {
// This is by definition a successful exit, override the status
parser().set_last_statuses(*statuses_just(STATUS_CMD_OK));
exit_loop_requested = true;
check_exit_loop_maybe_warning(this);
break;
}
case rl::delete_or_exit:
case rl::delete_char: {
case rl::DeleteOrExit:
case rl::DeleteChar: {
// Remove the current character in the character buffer and on the screen using
// syntax highlighting, etc.
editable_line_t *el = active_edit_line();
if (el->position() < el->size()) {
delete_char(false /* backward */);
} else if (c == rl::delete_or_exit && el->empty()) {
} else if (c == rl::DeleteOrExit && el->empty()) {
// This is by definition a successful exit, override the status
parser().set_last_statuses(*statuses_just(STATUS_CMD_OK));
exit_loop_requested = true;
@ -3582,7 +3582,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
case rl::execute: {
case rl::Execute: {
if (!this->handle_execute(rls)) {
event_fire_generic(parser(), L"fish_posterror", {*command_line.text()});
screen->reset_abandoning_line(termsize_last().width);
@ -3590,17 +3590,16 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
break;
}
case rl::history_prefix_search_backward:
case rl::history_prefix_search_forward:
case rl::history_search_backward:
case rl::history_search_forward:
case rl::history_token_search_backward:
case rl::history_token_search_forward: {
case rl::HistoryPrefixSearchBackward:
case rl::HistoryPrefixSearchForward:
case rl::HistorySearchBackward:
case rl::HistorySearchForward:
case rl::HistoryTokenSearchBackward:
case rl::HistoryTokenSearchForward: {
reader_history_search_t::mode_t mode =
(c == rl::history_token_search_backward || c == rl::history_token_search_forward)
(c == rl::HistoryTokenSearchBackward || c == rl::HistoryTokenSearchForward)
? reader_history_search_t::token
: (c == rl::history_prefix_search_backward ||
c == rl::history_prefix_search_forward)
: (c == rl::HistoryPrefixSearchBackward || c == rl::HistoryPrefixSearchForward)
? reader_history_search_t::prefix
: reader_history_search_t::line;
@ -3636,8 +3635,8 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
if (history_search.active()) {
history_search_direction_t dir =
(c == rl::history_search_backward || c == rl::history_token_search_backward ||
c == rl::history_prefix_search_backward)
(c == rl::HistorySearchBackward || c == rl::HistoryTokenSearchBackward ||
c == rl::HistoryPrefixSearchBackward)
? history_search_direction_t::Backward
: history_search_direction_t::Forward;
bool found = history_search.move_in_direction(dir);
@ -3656,7 +3655,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
case rl::history_pager: {
case rl::HistoryPager: {
if (history_pager_active) {
fill_history_pager(history_pager_invocation_t::Advance,
history_search_direction_t::Backward);
@ -3685,12 +3684,12 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
case rl::history_pager_delete: {
case rl::HistoryPagerDelete: {
if (!history_pager_active) {
inputter.function_set_status(false);
inputter->function_set_status(false);
break;
}
inputter.function_set_status(true);
inputter->function_set_status(true);
if (auto completion = pager.selected_completion(*current_page_rendering)) {
(*history)->remove(*completion->completion());
(*history)->save();
@ -3699,7 +3698,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
case rl::backward_char: {
case rl::BackwardChar: {
editable_line_t *el = active_edit_line();
if (is_navigating_pager_contents()) {
select_completion_in_direction(selection_motion_t::west);
@ -3708,7 +3707,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
case rl::forward_char: {
case rl::ForwardChar: {
editable_line_t *el = active_edit_line();
if (is_navigating_pager_contents()) {
select_completion_in_direction(selection_motion_t::east);
@ -3719,7 +3718,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
case rl::forward_single_char: {
case rl::ForwardSingleChar: {
editable_line_t *el = active_edit_line();
if (is_navigating_pager_contents()) {
select_completion_in_direction(selection_motion_t::east);
@ -3730,61 +3729,61 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
case rl::backward_kill_word:
case rl::backward_kill_path_component:
case rl::backward_kill_bigword: {
case rl::BackwardKillWord:
case rl::BackwardKillPathComponent:
case rl::BackwardKillBigword: {
move_word_style_t style =
(c == rl::backward_kill_bigword ? move_word_style_t::Whitespace
: c == rl::backward_kill_path_component ? move_word_style_t::PathComponents
(c == rl::BackwardKillBigword ? move_word_style_t::Whitespace
: c == rl::BackwardKillPathComponent ? move_word_style_t::PathComponents
: move_word_style_t::Punctuation);
// Is this the same killring item as the last kill?
bool newv = (rls.last_cmd != rl::backward_kill_word &&
rls.last_cmd != rl::backward_kill_path_component &&
rls.last_cmd != rl::backward_kill_bigword);
bool newv = (rls.last_cmd != rl::BackwardKillWord &&
rls.last_cmd != rl::BackwardKillPathComponent &&
rls.last_cmd != rl::BackwardKillBigword);
move_word(active_edit_line(), MOVE_DIR_LEFT, true /* erase */, style, newv);
break;
}
case rl::kill_word:
case rl::kill_bigword: {
case rl::KillWord:
case rl::KillBigword: {
// The "bigword" functions differ only in that they move to the next whitespace, not
// punctuation.
auto move_style = (c == rl::kill_word) ? move_word_style_t::Punctuation
auto move_style = (c == rl::KillWord) ? move_word_style_t::Punctuation
: move_word_style_t::Whitespace;
move_word(active_edit_line(), MOVE_DIR_RIGHT, true /* erase */, move_style,
rls.last_cmd != c /* same kill item if same movement */);
break;
}
case rl::backward_word:
case rl::backward_bigword:
case rl::prevd_or_backward_word: {
if (c == rl::prevd_or_backward_word && command_line.empty()) {
case rl::BackwardWord:
case rl::BackwardBigword:
case rl::PrevdOrBackwardWord: {
if (c == rl::PrevdOrBackwardWord && command_line.empty()) {
auto last_statuses = parser().vars().get_last_statuses();
(void)parser().eval(L"prevd", *new_io_chain());
parser().set_last_statuses(*std::move(last_statuses));
force_exec_prompt_and_repaint = true;
inputter.queue_char(readline_cmd_t::repaint);
inputter->queue_readline(readline_cmd_t::Repaint);
break;
}
auto move_style = (c != rl::backward_bigword) ? move_word_style_t::Punctuation
auto move_style = (c != rl::BackwardBigword) ? move_word_style_t::Punctuation
: move_word_style_t::Whitespace;
move_word(active_edit_line(), MOVE_DIR_LEFT, false /* do not erase */, move_style,
false);
break;
}
case rl::forward_word:
case rl::forward_bigword:
case rl::nextd_or_forward_word: {
if (c == rl::nextd_or_forward_word && command_line.empty()) {
case rl::ForwardWord:
case rl::ForwardBigword:
case rl::NextdOrForwardWord: {
if (c == rl::NextdOrForwardWord && command_line.empty()) {
auto last_statuses = parser().vars().get_last_statuses();
(void)parser().eval(L"nextd", *new_io_chain());
parser().set_last_statuses(*std::move(last_statuses));
force_exec_prompt_and_repaint = true;
inputter.queue_char(readline_cmd_t::repaint);
inputter->queue_readline(readline_cmd_t::Repaint);
break;
}
auto move_style = (c != rl::forward_bigword) ? move_word_style_t::Punctuation
auto move_style = (c != rl::ForwardBigword) ? move_word_style_t::Punctuation
: move_word_style_t::Whitespace;
editable_line_t *el = active_edit_line();
if (el->position() < el->size()) {
@ -3794,9 +3793,9 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
case rl::beginning_of_history:
case rl::end_of_history: {
bool up = (c == rl::beginning_of_history);
case rl::BeginningOfHistory:
case rl::EndOfHistory: {
bool up = (c == rl::BeginningOfHistory);
if (is_navigating_pager_contents()) {
select_completion_in_direction(up ? selection_motion_t::page_north
: selection_motion_t::page_south);
@ -3812,12 +3811,12 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
case rl::up_line:
case rl::down_line: {
case rl::UpLine:
case rl::DownLine: {
if (is_navigating_pager_contents()) {
// We are already navigating pager contents.
selection_motion_t direction;
if (c == rl::down_line) {
if (c == rl::DownLine) {
// Down arrow is always south.
direction = selection_motion_t::south;
} else if (selection_is_at_top(this)) {
@ -3832,7 +3831,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
select_completion_in_direction(direction);
} else if (!pager.empty()) {
// We pressed a direction with a non-empty pager, begin navigation.
select_completion_in_direction(c == rl::down_line ? selection_motion_t::south
select_completion_in_direction(c == rl::DownLine ? selection_motion_t::south
: selection_motion_t::north);
} else {
// Not navigating the pager contents.
@ -3840,7 +3839,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
int line_old = parse_util_get_line_from_offset(*el->text(), el->position());
int line_new;
if (c == rl::up_line)
if (c == rl::UpLine)
line_new = line_old - 1;
else
line_new = line_old + 1;
@ -3865,19 +3864,19 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
case rl::suppress_autosuggestion: {
case rl::SuppressAutosuggestion: {
suppress_autosuggestion = true;
bool success = !autosuggestion.empty();
autosuggestion.clear();
// Return true if we had a suggestion to clear.
inputter.function_set_status(success);
inputter->function_set_status(success);
break;
}
case rl::accept_autosuggestion: {
case rl::AcceptAutosuggestion: {
accept_autosuggestion(true);
break;
}
case rl::transpose_chars: {
case rl::TransposeChars: {
editable_line_t *el = active_edit_line();
if (el->size() < 2) {
break;
@ -3897,7 +3896,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
case rl::transpose_words: {
case rl::TransposeWords: {
editable_line_t *el = active_edit_line();
size_t len = el->size();
auto text = *el->text();
@ -3938,7 +3937,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
case rl::togglecase_char: {
case rl::TogglecaseChar: {
editable_line_t *el = active_edit_line();
size_t buff_pos = el->position();
@ -3964,7 +3963,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
case rl::togglecase_selection: {
case rl::TogglecaseSelection: {
editable_line_t *el = active_edit_line();
// Check that we have an active selection and get the bounds.
@ -3998,9 +3997,9 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
case rl::upcase_word:
case rl::downcase_word:
case rl::capitalize_word: {
case rl::UpcaseWord:
case rl::DowncaseWord:
case rl::CapitalizeWord: {
editable_line_t *el = active_edit_line();
// For capitalize_word, whether we've capitalized a character so far.
bool capitalized_first = false;
@ -4016,10 +4015,10 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
// We always change the case; this decides whether we go uppercase (true) or
// lowercase (false).
bool make_uppercase;
if (c == rl::capitalize_word)
if (c == rl::CapitalizeWord)
make_uppercase = !capitalized_first && iswalnum(chr);
else
make_uppercase = (c == rl::upcase_word);
make_uppercase = (c == rl::UpcaseWord);
// Apply the operation and then record what we did.
if (make_uppercase)
@ -4035,7 +4034,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
break;
}
case rl::begin_selection: {
case rl::BeginSelection: {
if (!selection) selection = selection_data_t{};
size_t pos = command_line.position();
selection->begin = pos;
@ -4045,12 +4044,12 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
break;
}
case rl::end_selection: {
case rl::EndSelection: {
selection.reset();
break;
}
case rl::swap_selection_start_stop: {
case rl::SwapSelectionStartStop: {
if (!selection) break;
size_t tmp = selection->begin;
selection->begin = command_line.position();
@ -4060,14 +4059,14 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
break;
}
case rl::kill_selection: {
bool newv = (rls.last_cmd != rl::kill_selection);
case rl::KillSelection: {
bool newv = (rls.last_cmd != rl::KillSelection);
if (auto selection = this->get_selection()) {
kill(&command_line, selection->start, selection->length, KILL_APPEND, newv);
}
break;
}
case rl::insert_line_over: {
case rl::InsertLineOver: {
editable_line_t *el = active_edit_line();
while (el->position() > 0 && el->text()->at(el->position() - 1) != L'\n') {
update_buff_pos(el, el->position() - 1);
@ -4076,7 +4075,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
update_buff_pos(el, el->position() - 1);
break;
}
case rl::insert_line_under: {
case rl::InsertLineUnder: {
editable_line_t *el = active_edit_line();
if (el->position() < el->size()) {
auto text = *el->text();
@ -4088,24 +4087,24 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
insert_char(el, L'\n');
break;
}
case rl::forward_jump:
case rl::backward_jump:
case rl::forward_jump_till:
case rl::backward_jump_till: {
auto direction = (c == rl::forward_jump || c == rl::forward_jump_till)
case rl::ForwardJump:
case rl::BackwardJump:
case rl::ForwardJumpTill:
case rl::BackwardJumpTill: {
auto direction = (c == rl::ForwardJump || c == rl::ForwardJumpTill)
? jump_direction_t::forward
: jump_direction_t::backward;
auto precision = (c == rl::forward_jump || c == rl::backward_jump)
auto precision = (c == rl::ForwardJump || c == rl::BackwardJump)
? jump_precision_t::to
: jump_precision_t::till;
editable_line_t *el = active_edit_line();
wchar_t target = inputter.function_pop_arg();
wchar_t target = inputter->function_pop_arg();
bool success = jump(direction, precision, el, target);
inputter.function_set_status(success);
inputter->function_set_status(success);
break;
}
case rl::repeat_jump: {
case rl::RepeatJump: {
editable_line_t *el = active_edit_line();
bool success = false;
@ -4113,10 +4112,10 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
success = jump(last_jump_direction, last_jump_precision, el, last_jump_target);
}
inputter.function_set_status(success);
inputter->function_set_status(success);
break;
}
case rl::reverse_repeat_jump: {
case rl::ReverseRepeatJump: {
editable_line_t *el = active_edit_line();
bool success = false;
jump_direction_t original_dir, dir;
@ -4134,22 +4133,22 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
last_jump_direction = original_dir;
inputter.function_set_status(success);
inputter->function_set_status(success);
break;
}
case rl::expand_abbr: {
case rl::ExpandAbbr: {
if (expand_abbreviation_at_cursor(1)) {
inputter.function_set_status(true);
inputter->function_set_status(true);
} else {
inputter.function_set_status(false);
inputter->function_set_status(false);
}
break;
}
case rl::undo:
case rl::redo: {
case rl::Undo:
case rl::Redo: {
editable_line_t *el = active_edit_line();
bool ok = (c == rl::undo) ? el->undo() : el->redo();
bool ok = (c == rl::Undo) ? el->undo() : el->redo();
if (ok) {
if (el == &command_line) {
clear_pager();
@ -4161,22 +4160,22 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
case rl::begin_undo_group: {
case rl::BeginUndoGroup: {
editable_line_t *el = active_edit_line();
el->begin_edit_group();
break;
}
case rl::end_undo_group: {
case rl::EndUndoGroup: {
editable_line_t *el = active_edit_line();
el->end_edit_group();
break;
}
case rl::disable_mouse_tracking: {
case rl::DisableMouseTracking: {
outputter_t &outp = stdoutput();
outp.writestr(L"\x1B[?1000l");
break;
}
case rl::clear_screen_and_repaint: {
case rl::ClearScreenAndRepaint: {
parser().libdata_pods_mut().is_repaint = true;
auto clear = *screen_clear();
if (!clear.empty()) {
@ -4198,10 +4197,10 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
break;
}
// Some commands should have been handled internally by inputter_t::readch().
case rl::self_insert:
case rl::self_insert_notfirst:
case rl::func_or:
case rl::func_and: {
case rl::SelfInsert:
case rl::SelfInsertNotFirst:
case rl::FuncOr:
case rl::FuncAnd: {
DIE("should have been handled by inputter_t::readch");
}
}
@ -4422,13 +4421,13 @@ maybe_t<wcstring> reader_data_t::readline(int nchars_or_0) {
break;
}
maybe_t<char_event_t> event_needing_handling{};
maybe_t<rust::Box<char_event_t>> maybe_event_needing_handling{};
while (true) {
event_needing_handling = read_normal_chars(rls);
if (event_needing_handling.has_value()) break;
maybe_event_needing_handling = read_normal_chars(rls);
if (maybe_event_needing_handling.has_value()) break;
if (rls.nchars <= command_line.size()) {
event_needing_handling.reset();
maybe_event_needing_handling.reset();
break;
}
}
@ -4438,22 +4437,23 @@ maybe_t<wcstring> reader_data_t::readline(int nchars_or_0) {
parser().libdata_pods_mut().exit_current_script = false;
if (exit_loop_requested) continue;
if (!event_needing_handling || event_needing_handling->is_check_exit()) {
if (!maybe_event_needing_handling || (*maybe_event_needing_handling)->is_check_exit()) {
continue;
} else if (event_needing_handling->is_eof()) {
} else if ((*maybe_event_needing_handling)->is_eof()) {
reader_sighup();
continue;
}
auto event_needing_handling = maybe_event_needing_handling.acquire();
assert((event_needing_handling->is_char() || event_needing_handling->is_readline()) &&
"Should have a char or readline");
if (rls.last_cmd != rl::yank && rls.last_cmd != rl::yank_pop) {
if (rls.last_cmd != rl::Yank && rls.last_cmd != rl::YankPop) {
rls.yank_len = 0;
}
if (event_needing_handling->is_readline()) {
readline_cmd_t readline_cmd = event_needing_handling->get_readline();
if (readline_cmd == rl::cancel && is_navigating_pager_contents()) {
if (readline_cmd == rl::Cancel && is_navigating_pager_contents()) {
clear_transient_edit();
}
@ -4469,7 +4469,7 @@ maybe_t<wcstring> reader_data_t::readline(int nchars_or_0) {
if (history_search.active() && command_ends_history_search(readline_cmd)) {
// "cancel" means to abort the whole thing, other ending commands mean to finish the
// search.
if (readline_cmd == rl::cancel) {
if (readline_cmd == rl::Cancel) {
// Go back to the search string by simply undoing the history-search edit.
clear_transient_edit();
}
@ -4481,7 +4481,7 @@ maybe_t<wcstring> reader_data_t::readline(int nchars_or_0) {
} else {
// Ordinary char.
wchar_t c = event_needing_handling->get_char();
if (event_needing_handling->input_style == char_input_style_t::notfirst &&
if (event_needing_handling->get_input_style() == char_input_style_t::NotFirst &&
active_edit_line()->position() == 0) {
// This character is skipped.
} else if (!fish_reserved_codepoint(c) && (c >= L' ' || c == L'\n' || c == L'\r') &&
@ -4618,7 +4618,7 @@ void reader_schedule_prompt_repaint() {
reader_data_t *data = current_data_or_null();
if (data && !data->force_exec_prompt_and_repaint) {
data->force_exec_prompt_and_repaint = true;
data->inputter.queue_char(readline_cmd_t::repaint);
data->inputter->queue_readline(readline_cmd_t::Repaint);
}
}
@ -4629,9 +4629,9 @@ void reader_handle_command(readline_cmd_t cmd) {
}
}
void reader_queue_ch(const char_event_t &ch) {
void reader_queue_ch(rust::Box<char_event_t> ch) {
if (reader_data_t *data = current_data_or_null()) {
data->inputter.queue_char(ch);
data->inputter->queue_char(std::move(ch));
}
}

View File

@ -20,6 +20,7 @@
#include "maybe.h"
#include "parse_constants.h"
#include "parser.h"
#include "wutil.h"
#if INCLUDE_RUST_HEADERS
#include "reader.rs.h"
@ -79,8 +80,9 @@ void reader_write_title_ffi(const wcstring &cmd, const void *parser, bool reset_
void reader_schedule_prompt_repaint();
/// Enqueue an event to the back of the reader's input queue.
class char_event_t;
void reader_queue_ch(const char_event_t &ch);
struct CharEvent;
using char_event_t = CharEvent;
void reader_queue_ch(rust::Box<char_event_t> ch);
/// Return the value of the interrupted flag, which is set by the sigint handler, and clear it if it
/// was set. If the current reader is interruptible, call \c reader_exit().