From 8190e3419dd1826a294b37115e01d36c357c9b9b Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Sun, 17 Dec 2023 15:41:14 -0800 Subject: [PATCH] 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. --- CMakeLists.txt | 3 - fish-rust/build.rs | 1 + fish-rust/src/builtins/bind.rs | 562 +++++++++++++++++- fish-rust/src/env/environment.rs | 3 +- fish-rust/src/ffi.rs | 8 - fish-rust/src/input_common.rs | 104 +--- fish-rust/src/input_ffi.rs | 267 +++++++++ fish-rust/src/lib.rs | 1 + fish-rust/src/threads.rs | 1 + src/builtins/bind.cpp | 521 ----------------- src/builtins/bind.h | 14 - src/builtins/commandline.cpp | 15 +- src/ffi_baggage.h | 4 - src/fish_key_reader.cpp | 20 +- src/fish_tests.cpp | 3 +- src/input.cpp | 973 ------------------------------- src/input.h | 162 ----- src/input_common.cpp | 341 ----------- src/input_common.h | 263 --------- src/parser.h | 1 + src/reader.cpp | 464 +++++++-------- src/reader.h | 6 +- 22 files changed, 1092 insertions(+), 2645 deletions(-) create mode 100644 fish-rust/src/input_ffi.rs delete mode 100644 src/builtins/bind.cpp delete mode 100644 src/builtins/bind.h delete mode 100644 src/input.cpp delete mode 100644 src/input.h delete mode 100644 src/input_common.cpp delete mode 100644 src/input_common.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 2589d1ac0..d309111e6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/fish-rust/build.rs b/fish-rust/build.rs index b64de9e68..3d8e968bf 100644 --- a/fish-rust/build.rs +++ b/fish-rust/build.rs @@ -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", diff --git a/fish-rust/src/builtins/bind.rs b/fish-rust/src/builtins/bind.rs index e2daa34d8..7ec28e8b1 100644 --- a/fish-rust/src/builtins/bind.rs +++ b/fish-rust/src/builtins/bind.rs @@ -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 { + 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 = 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 { + 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 { + 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 { - run_builtin_ffi(crate::ffi::builtin_bind, parser, streams, args) + BuiltinBind::new().bind(parser, streams, args) } diff --git a/fish-rust/src/env/environment.rs b/fish-rust/src/env/environment.rs index 8da18c3a0..296d7aec0 100644 --- a/fish-rust/src/env/environment.rs +++ b/fish-rust/src/env/environment.rs @@ -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). diff --git a/fish-rust/src/ffi.rs b/fish-rust/src/ffi.rs index d97c5c978..09d25cb4d 100644 --- a/fish-rust/src/ffi.rs +++ b/fish-rust/src/ffi.rs @@ -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. diff --git a/fish-rust/src/input_common.rs b/fish-rust/src/input_common.rs index 414e4a615..0f568ddf5 100644 --- a/fish-rust/src/input_common.rs +++ b/fish-rust/src/input_common.rs @@ -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, diff --git a/fish-rust/src/input_ffi.rs b/fish-rust/src/input_ffi.rs new file mode 100644 index 000000000..8425ccad5 --- /dev/null +++ b/fish-rust/src/input_ffi.rs @@ -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 { + Box::new(CharEvent::from_readline(cmd)) +} + +fn char_event_from_char_ffi(c: u8) -> Box { + Box::new(CharEvent::from_char(c.into())) +} + +fn make_inputter_ffi(parser: &ParserRefFFI, in_fd: i32) -> Box { + Box::new(Inputter::new(parser.0.clone(), in_fd)) +} + +fn make_input_event_queue_ffi(in_fd: i32) -> Box { + 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) { + self.queue_char(*ch); + } + + fn read_char_ffi(&mut self, command_handler: &cxx::SharedPtr) -> Box { + 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::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; + + #[cxx_name = "char_event_from_char"] + fn char_event_from_char_ffi(c: u8) -> Box; + + 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; + + #[cxx_name = "queue_char"] + fn queue_char_ffi(&mut self, ch: Box); + + fn queue_readline(&mut self, cmd: ReadlineCmd); + + #[cxx_name = "read_char"] + fn read_char_ffi(&mut self, command_handler: &SharedPtr) -> Box; + + 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; + + #[cxx_name = "readch_timed_esc"] + fn readch_timed_esc_ffi(&mut self) -> *mut CharEvent; + } +} diff --git a/fish-rust/src/lib.rs b/fish-rust/src/lib.rs index b27b66e0e..d8fef4378 100644 --- a/fish-rust/src/lib.rs +++ b/fish-rust/src/lib.rs @@ -71,6 +71,7 @@ mod highlight; mod history; mod input; mod input_common; +mod input_ffi; mod io; mod job_group; mod kill; diff --git a/fish-rust/src/threads.rs b/fish-rust/src/threads.rs index 23cd24fd7..82a5e4c75 100644 --- a/fish-rust/src/threads.rs +++ b/fish-rust/src/threads.rs @@ -102,6 +102,7 @@ mod ffi { } } +pub use ffi::CppCallback; unsafe impl Send for ffi::CppCallback {} unsafe impl Sync for ffi::CppCallback {} diff --git a/src/builtins/bind.cpp b/src/builtins/bind.cpp deleted file mode 100644 index 53231e552..000000000 --- a/src/builtins/bind.cpp +++ /dev/null @@ -1,521 +0,0 @@ -// Implementation of the bind builtin. -#include "config.h" // IWYU pragma: keep - -#include "bind.h" - -#include - -#include -#include -#include -#include - -#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 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_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 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 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 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 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 lst = input_mappings_->get_names(true); - const std::vector 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 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 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(_parser); - auto &streams = *static_cast(_streams); - auto argv = static_cast(_argv); - builtin_bind_t bind; - return *bind.builtin_bind(parser, streams, argv); -} diff --git a/src/builtins/bind.h b/src/builtins/bind.h deleted file mode 100644 index b30aaa764..000000000 --- a/src/builtins/bind.h +++ /dev/null @@ -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 diff --git a/src/builtins/commandline.cpp b/src/builtins/commandline.cpp index 4585aff07..a79a6dd09 100644 --- a/src/builtins/commandline.cpp +++ b/src/builtins/commandline.cpp @@ -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(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( diff --git a/src/ffi_baggage.h b/src/ffi_baggage.h index f4bc590f1..037acc4d8 100644 --- a/src/ffi_baggage.h +++ b/src/ffi_baggage.h @@ -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({}, {}, {}); } diff --git a/src/fish_key_reader.cpp b/src/fish_key_reader.cpp index 1e0bc4698..6e136cbdf 100644 --- a/src/fish_key_reader.cpp +++ b/src/fish_key_reader.cpp @@ -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 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 bind_chars; std::fwprintf(stderr, L"Press a key:\n"); while (!check_exit_loop_maybe_warning(nullptr)) { - maybe_t evt{}; + maybe_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::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 diff --git a/src/fish_tests.cpp b/src/fish_tests.cpp index 764847294..d4cbe6619 100644 --- a/src/fish_tests.cpp +++ b/src/fish_tests.cpp @@ -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" diff --git a/src/input.cpp b/src/input.cpp deleted file mode 100644 index c995e0cd3..000000000 --- a/src/input.cpp +++ /dev/null @@ -1,973 +0,0 @@ -// Functions for reading a character of input from stdin. -#include "config.h" - -#include - -#if HAVE_TERM_H -#include // IWYU pragma: keep -#include -#elif HAVE_NCURSES_TERM_H -#include -#endif -#include - -#include -#include -#include -#include -#include - -#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 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 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 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_set_t::input_mapping_set_t() = default; -input_mapping_set_t::~input_mapping_set_t() = default; - -acquired_lock input_mappings() { - static owning_lock s_mappings{input_mapping_set_t()}; - return s_mappings.acquire(); -} - -/// Terminfo map list. -static latch_t> s_terminfo_mappings; - -/// \return the input terminfo. -static std::vector 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 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 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 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(*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_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 local_list = user ? mapping_list_ : preset_mapping_list_; - std::sort(local_list.begin(), local_list.end(), specification_order_is_less_than); - std::vector 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 *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 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(std::move(all_mappings)); - } - return all_mappings_cache_; -} - -/// Create a list of terminfo mappings. -static std::vector 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 input_terminfo_get_names(bool skip_null) { - assert(s_terminfo_mappings.is_set()); - std::vector 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 &input_function_get_names() { - // The list and names of input functions are hard-coded and never change - static std::vector result = ([&]() { - std::vector 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 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(); -} diff --git a/src/input.h b/src/input.h deleted file mode 100644 index a89367134..000000000 --- a/src/input.h +++ /dev/null @@ -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 -#include - -#include -#include -#include - -#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 &)>; - 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 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 parser_; -#endif - - std::vector input_function_args_{}; - bool function_status_{false}; - - // Transient storage to avoid repeated allocations. - std::vector 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_mappings(); - friend void init_input(); - - using mapping_list_t = std::vector; - - mapping_list_t mapping_list_; - mapping_list_t preset_mapping_list_; - std::shared_ptr 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 *out_cmds, - bool user, wcstring *out_sets_mode) const; - - /// Returns all mapping names and modes. - std::vector 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 all_mappings(); -}; - -/// Access the singleton input mapping set. -acquired_lock 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 input_terminfo_get_names(bool skip_null); - -/// Returns the input function code for the given input function name. -maybe_t input_function_get_code(const wcstring &name); - -/// Returns a list of all existing input function names. -const std::vector &input_function_get_names(void); - -#endif diff --git a/src/input_common.cpp b/src/input_common.cpp deleted file mode 100644 index 333501588..000000000 --- a/src/input_common.cpp +++ /dev/null @@ -1,341 +0,0 @@ -// Implementation file for the low level input library. -#include "config.h" - -#include -#include // IWYU pragma: keep -#include -#include -#include -#ifdef HAVE_SYS_SELECT_H -#include -#endif - -#include -#include -#include -#include -#include -#include -#include - -#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(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(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(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(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(tmp); - } -} - -maybe_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(static_cast(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(-1): - std::memset(&state, '\0', sizeof(state)); - FLOG(reader, L"Illegal input"); - return char_event_type_t::check_exit; - - case static_cast(-2): - // Sequence not yet complete. - break; - - case 0: - // Actual nul char. - return 0; - - default: - // Sequence complete. - return res; - } - break; - } - } - } -} - -maybe_t input_event_queue_t::readch_timed_esc() { - return readch_timed(wait_on_escape_ms); -} - -maybe_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 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; diff --git a/src/input_common.h b/src/input_common.h deleted file mode 100644 index 14ca14a58..000000000 --- a/src/input_common.h +++ /dev/null @@ -1,263 +0,0 @@ -// Header file for the low level input library. -#ifndef INPUT_COMMON_H -#define INPUT_COMMON_H - -#include - -#include -#include -#include -#include - -#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(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 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 readch_timed(const int wait_time_ms); - - maybe_t readch_timed_esc(); - maybe_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 - 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 try_pop(); - - int in_{0}; - std::deque queue_; -}; - -#endif diff --git a/src/parser.h b/src/parser.h index 8ad97071d..1cc609461 100644 --- a/src/parser.h +++ b/src/parser.h @@ -16,6 +16,7 @@ struct Parser; using parser_t = Parser; +struct ParserRef; #if INCLUDE_RUST_HEADERS #include "parser.rs.h" diff --git a/src/reader.cpp b/src/reader.cpp index afe45270e..2a8e70314 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -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 { rust::Box screen; /// The source of input events. - inputter_t inputter; + rust::Box inputter; /// The history. maybe_t> history{}; /// The history search. @@ -687,7 +687,7 @@ class reader_data_t : public std::enable_shared_from_this { 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 new_pos = none_t()); @@ -725,10 +725,10 @@ class reader_data_t : public std::enable_shared_from_this { bool newv); void run_input_command_scripts(const std::vector &cmds); - maybe_t read_normal_chars(readline_loop_state_t &rls); + maybe_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 &cmds) /// Read normal characters, inserting them into the command line. /// \return the next unhandled event. -maybe_t reader_data_t::read_normal_chars(readline_loop_state_t &rls) { - maybe_t event_needing_handling{}; +maybe_t> reader_data_t::read_normal_chars(readline_loop_state_t &rls) { + maybe_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 &cmds) { - this->run_input_command_scripts(cmds); - }; - command_handler_t empty_handler = {}; + shared_ptr normal_handler_ffi = + std::make_shared([this](const void *param) -> void * { + const auto *list = static_cast(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 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(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 - // including the following newline". - case rl::kill_inner_line: // Do not kill the following newline + case rl::KillWholeLine: // We match the emacs behavior here: "kills the entire line + // including 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,62 +3729,62 @@ 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 - : move_word_style_t::Punctuation); + (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 - : move_word_style_t::Whitespace; + 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 - : move_word_style_t::Whitespace; + 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 - : move_word_style_t::Whitespace; + 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()) { move_word(el, MOVE_DIR_RIGHT, false /* do not erase */, move_style, false); @@ -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,15 +3831,15 @@ 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 - : selection_motion_t::north); + select_completion_in_direction(c == rl::DownLine ? selection_motion_t::south + : selection_motion_t::north); } else { // Not navigating the pager contents. editable_line_t *el = active_edit_line(); 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 reader_data_t::readline(int nchars_or_0) { break; } - maybe_t event_needing_handling{}; + maybe_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 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 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 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 ch) { if (reader_data_t *data = current_data_or_null()) { - data->inputter.queue_char(ch); + data->inputter->queue_char(std::move(ch)); } } diff --git a/src/reader.h b/src/reader.h index 434c9f9ef..b1f8c1017 100644 --- a/src/reader.h +++ b/src/reader.h @@ -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 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().