mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-13 21:44:16 +00:00
Add remaining input FFI bits and port builtin_bind
This implements input and input_common FFI pieces in input_ffi.rs, and simultaneously ports bind.rs. This was done as a single commit because builtin_bind would have required a substantial amount of work to use the input ffi.
This commit is contained in:
parent
7ffb62d1d9
commit
8190e3419d
22 changed files with 1092 additions and 2645 deletions
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -1,6 +1,19 @@
|
|||
//! Implementation of the bind builtin.
|
||||
|
||||
use super::prelude::*;
|
||||
use crate::common::{
|
||||
escape, escape_string, str2wcstring, valid_var_name, EscapeFlags, EscapeStringStyle,
|
||||
};
|
||||
use crate::highlight::{colorize, highlight_shell};
|
||||
use crate::input::{
|
||||
input_function_get_names, input_mappings, input_terminfo_get_name, input_terminfo_get_names,
|
||||
input_terminfo_get_sequence, InputMappingSet,
|
||||
};
|
||||
use crate::nix::isatty;
|
||||
use nix::errno::Errno;
|
||||
use std::sync::MutexGuard;
|
||||
|
||||
const DEFAULT_BIND_MODE: &wstr = L!("default");
|
||||
|
||||
const BIND_INSERT: c_int = 0;
|
||||
const BIND_ERASE: c_int = 1;
|
||||
|
@ -19,10 +32,553 @@ struct Options {
|
|||
have_preset: bool,
|
||||
preset: bool,
|
||||
mode: c_int,
|
||||
bind_mode: &'static wstr,
|
||||
sets_bind_mode: &'static wstr,
|
||||
bind_mode: WString,
|
||||
sets_bind_mode: WString,
|
||||
}
|
||||
|
||||
impl Options {
|
||||
fn new() -> Options {
|
||||
Options {
|
||||
all: false,
|
||||
bind_mode_given: false,
|
||||
list_modes: false,
|
||||
print_help: false,
|
||||
silent: false,
|
||||
use_terminfo: false,
|
||||
have_user: false,
|
||||
user: false,
|
||||
have_preset: false,
|
||||
preset: false,
|
||||
mode: BIND_INSERT,
|
||||
bind_mode: DEFAULT_BIND_MODE.to_owned(),
|
||||
sets_bind_mode: WString::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct BuiltinBind {
|
||||
/// Note that BuiltinBind holds the singleton lock.
|
||||
/// It must not call out to anything which can execute fish shell code or attempt to acquire the
|
||||
/// lock again.
|
||||
input_mappings: MutexGuard<'static, InputMappingSet>,
|
||||
opts: Options,
|
||||
}
|
||||
|
||||
impl BuiltinBind {
|
||||
fn new() -> BuiltinBind {
|
||||
BuiltinBind {
|
||||
input_mappings: input_mappings(),
|
||||
opts: Options::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// List a single key binding.
|
||||
/// Returns false if no binding with that sequence and mode exists.
|
||||
fn list_one(
|
||||
&self,
|
||||
seq: &wstr,
|
||||
bind_mode: &wstr,
|
||||
user: bool,
|
||||
parser: &Parser,
|
||||
streams: &mut IoStreams,
|
||||
) -> bool {
|
||||
let mut ecmds = Vec::new();
|
||||
let mut sets_mode = WString::new();
|
||||
let mut out = WString::new();
|
||||
if !self
|
||||
.input_mappings
|
||||
.get(seq, bind_mode, &mut ecmds, user, &mut sets_mode)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
out.push_str("bind");
|
||||
|
||||
// Append the mode flags if applicable.
|
||||
if !user {
|
||||
out.push_str(" --preset");
|
||||
}
|
||||
if bind_mode != DEFAULT_BIND_MODE {
|
||||
out.push_str(" -M ");
|
||||
out.push_utfstr(&escape(bind_mode));
|
||||
}
|
||||
if !sets_mode.is_empty() && sets_mode != bind_mode {
|
||||
out.push_str(" -m ");
|
||||
out.push_utfstr(&escape(&sets_mode));
|
||||
}
|
||||
|
||||
// Append the name.
|
||||
if let Some(tname) = input_terminfo_get_name(seq) {
|
||||
// Note that we show -k here because we have an input key name.
|
||||
out.push_str(" -k ");
|
||||
out.push_utfstr(&tname);
|
||||
} else {
|
||||
// No key name, so no -k; we show the escape sequence directly.
|
||||
let eseq = escape(seq);
|
||||
out.push(' ');
|
||||
out.push_utfstr(&eseq);
|
||||
}
|
||||
|
||||
// Now show the list of commands.
|
||||
for ecmd in ecmds {
|
||||
out.push(' ');
|
||||
out.push_utfstr(&escape(&ecmd));
|
||||
}
|
||||
out.push('\n');
|
||||
|
||||
if !streams.out_is_redirected && isatty(libc::STDOUT_FILENO) {
|
||||
let mut colors = Vec::new();
|
||||
highlight_shell(&out, &mut colors, &parser.context(), false, None);
|
||||
let colored = colorize(&out, &colors, parser.vars());
|
||||
streams.out.append(str2wcstring(&colored));
|
||||
} else {
|
||||
streams.out.append(out);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
// Overload with both kinds of bindings.
|
||||
// Returns false only if neither exists.
|
||||
fn list_one_user_andor_preset(
|
||||
&self,
|
||||
seq: &wstr,
|
||||
bind_mode: &wstr,
|
||||
user: bool,
|
||||
preset: bool,
|
||||
parser: &Parser,
|
||||
streams: &mut IoStreams,
|
||||
) -> bool {
|
||||
let mut retval = false;
|
||||
if preset {
|
||||
retval |= self.list_one(seq, bind_mode, false, parser, streams);
|
||||
}
|
||||
if user {
|
||||
retval |= self.list_one(seq, bind_mode, true, parser, streams);
|
||||
}
|
||||
retval
|
||||
}
|
||||
|
||||
/// List all current key bindings.
|
||||
fn list(&self, bind_mode: Option<&wstr>, user: bool, parser: &Parser, streams: &mut IoStreams) {
|
||||
let lst = self.input_mappings.get_names(user);
|
||||
for binding in lst {
|
||||
if bind_mode.is_some() && bind_mode.unwrap() != binding.mode {
|
||||
continue;
|
||||
}
|
||||
|
||||
self.list_one(&binding.seq, &binding.mode, user, parser, streams);
|
||||
}
|
||||
}
|
||||
|
||||
/// Print terminfo key binding names to string buffer used for standard output.
|
||||
///
|
||||
/// \param all if set, all terminfo key binding names will be printed. If not set, only ones that
|
||||
/// are defined for this terminal are printed.
|
||||
fn key_names(&self, all: bool, streams: &mut IoStreams) {
|
||||
let names = input_terminfo_get_names(!all);
|
||||
for name in names {
|
||||
streams.out.appendln(name);
|
||||
}
|
||||
}
|
||||
|
||||
/// Print all the special key binding functions to string buffer used for standard output.
|
||||
fn function_names(&self, streams: &mut IoStreams) {
|
||||
let names = input_function_get_names();
|
||||
for name in names {
|
||||
streams.out.appendln(name);
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps input_terminfo_get_sequence(), appending the correct error messages as needed.
|
||||
fn get_terminfo_sequence(&self, seq: &wstr, streams: &mut IoStreams) -> Option<WString> {
|
||||
let mut tseq = WString::new();
|
||||
if input_terminfo_get_sequence(seq, &mut tseq) {
|
||||
return Some(tseq);
|
||||
}
|
||||
let err = Errno::last();
|
||||
|
||||
if !self.opts.silent {
|
||||
let eseq = escape_string(seq, EscapeStringStyle::Script(EscapeFlags::NO_PRINTABLES));
|
||||
if err == Errno::ENOENT {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
"%ls: No key with name '%ls' found\n",
|
||||
"bind",
|
||||
eseq
|
||||
));
|
||||
} else if err == Errno::EILSEQ {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
"%ls: Key with name '%ls' does not have any mapping\n",
|
||||
"bind",
|
||||
eseq
|
||||
));
|
||||
} else {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
"%ls: Unknown error trying to bind to key named '%ls'\n",
|
||||
"bind",
|
||||
eseq
|
||||
));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Add specified key binding.
|
||||
fn add(
|
||||
&mut self,
|
||||
seq: &wstr,
|
||||
cmds: &[&wstr],
|
||||
mode: WString,
|
||||
sets_mode: WString,
|
||||
terminfo: bool,
|
||||
user: bool,
|
||||
streams: &mut IoStreams,
|
||||
) -> bool {
|
||||
let cmds = cmds.iter().map(|&s| s.to_owned()).collect();
|
||||
if terminfo {
|
||||
if let Some(seq2) = self.get_terminfo_sequence(seq, streams) {
|
||||
self.input_mappings.add(seq2, cmds, mode, sets_mode, user);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
self.input_mappings
|
||||
.add(seq.to_owned(), cmds, mode, sets_mode, user)
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Erase specified key bindings
|
||||
///
|
||||
/// @param seq
|
||||
/// an array of all key bindings to erase
|
||||
/// @param all
|
||||
/// if specified, _all_ key bindings will be erased
|
||||
/// @param mode
|
||||
/// if specified, only bindings from that mode will be erased. If not given
|
||||
/// and @c all is @c false, @c DEFAULT_BIND_MODE will be used.
|
||||
/// @param use_terminfo
|
||||
/// Whether to look use terminfo -k name
|
||||
///
|
||||
fn erase(
|
||||
&mut self,
|
||||
seq: &[&wstr],
|
||||
all: bool,
|
||||
mode: Option<&wstr>,
|
||||
use_terminfo: bool,
|
||||
user: bool,
|
||||
streams: &mut IoStreams,
|
||||
) -> bool {
|
||||
if all {
|
||||
self.input_mappings.clear(mode, user);
|
||||
return false;
|
||||
}
|
||||
|
||||
let mut res = false;
|
||||
let mode = mode.unwrap_or(DEFAULT_BIND_MODE);
|
||||
|
||||
for s in seq {
|
||||
if use_terminfo {
|
||||
if let Some(seq2) = self.get_terminfo_sequence(s, streams) {
|
||||
self.input_mappings.erase(&seq2, mode, user);
|
||||
} else {
|
||||
res = true;
|
||||
}
|
||||
} else {
|
||||
self.input_mappings.erase(s, mode, user);
|
||||
}
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn insert(
|
||||
&mut self,
|
||||
optind: usize,
|
||||
argv: &[&wstr],
|
||||
parser: &Parser,
|
||||
streams: &mut IoStreams,
|
||||
) -> bool {
|
||||
let argc = argv.len();
|
||||
let cmd = argv[0];
|
||||
let arg_count = argc - optind;
|
||||
if arg_count < 2 {
|
||||
// If we get both or neither preset/user, we list both.
|
||||
if !self.opts.have_preset && !self.opts.have_user {
|
||||
self.opts.preset = true;
|
||||
self.opts.user = true;
|
||||
}
|
||||
} else {
|
||||
// Inserting both on the other hand makes no sense.
|
||||
if self.opts.have_preset && self.opts.have_user {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
BUILTIN_ERR_COMBO2_EXCLUSIVE,
|
||||
cmd,
|
||||
"--preset",
|
||||
"--user"
|
||||
));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if arg_count == 0 {
|
||||
// We don't overload this with user and def because we want them to be grouped.
|
||||
// First the presets, then the users (because of scrolling).
|
||||
let bind_mode = if self.opts.bind_mode_given {
|
||||
Some(self.opts.bind_mode.as_utfstr())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
if self.opts.preset {
|
||||
self.list(bind_mode, false, parser, streams);
|
||||
}
|
||||
if self.opts.user {
|
||||
self.list(bind_mode, true, parser, streams);
|
||||
}
|
||||
} else if arg_count == 1 {
|
||||
let seq = if self.opts.use_terminfo {
|
||||
let Some(seq2) = self.get_terminfo_sequence(argv[optind], streams) else {
|
||||
// get_terminfo_sequence already printed the error.
|
||||
return true;
|
||||
};
|
||||
seq2
|
||||
} else {
|
||||
argv[optind].to_owned()
|
||||
};
|
||||
|
||||
if !self.list_one_user_andor_preset(
|
||||
&seq,
|
||||
&self.opts.bind_mode,
|
||||
self.opts.user,
|
||||
self.opts.preset,
|
||||
parser,
|
||||
streams,
|
||||
) {
|
||||
let eseq = escape_string(
|
||||
argv[optind],
|
||||
EscapeStringStyle::Script(EscapeFlags::NO_PRINTABLES),
|
||||
);
|
||||
if !self.opts.silent {
|
||||
if self.opts.use_terminfo {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
"%ls: No binding found for key '%ls'\n",
|
||||
cmd,
|
||||
eseq
|
||||
));
|
||||
} else {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
"%ls: No binding found for sequence '%ls'\n",
|
||||
cmd,
|
||||
eseq
|
||||
));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Actually insert!
|
||||
if self.add(
|
||||
argv[optind],
|
||||
&argv[optind + 1..],
|
||||
self.opts.bind_mode.to_owned(),
|
||||
self.opts.sets_bind_mode.to_owned(),
|
||||
self.opts.use_terminfo,
|
||||
self.opts.user,
|
||||
streams,
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
||||
/// List all current bind modes.
|
||||
fn list_modes(&mut self, streams: &mut IoStreams) {
|
||||
// List all known modes, even if they are only in preset bindings.
|
||||
let lst = self.input_mappings.get_names(true);
|
||||
let preset_lst = self.input_mappings.get_names(false);
|
||||
|
||||
// Extract the bind modes, uniqueize, and sort.
|
||||
let mut modes: Vec<WString> = lst.into_iter().chain(preset_lst).map(|m| m.mode).collect();
|
||||
modes.sort_unstable();
|
||||
modes.dedup();
|
||||
|
||||
for mode in modes {
|
||||
streams.out.appendln(mode);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_cmd_opts(
|
||||
opts: &mut Options,
|
||||
optind: &mut usize,
|
||||
argv: &mut [&wstr],
|
||||
parser: &Parser,
|
||||
streams: &mut IoStreams,
|
||||
) -> Option<i32> {
|
||||
let cmd = argv[0];
|
||||
let short_options = L!(":aehkKfM:Lm:s");
|
||||
const long_options: &[woption] = &[
|
||||
wopt(L!("all"), no_argument, 'a'),
|
||||
wopt(L!("erase"), no_argument, 'e'),
|
||||
wopt(L!("function-names"), no_argument, 'f'),
|
||||
wopt(L!("help"), no_argument, 'h'),
|
||||
wopt(L!("key"), no_argument, 'k'),
|
||||
wopt(L!("key-names"), no_argument, 'K'),
|
||||
wopt(L!("list-modes"), no_argument, 'L'),
|
||||
wopt(L!("mode"), required_argument, 'M'),
|
||||
wopt(L!("preset"), no_argument, 'p'),
|
||||
wopt(L!("sets-mode"), required_argument, 'm'),
|
||||
wopt(L!("silent"), no_argument, 's'),
|
||||
wopt(L!("user"), no_argument, 'u'),
|
||||
];
|
||||
|
||||
let mut w = wgetopter_t::new(short_options, long_options, argv);
|
||||
while let Some(c) = w.wgetopt_long() {
|
||||
match c {
|
||||
'a' => opts.all = true,
|
||||
'e' => opts.mode = BIND_ERASE,
|
||||
'f' => opts.mode = BIND_FUNCTION_NAMES,
|
||||
'h' => opts.print_help = true,
|
||||
'k' => opts.use_terminfo = true,
|
||||
'K' => opts.mode = BIND_KEY_NAMES,
|
||||
'L' => {
|
||||
opts.list_modes = true;
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
'M' => {
|
||||
if !valid_var_name(w.woptarg.unwrap()) {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
BUILTIN_ERR_BIND_MODE,
|
||||
cmd,
|
||||
w.woptarg.unwrap()
|
||||
));
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
opts.bind_mode = w.woptarg.unwrap().to_owned();
|
||||
opts.bind_mode_given = true;
|
||||
}
|
||||
'm' => {
|
||||
if !valid_var_name(w.woptarg.unwrap()) {
|
||||
streams.err.append(wgettext_fmt!(
|
||||
BUILTIN_ERR_BIND_MODE,
|
||||
cmd,
|
||||
w.woptarg.unwrap()
|
||||
));
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
opts.sets_bind_mode = w.woptarg.unwrap().to_owned();
|
||||
}
|
||||
'p' => {
|
||||
opts.have_preset = true;
|
||||
opts.preset = true;
|
||||
}
|
||||
's' => opts.silent = true,
|
||||
'u' => {
|
||||
opts.have_user = true;
|
||||
opts.user = true;
|
||||
}
|
||||
':' => {
|
||||
builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1], true);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
'?' => {
|
||||
builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1], true);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
_ => {
|
||||
panic!("unexpected retval from wgetopt_long")
|
||||
}
|
||||
}
|
||||
}
|
||||
*optind = w.woptind;
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
impl BuiltinBind {
|
||||
/// The bind builtin, used for setting character sequences.
|
||||
pub fn bind(
|
||||
&mut self,
|
||||
parser: &Parser,
|
||||
streams: &mut IoStreams,
|
||||
argv: &mut [&wstr],
|
||||
) -> Option<c_int> {
|
||||
let cmd = argv[0];
|
||||
let mut optind = 0;
|
||||
let retval = parse_cmd_opts(&mut self.opts, &mut optind, argv, parser, streams);
|
||||
if retval != STATUS_CMD_OK {
|
||||
return retval;
|
||||
}
|
||||
|
||||
if self.opts.list_modes {
|
||||
self.list_modes(streams);
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
if self.opts.print_help {
|
||||
builtin_print_help(parser, streams, cmd);
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
// Default to user mode
|
||||
if !self.opts.have_preset && !self.opts.have_user {
|
||||
self.opts.user = true;
|
||||
}
|
||||
|
||||
match self.opts.mode {
|
||||
BIND_ERASE => {
|
||||
// TODO: satisfy the borrow checker here.
|
||||
let storage;
|
||||
let bind_mode = if self.opts.bind_mode_given {
|
||||
storage = self.opts.bind_mode.clone();
|
||||
Some(storage.as_utfstr())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// If we get both, we erase both.
|
||||
if self.opts.user {
|
||||
if self.erase(
|
||||
&argv[optind..],
|
||||
self.opts.all,
|
||||
bind_mode,
|
||||
self.opts.use_terminfo,
|
||||
true, /* user */
|
||||
streams,
|
||||
) {
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
}
|
||||
if self.opts.preset {
|
||||
if self.erase(
|
||||
&argv[optind..],
|
||||
self.opts.all,
|
||||
bind_mode,
|
||||
self.opts.use_terminfo,
|
||||
false, /* user */
|
||||
streams,
|
||||
) {
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
BIND_INSERT => {
|
||||
if self.insert(optind, argv, parser, streams) {
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
}
|
||||
BIND_KEY_NAMES => self.key_names(self.opts.all, streams),
|
||||
BIND_FUNCTION_NAMES => self.function_names(streams),
|
||||
_ => {
|
||||
streams
|
||||
.err
|
||||
.append(wgettext_fmt!("%ls: Invalid state\n", cmd));
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
}
|
||||
STATUS_CMD_OK
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bind(parser: &Parser, streams: &mut IoStreams, args: &mut [&wstr]) -> Option<c_int> {
|
||||
run_builtin_ffi(crate::ffi::builtin_bind, parser, streams, args)
|
||||
BuiltinBind::new().bind(parser, streams, args)
|
||||
}
|
||||
|
|
3
fish-rust/src/env/environment.rs
vendored
3
fish-rust/src/env/environment.rs
vendored
|
@ -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).
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -12,96 +12,11 @@ use std::os::fd::RawFd;
|
|||
use std::ptr;
|
||||
use std::sync::atomic::{AtomicUsize, Ordering};
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum ReadlineCmd {
|
||||
BeginningOfLine,
|
||||
EndOfLine,
|
||||
ForwardChar,
|
||||
BackwardChar,
|
||||
ForwardSingleChar,
|
||||
ForwardWord,
|
||||
BackwardWord,
|
||||
ForwardBigword,
|
||||
BackwardBigword,
|
||||
NextdOrForwardWord,
|
||||
PrevdOrBackwardWord,
|
||||
HistorySearchBackward,
|
||||
HistorySearchForward,
|
||||
HistoryPrefixSearchBackward,
|
||||
HistoryPrefixSearchForward,
|
||||
HistoryPager,
|
||||
HistoryPagerDelete,
|
||||
DeleteChar,
|
||||
BackwardDeleteChar,
|
||||
KillLine,
|
||||
Yank,
|
||||
YankPop,
|
||||
Complete,
|
||||
CompleteAndSearch,
|
||||
PagerToggleSearch,
|
||||
BeginningOfHistory,
|
||||
EndOfHistory,
|
||||
BackwardKillLine,
|
||||
KillWholeLine,
|
||||
KillInnerLine,
|
||||
KillWord,
|
||||
KillBigword,
|
||||
BackwardKillWord,
|
||||
BackwardKillPathComponent,
|
||||
BackwardKillBigword,
|
||||
HistoryTokenSearchBackward,
|
||||
HistoryTokenSearchForward,
|
||||
SelfInsert,
|
||||
SelfInsertNotFirst,
|
||||
TransposeChars,
|
||||
TransposeWords,
|
||||
UpcaseWord,
|
||||
DowncaseWord,
|
||||
CapitalizeWord,
|
||||
TogglecaseChar,
|
||||
TogglecaseSelection,
|
||||
Execute,
|
||||
BeginningOfBuffer,
|
||||
EndOfBuffer,
|
||||
RepaintMode,
|
||||
Repaint,
|
||||
ForceRepaint,
|
||||
UpLine,
|
||||
DownLine,
|
||||
SuppressAutosuggestion,
|
||||
AcceptAutosuggestion,
|
||||
BeginSelection,
|
||||
SwapSelectionStartStop,
|
||||
EndSelection,
|
||||
KillSelection,
|
||||
InsertLineUnder,
|
||||
InsertLineOver,
|
||||
ForwardJump,
|
||||
BackwardJump,
|
||||
ForwardJumpTill,
|
||||
BackwardJumpTill,
|
||||
FuncAnd,
|
||||
FuncOr,
|
||||
ExpandAbbr,
|
||||
DeleteOrExit,
|
||||
Exit,
|
||||
CancelCommandline,
|
||||
Cancel,
|
||||
Undo,
|
||||
Redo,
|
||||
BeginUndoGroup,
|
||||
EndUndoGroup,
|
||||
RepeatJump,
|
||||
DisableMouseTracking,
|
||||
// ncurses uses the obvious name
|
||||
ClearScreenAndRepaint,
|
||||
// NOTE: This one has to be last.
|
||||
ReverseRepeatJump,
|
||||
}
|
||||
|
||||
// The range of key codes for inputrc-style keyboard functions.
|
||||
pub const R_END_INPUT_FUNCTIONS: usize = (ReadlineCmd::ReverseRepeatJump as usize) + 1;
|
||||
pub const R_END_INPUT_FUNCTIONS: usize = (ReadlineCmd::ReverseRepeatJump.repr as usize) + 1;
|
||||
|
||||
// TODO: move CharInputStyle and ReadlineCmd here once they no longer must be exposed to C++.
|
||||
pub use crate::input_ffi::{CharInputStyle, ReadlineCmd};
|
||||
|
||||
/// Represents an event on the character input stream.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
|
@ -120,17 +35,6 @@ pub enum CharEventType {
|
|||
CheckExit,
|
||||
}
|
||||
|
||||
/// Hackish: the input style, which describes how char events (only) are applied to the command
|
||||
/// line. Note this is set only after applying bindings; it is not set from readb().
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum CharInputStyle {
|
||||
// Insert characters normally.
|
||||
Normal,
|
||||
|
||||
// Insert characters only if the cursor is not at the beginning. Otherwise, discard them.
|
||||
NotFirst,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CharEvent {
|
||||
pub evt: CharEventType,
|
||||
|
|
267
fish-rust/src/input_ffi.rs
Normal file
267
fish-rust/src/input_ffi.rs
Normal file
|
@ -0,0 +1,267 @@
|
|||
use crate::ffi::wcstring_list_ffi_t;
|
||||
use crate::input::*;
|
||||
use crate::input_common::*;
|
||||
use crate::parser::ParserRefFFI;
|
||||
use crate::threads::CppCallback;
|
||||
use crate::wchar::prelude::*;
|
||||
use crate::wchar_ffi::AsWstr;
|
||||
use crate::wchar_ffi::WCharToFFI;
|
||||
use cxx::CxxWString;
|
||||
pub use ffi::{CharInputStyle, ReadlineCmd};
|
||||
use std::pin::Pin;
|
||||
|
||||
// Returns the code, or -1 on failure.
|
||||
fn input_function_get_code_ffi(name: &CxxWString) -> i32 {
|
||||
if let Some(code) = input_function_get_code(name.as_wstr()) {
|
||||
code.repr as i32
|
||||
} else {
|
||||
-1
|
||||
}
|
||||
}
|
||||
|
||||
fn char_event_from_readline_ffi(cmd: ReadlineCmd) -> Box<CharEvent> {
|
||||
Box::new(CharEvent::from_readline(cmd))
|
||||
}
|
||||
|
||||
fn char_event_from_char_ffi(c: u8) -> Box<CharEvent> {
|
||||
Box::new(CharEvent::from_char(c.into()))
|
||||
}
|
||||
|
||||
fn make_inputter_ffi(parser: &ParserRefFFI, in_fd: i32) -> Box<Inputter> {
|
||||
Box::new(Inputter::new(parser.0.clone(), in_fd))
|
||||
}
|
||||
|
||||
fn make_input_event_queue_ffi(in_fd: i32) -> Box<InputEventQueue> {
|
||||
Box::new(InputEventQueue::new(in_fd))
|
||||
}
|
||||
|
||||
fn input_terminfo_get_name_ffi(seq: &CxxWString, out: Pin<&mut CxxWString>) -> bool {
|
||||
let Some(name) = input_terminfo_get_name(seq.as_wstr()) else {
|
||||
return false;
|
||||
};
|
||||
out.push_chars(name.as_char_slice());
|
||||
true
|
||||
}
|
||||
|
||||
impl Inputter {
|
||||
#[allow(clippy::boxed_local)]
|
||||
fn queue_char_ffi(&mut self, ch: Box<CharEvent>) {
|
||||
self.queue_char(*ch);
|
||||
}
|
||||
|
||||
fn read_char_ffi(&mut self, command_handler: &cxx::SharedPtr<CppCallback>) -> Box<CharEvent> {
|
||||
let mut rust_handler = |cmds: &[WString]| {
|
||||
let ffi_cmds = cmds.to_ffi();
|
||||
command_handler.invoke_with_param(ffi_cmds.as_ref().unwrap() as *const _ as *const u8);
|
||||
};
|
||||
let mhandler = if !command_handler.is_null() {
|
||||
Some(&mut rust_handler as &mut CommandHandler)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Box::new(self.read_char(mhandler))
|
||||
}
|
||||
|
||||
fn function_pop_arg_ffi(&mut self) -> u32 {
|
||||
self.function_pop_arg().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl CharEvent {
|
||||
fn get_char_ffi(&self) -> u32 {
|
||||
self.get_char().into()
|
||||
}
|
||||
|
||||
fn get_input_style_ffi(&self) -> CharInputStyle {
|
||||
self.input_style
|
||||
}
|
||||
}
|
||||
|
||||
impl InputEventQueue {
|
||||
// Returns Box<CharEvent>::into_raw(), or nullptr if None.
|
||||
fn readch_timed_esc_ffi(&mut self) -> *mut CharEvent {
|
||||
match self.readch_timed_esc() {
|
||||
Some(ch) => Box::into_raw(Box::new(ch)),
|
||||
None => std::ptr::null_mut(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cxx::bridge]
|
||||
mod ffi {
|
||||
/// Hackish: the input style, which describes how char events (only) are applied to the command
|
||||
/// line. Note this is set only after applying bindings; it is not set from readb().
|
||||
#[cxx_name = "char_input_style_t"]
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum CharInputStyle {
|
||||
// Insert characters normally.
|
||||
Normal,
|
||||
|
||||
// Insert characters only if the cursor is not at the beginning. Otherwise, discard them.
|
||||
NotFirst,
|
||||
}
|
||||
|
||||
#[cxx_name = "readline_cmd_t"]
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||
pub enum ReadlineCmd {
|
||||
BeginningOfLine,
|
||||
EndOfLine,
|
||||
ForwardChar,
|
||||
BackwardChar,
|
||||
ForwardSingleChar,
|
||||
ForwardWord,
|
||||
BackwardWord,
|
||||
ForwardBigword,
|
||||
BackwardBigword,
|
||||
NextdOrForwardWord,
|
||||
PrevdOrBackwardWord,
|
||||
HistorySearchBackward,
|
||||
HistorySearchForward,
|
||||
HistoryPrefixSearchBackward,
|
||||
HistoryPrefixSearchForward,
|
||||
HistoryPager,
|
||||
HistoryPagerDelete,
|
||||
DeleteChar,
|
||||
BackwardDeleteChar,
|
||||
KillLine,
|
||||
Yank,
|
||||
YankPop,
|
||||
Complete,
|
||||
CompleteAndSearch,
|
||||
PagerToggleSearch,
|
||||
BeginningOfHistory,
|
||||
EndOfHistory,
|
||||
BackwardKillLine,
|
||||
KillWholeLine,
|
||||
KillInnerLine,
|
||||
KillWord,
|
||||
KillBigword,
|
||||
BackwardKillWord,
|
||||
BackwardKillPathComponent,
|
||||
BackwardKillBigword,
|
||||
HistoryTokenSearchBackward,
|
||||
HistoryTokenSearchForward,
|
||||
SelfInsert,
|
||||
SelfInsertNotFirst,
|
||||
TransposeChars,
|
||||
TransposeWords,
|
||||
UpcaseWord,
|
||||
DowncaseWord,
|
||||
CapitalizeWord,
|
||||
TogglecaseChar,
|
||||
TogglecaseSelection,
|
||||
Execute,
|
||||
BeginningOfBuffer,
|
||||
EndOfBuffer,
|
||||
RepaintMode,
|
||||
Repaint,
|
||||
ForceRepaint,
|
||||
UpLine,
|
||||
DownLine,
|
||||
SuppressAutosuggestion,
|
||||
AcceptAutosuggestion,
|
||||
BeginSelection,
|
||||
SwapSelectionStartStop,
|
||||
EndSelection,
|
||||
KillSelection,
|
||||
InsertLineUnder,
|
||||
InsertLineOver,
|
||||
ForwardJump,
|
||||
BackwardJump,
|
||||
ForwardJumpTill,
|
||||
BackwardJumpTill,
|
||||
FuncAnd,
|
||||
FuncOr,
|
||||
ExpandAbbr,
|
||||
DeleteOrExit,
|
||||
Exit,
|
||||
CancelCommandline,
|
||||
Cancel,
|
||||
Undo,
|
||||
Redo,
|
||||
BeginUndoGroup,
|
||||
EndUndoGroup,
|
||||
RepeatJump,
|
||||
DisableMouseTracking,
|
||||
// ncurses uses the obvious name
|
||||
ClearScreenAndRepaint,
|
||||
// NOTE: This one has to be last.
|
||||
ReverseRepeatJump,
|
||||
}
|
||||
|
||||
extern "C++" {
|
||||
include!("parser.h");
|
||||
include!("reader.h");
|
||||
include!("callback.h");
|
||||
type wcstring_list_ffi_t = super::wcstring_list_ffi_t;
|
||||
type ParserRef = crate::parser::ParserRefFFI;
|
||||
|
||||
#[rust_name = "CppCallback"]
|
||||
type callback_t = crate::threads::CppCallback;
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
fn init_input();
|
||||
|
||||
#[cxx_name = "input_function_get_code"]
|
||||
fn input_function_get_code_ffi(name: &CxxWString) -> i32;
|
||||
|
||||
#[cxx_name = "input_terminfo_get_name"]
|
||||
fn input_terminfo_get_name_ffi(seq: &CxxWString, out: Pin<&mut CxxWString>) -> bool;
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
type CharEvent;
|
||||
|
||||
#[cxx_name = "char_event_from_readline"]
|
||||
fn char_event_from_readline_ffi(cmd: ReadlineCmd) -> Box<CharEvent>;
|
||||
|
||||
#[cxx_name = "char_event_from_char"]
|
||||
fn char_event_from_char_ffi(c: u8) -> Box<CharEvent>;
|
||||
|
||||
fn is_char(&self) -> bool;
|
||||
fn is_readline(&self) -> bool;
|
||||
fn is_check_exit(&self) -> bool;
|
||||
fn is_eof(&self) -> bool;
|
||||
|
||||
#[cxx_name = "get_char"]
|
||||
fn get_char_ffi(&self) -> u32;
|
||||
|
||||
#[cxx_name = "get_input_style"]
|
||||
fn get_input_style_ffi(&self) -> CharInputStyle;
|
||||
|
||||
fn get_readline(&self) -> ReadlineCmd;
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
type Inputter;
|
||||
|
||||
#[cxx_name = "make_inputter"]
|
||||
fn make_inputter_ffi(parser: &ParserRef, in_fd: i32) -> Box<Inputter>;
|
||||
|
||||
#[cxx_name = "queue_char"]
|
||||
fn queue_char_ffi(&mut self, ch: Box<CharEvent>);
|
||||
|
||||
fn queue_readline(&mut self, cmd: ReadlineCmd);
|
||||
|
||||
#[cxx_name = "read_char"]
|
||||
fn read_char_ffi(&mut self, command_handler: &SharedPtr<CppCallback>) -> Box<CharEvent>;
|
||||
|
||||
fn function_set_status(&mut self, status: bool);
|
||||
|
||||
#[cxx_name = "function_pop_arg"]
|
||||
fn function_pop_arg_ffi(&mut self) -> u32;
|
||||
}
|
||||
|
||||
extern "Rust" {
|
||||
type InputEventQueue;
|
||||
|
||||
#[cxx_name = "make_input_event_queue"]
|
||||
fn make_input_event_queue_ffi(in_fd: i32) -> Box<InputEventQueue>;
|
||||
|
||||
#[cxx_name = "readch_timed_esc"]
|
||||
fn readch_timed_esc_ffi(&mut self) -> *mut CharEvent;
|
||||
}
|
||||
}
|
|
@ -71,6 +71,7 @@ mod highlight;
|
|||
mod history;
|
||||
mod input;
|
||||
mod input_common;
|
||||
mod input_ffi;
|
||||
mod io;
|
||||
mod job_group;
|
||||
mod kill;
|
||||
|
|
|
@ -102,6 +102,7 @@ mod ffi {
|
|||
}
|
||||
}
|
||||
|
||||
pub use ffi::CppCallback;
|
||||
unsafe impl Send for ffi::CppCallback {}
|
||||
unsafe impl Sync for ffi::CppCallback {}
|
||||
|
||||
|
|
|
@ -1,521 +0,0 @@
|
|||
// Implementation of the bind builtin.
|
||||
#include "config.h" // IWYU pragma: keep
|
||||
|
||||
#include "bind.h"
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cerrno>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "../builtin.h"
|
||||
#include "../common.h"
|
||||
#include "../env.h"
|
||||
#include "../fallback.h" // IWYU pragma: keep
|
||||
#include "../highlight.h"
|
||||
#include "../input.h"
|
||||
#include "../io.h"
|
||||
#include "../maybe.h"
|
||||
#include "../parser.h"
|
||||
#include "../wgetopt.h"
|
||||
#include "../wutil.h" // IWYU pragma: keep
|
||||
#include "builtins/shared.rs.h"
|
||||
|
||||
enum { BIND_INSERT, BIND_ERASE, BIND_KEY_NAMES, BIND_FUNCTION_NAMES };
|
||||
struct bind_cmd_opts_t {
|
||||
bool all = false;
|
||||
bool bind_mode_given = false;
|
||||
bool list_modes = false;
|
||||
bool print_help = false;
|
||||
bool silent = false;
|
||||
bool use_terminfo = false;
|
||||
bool have_user = false;
|
||||
bool user = false;
|
||||
bool have_preset = false;
|
||||
bool preset = false;
|
||||
int mode = BIND_INSERT;
|
||||
const wchar_t *bind_mode = DEFAULT_BIND_MODE;
|
||||
const wchar_t *sets_bind_mode = L"";
|
||||
};
|
||||
|
||||
namespace {
|
||||
class builtin_bind_t {
|
||||
public:
|
||||
maybe_t<int> builtin_bind(const parser_t &parser, io_streams_t &streams, const wchar_t **argv);
|
||||
|
||||
builtin_bind_t() : input_mappings_(input_mappings()) {}
|
||||
|
||||
private:
|
||||
bind_cmd_opts_t *opts;
|
||||
|
||||
/// Note that builtin_bind_t holds the singleton lock.
|
||||
/// It must not call out to anything which can execute fish shell code or attempt to acquire the
|
||||
/// lock again.
|
||||
acquired_lock<input_mapping_set_t> input_mappings_;
|
||||
|
||||
void list(const wchar_t *bind_mode, bool user, const parser_t &parser, io_streams_t &streams);
|
||||
void key_names(bool all, io_streams_t &streams);
|
||||
void function_names(io_streams_t &streams);
|
||||
bool add(const wcstring &seq, const wchar_t *const *cmds, size_t cmds_len, const wchar_t *mode,
|
||||
const wchar_t *sets_mode, bool terminfo, bool user, io_streams_t &streams);
|
||||
bool erase(const wchar_t *const *seq, bool all, const wchar_t *mode, bool use_terminfo,
|
||||
bool user, io_streams_t &streams);
|
||||
bool get_terminfo_sequence(const wcstring &seq, wcstring *out_seq, io_streams_t &streams) const;
|
||||
bool insert(int optind, int argc, const wchar_t **argv, const parser_t &parser,
|
||||
io_streams_t &streams);
|
||||
void list_modes(io_streams_t &streams);
|
||||
bool list_one(const wcstring &seq, const wcstring &bind_mode, bool user, const parser_t &parser,
|
||||
io_streams_t &streams);
|
||||
bool list_one(const wcstring &seq, const wcstring &bind_mode, bool user, bool preset,
|
||||
const parser_t &parser, io_streams_t &streams);
|
||||
};
|
||||
|
||||
/// List a single key binding.
|
||||
/// Returns false if no binding with that sequence and mode exists.
|
||||
bool builtin_bind_t::list_one(const wcstring &seq, const wcstring &bind_mode, bool user,
|
||||
const parser_t &parser, io_streams_t &streams) {
|
||||
std::vector<wcstring> ecmds;
|
||||
wcstring sets_mode, out;
|
||||
|
||||
if (!input_mappings_->get(seq, bind_mode, &ecmds, user, &sets_mode)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
out.append(L"bind");
|
||||
|
||||
// Append the mode flags if applicable.
|
||||
if (!user) {
|
||||
out.append(L" --preset");
|
||||
}
|
||||
if (bind_mode != DEFAULT_BIND_MODE) {
|
||||
out.append(L" -M ");
|
||||
out.append(escape_string(bind_mode));
|
||||
}
|
||||
if (!sets_mode.empty() && sets_mode != bind_mode) {
|
||||
out.append(L" -m ");
|
||||
out.append(escape_string(sets_mode));
|
||||
}
|
||||
|
||||
// Append the name.
|
||||
wcstring tname;
|
||||
if (input_terminfo_get_name(seq, &tname)) {
|
||||
// Note that we show -k here because we have an input key name.
|
||||
out.append(L" -k ");
|
||||
out.append(tname);
|
||||
} else {
|
||||
// No key name, so no -k; we show the escape sequence directly.
|
||||
const wcstring eseq = escape_string(seq);
|
||||
out.append(L" ");
|
||||
out.append(eseq);
|
||||
}
|
||||
|
||||
// Now show the list of commands.
|
||||
for (const auto &ecmd : ecmds) {
|
||||
out.push_back(' ');
|
||||
out.append(escape_string(ecmd));
|
||||
}
|
||||
out.push_back(L'\n');
|
||||
|
||||
if (!streams.out_is_redirected() && isatty(STDOUT_FILENO)) {
|
||||
auto ffi_colors = highlight_shell_ffi(out, *parser_context(parser), false, {});
|
||||
auto ffi_colored = colorize(out, *ffi_colors, parser.vars());
|
||||
std::string colored{ffi_colored.begin(), ffi_colored.end()};
|
||||
streams.out()->append(str2wcstring(std::move(colored)));
|
||||
} else {
|
||||
streams.out()->append(out);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Overload with both kinds of bindings.
|
||||
// Returns false only if neither exists.
|
||||
bool builtin_bind_t::list_one(const wcstring &seq, const wcstring &bind_mode, bool user,
|
||||
bool preset, const parser_t &parser, io_streams_t &streams) {
|
||||
bool retval = false;
|
||||
if (preset) {
|
||||
retval |= list_one(seq, bind_mode, false, parser, streams);
|
||||
}
|
||||
if (user) {
|
||||
retval |= list_one(seq, bind_mode, true, parser, streams);
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
/// List all current key bindings.
|
||||
void builtin_bind_t::list(const wchar_t *bind_mode, bool user, const parser_t &parser,
|
||||
io_streams_t &streams) {
|
||||
const std::vector<input_mapping_name_t> lst = input_mappings_->get_names(user);
|
||||
|
||||
for (const input_mapping_name_t &binding : lst) {
|
||||
if (bind_mode && bind_mode != binding.mode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
list_one(binding.seq, binding.mode, user, parser, streams);
|
||||
}
|
||||
}
|
||||
|
||||
/// Print terminfo key binding names to string buffer used for standard output.
|
||||
///
|
||||
/// \param all if set, all terminfo key binding names will be printed. If not set, only ones that
|
||||
/// are defined for this terminal are printed.
|
||||
void builtin_bind_t::key_names(bool all, io_streams_t &streams) {
|
||||
const std::vector<wcstring> names = input_terminfo_get_names(!all);
|
||||
for (const wcstring &name : names) {
|
||||
streams.out()->append(name);
|
||||
streams.out()->push(L'\n');
|
||||
}
|
||||
}
|
||||
|
||||
/// Print all the special key binding functions to string buffer used for standard output.
|
||||
void builtin_bind_t::function_names(io_streams_t &streams) {
|
||||
std::vector<wcstring> names = input_function_get_names();
|
||||
|
||||
for (const auto &name : names) {
|
||||
auto seq = name.c_str();
|
||||
streams.out()->append(format_string(L"%ls\n", seq));
|
||||
}
|
||||
}
|
||||
|
||||
/// Wraps input_terminfo_get_sequence(), appending the correct error messages as needed.
|
||||
bool builtin_bind_t::get_terminfo_sequence(const wcstring &seq, wcstring *out_seq,
|
||||
io_streams_t &streams) const {
|
||||
if (input_terminfo_get_sequence(seq, out_seq)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
wcstring eseq = escape_string(seq, ESCAPE_NO_PRINTABLES);
|
||||
if (!opts->silent) {
|
||||
if (errno == ENOENT) {
|
||||
streams.err()->append(
|
||||
format_string(_(L"%ls: No key with name '%ls' found\n"), L"bind", eseq.c_str()));
|
||||
} else if (errno == EILSEQ) {
|
||||
streams.err()->append(format_string(
|
||||
_(L"%ls: Key with name '%ls' does not have any mapping\n"), L"bind", eseq.c_str()));
|
||||
} else {
|
||||
streams.err()->append(
|
||||
format_string(_(L"%ls: Unknown error trying to bind to key named '%ls'\n"), L"bind",
|
||||
eseq.c_str()));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Add specified key binding.
|
||||
bool builtin_bind_t::add(const wcstring &seq, const wchar_t *const *cmds, size_t cmds_len,
|
||||
const wchar_t *mode, const wchar_t *sets_mode, bool terminfo, bool user,
|
||||
io_streams_t &streams) {
|
||||
if (terminfo) {
|
||||
wcstring seq2;
|
||||
if (get_terminfo_sequence(seq, &seq2, streams)) {
|
||||
input_mappings_->add(seq2, cmds, cmds_len, mode, sets_mode, user);
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
|
||||
} else {
|
||||
input_mappings_->add(seq, cmds, cmds_len, mode, sets_mode, user);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Erase specified key bindings
|
||||
///
|
||||
/// @param seq
|
||||
/// an array of all key bindings to erase
|
||||
/// @param all
|
||||
/// if specified, _all_ key bindings will be erased
|
||||
/// @param mode
|
||||
/// if specified, only bindings from that mode will be erased. If not given
|
||||
/// and @c all is @c false, @c DEFAULT_BIND_MODE will be used.
|
||||
/// @param use_terminfo
|
||||
/// Whether to look use terminfo -k name
|
||||
///
|
||||
bool builtin_bind_t::erase(const wchar_t *const *seq, bool all, const wchar_t *mode,
|
||||
bool use_terminfo, bool user, io_streams_t &streams) {
|
||||
if (all) {
|
||||
input_mappings_->clear(mode, user);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool res = false;
|
||||
if (mode == nullptr) mode = DEFAULT_BIND_MODE; //!OCLINT(parameter reassignment)
|
||||
|
||||
while (*seq) {
|
||||
if (use_terminfo) {
|
||||
wcstring seq2;
|
||||
if (get_terminfo_sequence(*seq++, &seq2, streams)) {
|
||||
input_mappings_->erase(seq2, mode, user);
|
||||
} else {
|
||||
res = true;
|
||||
}
|
||||
} else {
|
||||
input_mappings_->erase(*seq++, mode, user);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
bool builtin_bind_t::insert(int optind, int argc, const wchar_t **argv, const parser_t &parser,
|
||||
io_streams_t &streams) {
|
||||
const wchar_t *cmd = argv[0];
|
||||
int arg_count = argc - optind;
|
||||
|
||||
if (arg_count < 2) {
|
||||
// If we get both or neither preset/user, we list both.
|
||||
if (!opts->have_preset && !opts->have_user) {
|
||||
opts->preset = true;
|
||||
opts->user = true;
|
||||
}
|
||||
} else {
|
||||
// Inserting both on the other hand makes no sense.
|
||||
if (opts->have_preset && opts->have_user) {
|
||||
streams.err()->append(
|
||||
format_string(BUILTIN_ERR_COMBO2_EXCLUSIVE, cmd, L"--preset", "--user"));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (arg_count == 0) {
|
||||
// We don't overload this with user and def because we want them to be grouped.
|
||||
// First the presets, then the users (because of scrolling).
|
||||
if (opts->preset) {
|
||||
list(opts->bind_mode_given ? opts->bind_mode : nullptr, false, parser, streams);
|
||||
}
|
||||
if (opts->user) {
|
||||
list(opts->bind_mode_given ? opts->bind_mode : nullptr, true, parser, streams);
|
||||
}
|
||||
} else if (arg_count == 1) {
|
||||
wcstring seq;
|
||||
if (opts->use_terminfo) {
|
||||
if (!get_terminfo_sequence(argv[optind], &seq, streams)) {
|
||||
// get_terminfo_sequence already printed the error.
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
seq = argv[optind];
|
||||
}
|
||||
|
||||
if (!list_one(seq, opts->bind_mode, opts->user, opts->preset, parser, streams)) {
|
||||
wcstring eseq = escape_string(argv[optind], ESCAPE_NO_PRINTABLES);
|
||||
if (!opts->silent) {
|
||||
if (opts->use_terminfo) {
|
||||
streams.err()->append(format_string(_(L"%ls: No binding found for key '%ls'\n"),
|
||||
cmd, eseq.c_str()));
|
||||
} else {
|
||||
streams.err()->append(format_string(
|
||||
_(L"%ls: No binding found for sequence '%ls'\n"), cmd, eseq.c_str()));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
// Actually insert!
|
||||
if (add(argv[optind], argv + (optind + 1), argc - (optind + 1), opts->bind_mode,
|
||||
opts->sets_bind_mode, opts->use_terminfo, opts->user, streams)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// List all current bind modes.
|
||||
void builtin_bind_t::list_modes(io_streams_t &streams) {
|
||||
// List all known modes, even if they are only in preset bindings.
|
||||
const std::vector<input_mapping_name_t> lst = input_mappings_->get_names(true);
|
||||
const std::vector<input_mapping_name_t> preset_lst = input_mappings_->get_names(false);
|
||||
// A set accomplishes two things for us here:
|
||||
// - It removes duplicates (no twenty "default" entries).
|
||||
// - It sorts it, which makes it nicer on the user.
|
||||
std::set<wcstring> modes;
|
||||
|
||||
for (const input_mapping_name_t &binding : lst) {
|
||||
modes.insert(binding.mode);
|
||||
}
|
||||
for (const input_mapping_name_t &binding : preset_lst) {
|
||||
modes.insert(binding.mode);
|
||||
}
|
||||
for (const auto &mode : modes) {
|
||||
streams.out()->append(format_string(L"%ls\n", mode.c_str()));
|
||||
}
|
||||
}
|
||||
|
||||
static int parse_cmd_opts(bind_cmd_opts_t &opts, int *optind, //!OCLINT(high ncss method)
|
||||
int argc, const wchar_t **argv, const parser_t &parser,
|
||||
io_streams_t &streams) {
|
||||
const wchar_t *cmd = argv[0];
|
||||
static const wchar_t *const short_options = L":aehkKfM:Lm:s";
|
||||
static const struct woption long_options[] = {{L"all", no_argument, 'a'},
|
||||
{L"erase", no_argument, 'e'},
|
||||
{L"function-names", no_argument, 'f'},
|
||||
{L"help", no_argument, 'h'},
|
||||
{L"key", no_argument, 'k'},
|
||||
{L"key-names", no_argument, 'K'},
|
||||
{L"list-modes", no_argument, 'L'},
|
||||
{L"mode", required_argument, 'M'},
|
||||
{L"preset", no_argument, 'p'},
|
||||
{L"sets-mode", required_argument, 'm'},
|
||||
{L"silent", no_argument, 's'},
|
||||
{L"user", no_argument, 'u'},
|
||||
{}};
|
||||
|
||||
int opt;
|
||||
wgetopter_t w;
|
||||
while ((opt = w.wgetopt_long(argc, argv, short_options, long_options, nullptr)) != -1) {
|
||||
switch (opt) {
|
||||
case L'a': {
|
||||
opts.all = true;
|
||||
break;
|
||||
}
|
||||
case L'e': {
|
||||
opts.mode = BIND_ERASE;
|
||||
break;
|
||||
}
|
||||
case L'f': {
|
||||
opts.mode = BIND_FUNCTION_NAMES;
|
||||
break;
|
||||
}
|
||||
case L'h': {
|
||||
opts.print_help = true;
|
||||
break;
|
||||
}
|
||||
case L'k': {
|
||||
opts.use_terminfo = true;
|
||||
break;
|
||||
}
|
||||
case L'K': {
|
||||
opts.mode = BIND_KEY_NAMES;
|
||||
break;
|
||||
}
|
||||
case L'L': {
|
||||
opts.list_modes = true;
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
case L'M': {
|
||||
if (!valid_var_name(w.woptarg)) {
|
||||
streams.err()->append(format_string(BUILTIN_ERR_BIND_MODE, cmd, w.woptarg));
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
opts.bind_mode = w.woptarg;
|
||||
opts.bind_mode_given = true;
|
||||
break;
|
||||
}
|
||||
case L'm': {
|
||||
if (!valid_var_name(w.woptarg)) {
|
||||
streams.err()->append(format_string(BUILTIN_ERR_BIND_MODE, cmd, w.woptarg));
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
opts.sets_bind_mode = w.woptarg;
|
||||
break;
|
||||
}
|
||||
case L'p': {
|
||||
opts.have_preset = true;
|
||||
opts.preset = true;
|
||||
break;
|
||||
}
|
||||
case L's': {
|
||||
opts.silent = true;
|
||||
break;
|
||||
}
|
||||
case L'u': {
|
||||
opts.have_user = true;
|
||||
opts.user = true;
|
||||
break;
|
||||
}
|
||||
case ':': {
|
||||
builtin_missing_argument(parser, streams, cmd, argv[w.woptind - 1], true);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
case L'?': {
|
||||
builtin_unknown_option(parser, streams, cmd, argv[w.woptind - 1], true);
|
||||
return STATUS_INVALID_ARGS;
|
||||
}
|
||||
default: {
|
||||
DIE("unexpected retval from wgetopt_long");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
*optind = w.woptind;
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
/// The bind builtin, used for setting character sequences.
|
||||
maybe_t<int> builtin_bind_t::builtin_bind(const parser_t &parser, io_streams_t &streams,
|
||||
const wchar_t **argv) {
|
||||
const wchar_t *cmd = argv[0];
|
||||
int argc = builtin_count_args(argv);
|
||||
bind_cmd_opts_t opts;
|
||||
this->opts = &opts;
|
||||
|
||||
int optind;
|
||||
int retval = parse_cmd_opts(opts, &optind, argc, argv, parser, streams);
|
||||
if (retval != STATUS_CMD_OK) return retval;
|
||||
|
||||
if (opts.list_modes) {
|
||||
list_modes(streams);
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
if (opts.print_help) {
|
||||
builtin_print_help(parser, streams, cmd);
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
// Default to user mode
|
||||
if (!opts.have_preset && !opts.have_user) opts.user = true;
|
||||
switch (opts.mode) {
|
||||
case BIND_ERASE: {
|
||||
const wchar_t *bind_mode = opts.bind_mode_given ? opts.bind_mode : nullptr;
|
||||
// If we get both, we erase both.
|
||||
if (opts.user) {
|
||||
if (erase(&argv[optind], opts.all, bind_mode, opts.use_terminfo, /* user */ true,
|
||||
streams)) {
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
}
|
||||
if (opts.preset) {
|
||||
if (erase(&argv[optind], opts.all, bind_mode, opts.use_terminfo, /* user */ false,
|
||||
streams)) {
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case BIND_INSERT: {
|
||||
if (insert(optind, argc, argv, parser, streams)) {
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case BIND_KEY_NAMES: {
|
||||
key_names(opts.all, streams);
|
||||
break;
|
||||
}
|
||||
case BIND_FUNCTION_NAMES: {
|
||||
function_names(streams);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
streams.err()->append(format_string(_(L"%ls: Invalid state\n"), cmd));
|
||||
return STATUS_CMD_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
return STATUS_CMD_OK;
|
||||
}
|
||||
|
||||
int builtin_bind(const void *_parser, void *_streams, void *_argv) {
|
||||
const auto &parser = *static_cast<const parser_t *>(_parser);
|
||||
auto &streams = *static_cast<io_streams_t *>(_streams);
|
||||
auto argv = static_cast<const wchar_t **>(_argv);
|
||||
builtin_bind_t bind;
|
||||
return *bind.builtin_bind(parser, streams, argv);
|
||||
}
|
|
@ -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
|
|
@ -11,8 +11,6 @@
|
|||
#include "../builtin.h"
|
||||
#include "../common.h"
|
||||
#include "../fallback.h" // IWYU pragma: keep
|
||||
#include "../input.h"
|
||||
#include "../input_common.h"
|
||||
#include "../io.h"
|
||||
#include "../maybe.h"
|
||||
#include "../parse_constants.h"
|
||||
|
@ -24,6 +22,7 @@
|
|||
#include "../wgetopt.h"
|
||||
#include "../wutil.h" // IWYU pragma: keep
|
||||
#include "builtins/shared.rs.h"
|
||||
#include "input_ffi.rs.h"
|
||||
|
||||
/// Which part of the comandbuffer are we operating on.
|
||||
enum {
|
||||
|
@ -305,10 +304,12 @@ int builtin_commandline(const void *_parser, void *_streams, void *_argv) {
|
|||
|
||||
using rl = readline_cmd_t;
|
||||
for (i = w.woptind; i < argc; i++) {
|
||||
if (auto mc = input_function_get_code(argv[i])) {
|
||||
int mci = input_function_get_code(argv[i]);
|
||||
if (mci >= 0) {
|
||||
readline_cmd_t mc = static_cast<readline_cmd_t>(mci);
|
||||
// Don't enqueue a repaint if we're currently in the middle of one,
|
||||
// because that's an infinite loop.
|
||||
if (mc == rl::repaint_mode || mc == rl::force_repaint || mc == rl::repaint) {
|
||||
if (mc == rl::RepaintMode || mc == rl::ForceRepaint || mc == rl::Repaint) {
|
||||
if (ld.is_repaint()) continue;
|
||||
}
|
||||
|
||||
|
@ -317,11 +318,11 @@ int builtin_commandline(const void *_parser, void *_streams, void *_argv) {
|
|||
// insert/replace operations into readline functions with associated data, so that
|
||||
// all queued `commandline` operations - including buffer modifications - are
|
||||
// executed in order
|
||||
if (mc == rl::begin_undo_group || mc == rl::end_undo_group) {
|
||||
reader_handle_command(*mc);
|
||||
if (mc == rl::BeginUndoGroup || mc == rl::EndUndoGroup) {
|
||||
reader_handle_command(mc);
|
||||
} else {
|
||||
// Inserts the readline function at the back of the queue.
|
||||
reader_queue_ch(*mc);
|
||||
reader_queue_ch(char_event_from_readline(mc));
|
||||
}
|
||||
} else {
|
||||
streams.err()->append(
|
||||
|
|
|
@ -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({}, {}, {});
|
||||
}
|
||||
|
|
|
@ -27,8 +27,7 @@
|
|||
#include "ffi_baggage.h"
|
||||
#include "ffi_init.rs.h"
|
||||
#include "fish_version.h"
|
||||
#include "input.h"
|
||||
#include "input_common.h"
|
||||
#include "input_ffi.rs.h"
|
||||
#include "maybe.h"
|
||||
#include "parser.h"
|
||||
#include "print_help.rs.h"
|
||||
|
@ -85,7 +84,7 @@ static maybe_t<wcstring> sequence_name(wchar_t wc) {
|
|||
for (size_t i = 0; i < recent_chars.size(); i++) {
|
||||
wcstring out_name;
|
||||
wcstring seq = str2wcstring(recent_chars.substr(i));
|
||||
if (input_terminfo_get_name(seq, &out_name)) {
|
||||
if (input_terminfo_get_name(seq, out_name)) {
|
||||
return out_name;
|
||||
}
|
||||
}
|
||||
|
@ -230,18 +229,21 @@ static double output_elapsed_time(double prev_tstamp, bool first_char_seen, bool
|
|||
static void process_input(bool continuous_mode, bool verbose) {
|
||||
bool first_char_seen = false;
|
||||
double prev_tstamp = 0.0;
|
||||
input_event_queue_t queue;
|
||||
auto queue = make_input_event_queue(STDIN_FILENO);
|
||||
std::vector<wchar_t> bind_chars;
|
||||
|
||||
std::fwprintf(stderr, L"Press a key:\n");
|
||||
while (!check_exit_loop_maybe_warning(nullptr)) {
|
||||
maybe_t<char_event_t> evt{};
|
||||
maybe_t<rust::Box<char_event_t>> evt{};
|
||||
if (reader_test_and_clear_interrupted()) {
|
||||
evt = char_event_t{shell_modes.c_cc[VINTR]};
|
||||
evt = char_event_from_char(shell_modes.c_cc[VINTR]);
|
||||
} else {
|
||||
evt = queue.readch_timed_esc();
|
||||
char_event_t *evt_raw = queue->readch_timed_esc();
|
||||
if (evt_raw) {
|
||||
evt = rust::Box<char_event_t>::from_raw(evt_raw);
|
||||
}
|
||||
}
|
||||
if (!evt || !evt->is_char()) {
|
||||
if (!evt || !(*evt)->is_char()) {
|
||||
output_bind_command(bind_chars);
|
||||
if (first_char_seen && !continuous_mode) {
|
||||
return;
|
||||
|
@ -249,7 +251,7 @@ static void process_input(bool continuous_mode, bool verbose) {
|
|||
continue;
|
||||
}
|
||||
|
||||
wchar_t wc = evt->get_char();
|
||||
wchar_t wc = (*evt)->get_char();
|
||||
prev_tstamp = output_elapsed_time(prev_tstamp, first_char_seen, verbose);
|
||||
// Hack for #3189. Do not suggest \c@ as the binding for nul, because a string containing
|
||||
// nul cannot be passed to builtin_bind since it uses C strings. We'll output the name of
|
||||
|
|
|
@ -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"
|
||||
|
|
973
src/input.cpp
973
src/input.cpp
|
@ -1,973 +0,0 @@
|
|||
// Functions for reading a character of input from stdin.
|
||||
#include "config.h"
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#if HAVE_TERM_H
|
||||
#include <curses.h> // IWYU pragma: keep
|
||||
#include <term.h>
|
||||
#elif HAVE_NCURSES_TERM_H
|
||||
#include <ncurses/term.h>
|
||||
#endif
|
||||
#include <termios.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common.h"
|
||||
#include "env.h"
|
||||
#include "event.h"
|
||||
#include "fallback.h" // IWYU pragma: keep
|
||||
#include "flog.h"
|
||||
#include "global_safety.h"
|
||||
#include "input.h"
|
||||
#include "input_common.h"
|
||||
#include "parser.h"
|
||||
#include "proc.h"
|
||||
#include "reader.h"
|
||||
#include "signals.h" // IWYU pragma: keep
|
||||
#include "threads.rs.h"
|
||||
#include "wutil.h" // IWYU pragma: keep
|
||||
|
||||
/// A name for our own key mapping for nul.
|
||||
static const wchar_t *k_nul_mapping_name = L"nul";
|
||||
|
||||
/// Struct representing a keybinding. Returned by input_get_mappings.
|
||||
struct input_mapping_t {
|
||||
/// Character sequence which generates this event.
|
||||
wcstring seq;
|
||||
/// Commands that should be evaluated by this mapping.
|
||||
std::vector<wcstring> commands;
|
||||
/// We wish to preserve the user-specified order. This is just an incrementing value.
|
||||
unsigned int specification_order;
|
||||
/// Mode in which this command should be evaluated.
|
||||
wcstring mode;
|
||||
/// New mode that should be switched to after command evaluation.
|
||||
wcstring sets_mode;
|
||||
|
||||
input_mapping_t(wcstring s, std::vector<wcstring> c, wcstring m, wcstring sm)
|
||||
: seq(std::move(s)), commands(std::move(c)), mode(std::move(m)), sets_mode(std::move(sm)) {
|
||||
static unsigned int s_last_input_map_spec_order = 0;
|
||||
specification_order = ++s_last_input_map_spec_order;
|
||||
}
|
||||
|
||||
/// \return true if this is a generic mapping, i.e. acts as a fallback.
|
||||
bool is_generic() const { return seq.empty(); }
|
||||
};
|
||||
|
||||
/// A struct representing the mapping from a terminfo key name to a terminfo character sequence.
|
||||
struct terminfo_mapping_t {
|
||||
// name of key
|
||||
const wchar_t *name;
|
||||
|
||||
// character sequence generated on keypress, or none if there was no mapping.
|
||||
maybe_t<std::string> seq;
|
||||
|
||||
terminfo_mapping_t(const wchar_t *name, const char *s) : name(name) {
|
||||
if (s) seq.emplace(s);
|
||||
}
|
||||
|
||||
terminfo_mapping_t(const wchar_t *name, std::string s) : name(name), seq(std::move(s)) {}
|
||||
};
|
||||
|
||||
static constexpr size_t input_function_count = R_END_INPUT_FUNCTIONS;
|
||||
|
||||
/// Input function metadata. This list should be kept in sync with the key code list in
|
||||
/// input_common.h.
|
||||
struct input_function_metadata_t {
|
||||
const wchar_t *name;
|
||||
readline_cmd_t code;
|
||||
};
|
||||
|
||||
/// A static mapping of all readline commands as strings to their readline_cmd_t equivalent.
|
||||
/// Keep this list sorted alphabetically!
|
||||
static constexpr const input_function_metadata_t input_function_metadata[] = {
|
||||
// NULL makes it unusable - this is specially inserted when we detect mouse input
|
||||
{L"", readline_cmd_t::disable_mouse_tracking},
|
||||
{L"accept-autosuggestion", readline_cmd_t::accept_autosuggestion},
|
||||
{L"and", readline_cmd_t::func_and},
|
||||
{L"backward-bigword", readline_cmd_t::backward_bigword},
|
||||
{L"backward-char", readline_cmd_t::backward_char},
|
||||
{L"backward-delete-char", readline_cmd_t::backward_delete_char},
|
||||
{L"backward-jump", readline_cmd_t::backward_jump},
|
||||
{L"backward-jump-till", readline_cmd_t::backward_jump_till},
|
||||
{L"backward-kill-bigword", readline_cmd_t::backward_kill_bigword},
|
||||
{L"backward-kill-line", readline_cmd_t::backward_kill_line},
|
||||
{L"backward-kill-path-component", readline_cmd_t::backward_kill_path_component},
|
||||
{L"backward-kill-word", readline_cmd_t::backward_kill_word},
|
||||
{L"backward-word", readline_cmd_t::backward_word},
|
||||
{L"begin-selection", readline_cmd_t::begin_selection},
|
||||
{L"begin-undo-group", readline_cmd_t::begin_undo_group},
|
||||
{L"beginning-of-buffer", readline_cmd_t::beginning_of_buffer},
|
||||
{L"beginning-of-history", readline_cmd_t::beginning_of_history},
|
||||
{L"beginning-of-line", readline_cmd_t::beginning_of_line},
|
||||
{L"cancel", readline_cmd_t::cancel},
|
||||
{L"cancel-commandline", readline_cmd_t::cancel_commandline},
|
||||
{L"capitalize-word", readline_cmd_t::capitalize_word},
|
||||
{L"clear-screen", readline_cmd_t::clear_screen_and_repaint},
|
||||
{L"complete", readline_cmd_t::complete},
|
||||
{L"complete-and-search", readline_cmd_t::complete_and_search},
|
||||
{L"delete-char", readline_cmd_t::delete_char},
|
||||
{L"delete-or-exit", readline_cmd_t::delete_or_exit},
|
||||
{L"down-line", readline_cmd_t::down_line},
|
||||
{L"downcase-word", readline_cmd_t::downcase_word},
|
||||
{L"end-of-buffer", readline_cmd_t::end_of_buffer},
|
||||
{L"end-of-history", readline_cmd_t::end_of_history},
|
||||
{L"end-of-line", readline_cmd_t::end_of_line},
|
||||
{L"end-selection", readline_cmd_t::end_selection},
|
||||
{L"end-undo-group", readline_cmd_t::end_undo_group},
|
||||
{L"execute", readline_cmd_t::execute},
|
||||
{L"exit", readline_cmd_t::exit},
|
||||
{L"expand-abbr", readline_cmd_t::expand_abbr},
|
||||
{L"force-repaint", readline_cmd_t::force_repaint},
|
||||
{L"forward-bigword", readline_cmd_t::forward_bigword},
|
||||
{L"forward-char", readline_cmd_t::forward_char},
|
||||
{L"forward-jump", readline_cmd_t::forward_jump},
|
||||
{L"forward-jump-till", readline_cmd_t::forward_jump_till},
|
||||
{L"forward-single-char", readline_cmd_t::forward_single_char},
|
||||
{L"forward-word", readline_cmd_t::forward_word},
|
||||
{L"history-pager", readline_cmd_t::history_pager},
|
||||
{L"history-pager-delete", readline_cmd_t::history_pager_delete},
|
||||
{L"history-prefix-search-backward", readline_cmd_t::history_prefix_search_backward},
|
||||
{L"history-prefix-search-forward", readline_cmd_t::history_prefix_search_forward},
|
||||
{L"history-search-backward", readline_cmd_t::history_search_backward},
|
||||
{L"history-search-forward", readline_cmd_t::history_search_forward},
|
||||
{L"history-token-search-backward", readline_cmd_t::history_token_search_backward},
|
||||
{L"history-token-search-forward", readline_cmd_t::history_token_search_forward},
|
||||
{L"insert-line-over", readline_cmd_t::insert_line_over},
|
||||
{L"insert-line-under", readline_cmd_t::insert_line_under},
|
||||
{L"kill-bigword", readline_cmd_t::kill_bigword},
|
||||
{L"kill-inner-line", readline_cmd_t::kill_inner_line},
|
||||
{L"kill-line", readline_cmd_t::kill_line},
|
||||
{L"kill-selection", readline_cmd_t::kill_selection},
|
||||
{L"kill-whole-line", readline_cmd_t::kill_whole_line},
|
||||
{L"kill-word", readline_cmd_t::kill_word},
|
||||
{L"nextd-or-forward-word", readline_cmd_t::nextd_or_forward_word},
|
||||
{L"or", readline_cmd_t::func_or},
|
||||
{L"pager-toggle-search", readline_cmd_t::pager_toggle_search},
|
||||
{L"prevd-or-backward-word", readline_cmd_t::prevd_or_backward_word},
|
||||
{L"redo", readline_cmd_t::redo},
|
||||
{L"repaint", readline_cmd_t::repaint},
|
||||
{L"repaint-mode", readline_cmd_t::repaint_mode},
|
||||
{L"repeat-jump", readline_cmd_t::repeat_jump},
|
||||
{L"repeat-jump-reverse", readline_cmd_t::reverse_repeat_jump},
|
||||
{L"self-insert", readline_cmd_t::self_insert},
|
||||
{L"self-insert-notfirst", readline_cmd_t::self_insert_notfirst},
|
||||
{L"suppress-autosuggestion", readline_cmd_t::suppress_autosuggestion},
|
||||
{L"swap-selection-start-stop", readline_cmd_t::swap_selection_start_stop},
|
||||
{L"togglecase-char", readline_cmd_t::togglecase_char},
|
||||
{L"togglecase-selection", readline_cmd_t::togglecase_selection},
|
||||
{L"transpose-chars", readline_cmd_t::transpose_chars},
|
||||
{L"transpose-words", readline_cmd_t::transpose_words},
|
||||
{L"undo", readline_cmd_t::undo},
|
||||
{L"up-line", readline_cmd_t::up_line},
|
||||
{L"upcase-word", readline_cmd_t::upcase_word},
|
||||
{L"yank", readline_cmd_t::yank},
|
||||
{L"yank-pop", readline_cmd_t::yank_pop},
|
||||
};
|
||||
|
||||
ASSERT_SORTED_BY_NAME(input_function_metadata);
|
||||
static_assert(sizeof(input_function_metadata) / sizeof(input_function_metadata[0]) ==
|
||||
input_function_count,
|
||||
"input_function_metadata size mismatch with input_common. Did you forget to update "
|
||||
"input_function_metadata?");
|
||||
|
||||
// Keep this function for debug purposes
|
||||
// See 031b265
|
||||
wcstring describe_char(wint_t c) {
|
||||
if (c < R_END_INPUT_FUNCTIONS) {
|
||||
return format_string(L"%02x (%ls)", c, input_function_metadata[c].name);
|
||||
}
|
||||
return format_string(L"%02x", c);
|
||||
}
|
||||
|
||||
using mapping_list_t = std::vector<input_mapping_t>;
|
||||
input_mapping_set_t::input_mapping_set_t() = default;
|
||||
input_mapping_set_t::~input_mapping_set_t() = default;
|
||||
|
||||
acquired_lock<input_mapping_set_t> input_mappings() {
|
||||
static owning_lock<input_mapping_set_t> s_mappings{input_mapping_set_t()};
|
||||
return s_mappings.acquire();
|
||||
}
|
||||
|
||||
/// Terminfo map list.
|
||||
static latch_t<std::vector<terminfo_mapping_t>> s_terminfo_mappings;
|
||||
|
||||
/// \return the input terminfo.
|
||||
static std::vector<terminfo_mapping_t> create_input_terminfo();
|
||||
|
||||
/// Return the current bind mode.
|
||||
static wcstring input_get_bind_mode(const environment_t &vars) {
|
||||
auto mode = vars.get(FISH_BIND_MODE_VAR);
|
||||
return mode ? mode->as_string() : DEFAULT_BIND_MODE;
|
||||
}
|
||||
|
||||
/// Set the current bind mode.
|
||||
static void input_set_bind_mode(const parser_t &parser, const wcstring &bm) {
|
||||
// Only set this if it differs to not execute variable handlers all the time.
|
||||
// modes may not be empty - empty is a sentinel value meaning to not change the mode
|
||||
assert(!bm.empty());
|
||||
if (input_get_bind_mode(env_stack_t{parser.vars_boxed()}) != bm) {
|
||||
// Must send events here - see #6653.
|
||||
parser.set_var_and_fire(FISH_BIND_MODE_VAR, ENV_GLOBAL, wcstring_list_ffi_t{{bm}});
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the arity of a given input function.
|
||||
static int input_function_arity(readline_cmd_t function) {
|
||||
switch (function) {
|
||||
case readline_cmd_t::forward_jump:
|
||||
case readline_cmd_t::backward_jump:
|
||||
case readline_cmd_t::forward_jump_till:
|
||||
case readline_cmd_t::backward_jump_till:
|
||||
return 1;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function to compare the lengths of sequences.
|
||||
static bool length_is_greater_than(const input_mapping_t &m1, const input_mapping_t &m2) {
|
||||
return m1.seq.size() > m2.seq.size();
|
||||
}
|
||||
|
||||
static bool specification_order_is_less_than(const input_mapping_t &m1, const input_mapping_t &m2) {
|
||||
return m1.specification_order < m2.specification_order;
|
||||
}
|
||||
|
||||
/// Inserts an input mapping at the correct position. We sort them in descending order by length, so
|
||||
/// that we test longer sequences first.
|
||||
static void input_mapping_insert_sorted(mapping_list_t &ml, input_mapping_t new_mapping) {
|
||||
auto loc = std::lower_bound(ml.begin(), ml.end(), new_mapping, length_is_greater_than);
|
||||
ml.insert(loc, std::move(new_mapping));
|
||||
}
|
||||
|
||||
/// Adds an input mapping.
|
||||
void input_mapping_set_t::add(wcstring sequence, const wchar_t *const *commands,
|
||||
size_t commands_len, const wchar_t *mode, const wchar_t *sets_mode,
|
||||
bool user) {
|
||||
assert(commands && mode && sets_mode && "Null parameter");
|
||||
|
||||
// Clear cached mappings.
|
||||
all_mappings_cache_.reset();
|
||||
|
||||
// Remove existing mappings with this sequence.
|
||||
const std::vector<wcstring> commands_vector(commands, commands + commands_len);
|
||||
|
||||
mapping_list_t &ml = user ? mapping_list_ : preset_mapping_list_;
|
||||
|
||||
for (input_mapping_t &m : ml) {
|
||||
if (m.seq == sequence && m.mode == mode) {
|
||||
m.commands = commands_vector;
|
||||
m.sets_mode = sets_mode;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Add a new mapping, using the next order.
|
||||
input_mapping_t new_mapping =
|
||||
input_mapping_t(std::move(sequence), commands_vector, mode, sets_mode);
|
||||
input_mapping_insert_sorted(ml, std::move(new_mapping));
|
||||
}
|
||||
|
||||
void input_mapping_set_t::add(wcstring sequence, const wchar_t *command, const wchar_t *mode,
|
||||
const wchar_t *sets_mode, bool user) {
|
||||
input_mapping_set_t::add(std::move(sequence), &command, 1, mode, sets_mode, user);
|
||||
}
|
||||
|
||||
/// Set up arrays used by readch to detect escape sequences for special keys and perform related
|
||||
/// initializations for our input subsystem.
|
||||
void init_input() {
|
||||
ASSERT_IS_MAIN_THREAD();
|
||||
|
||||
if (s_terminfo_mappings.is_set()) return;
|
||||
s_terminfo_mappings = create_input_terminfo();
|
||||
|
||||
auto input_mapping = input_mappings();
|
||||
|
||||
// If we have no keybindings, add a few simple defaults.
|
||||
if (input_mapping->preset_mapping_list_.empty()) {
|
||||
input_mapping->add(L"", L"self-insert", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
|
||||
input_mapping->add(L"\n", L"execute", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
|
||||
input_mapping->add(L"\r", L"execute", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
|
||||
input_mapping->add(L"\t", L"complete", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
|
||||
input_mapping->add(L"\x3", L"cancel-commandline", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE,
|
||||
false);
|
||||
input_mapping->add(L"\x4", L"exit", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
|
||||
input_mapping->add(L"\x5", L"bind", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
|
||||
// ctrl-s
|
||||
input_mapping->add(L"\x13", L"pager-toggle-search", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE,
|
||||
false);
|
||||
// ctrl-u
|
||||
input_mapping->add(L"\x15", L"backward-kill-line", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE,
|
||||
false);
|
||||
// del/backspace
|
||||
input_mapping->add(L"\x7f", L"backward-delete-char", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE,
|
||||
false);
|
||||
// Arrows - can't have functions, so *-or-search isn't available.
|
||||
input_mapping->add(L"\x1B[A", L"up-line", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
|
||||
input_mapping->add(L"\x1B[B", L"down-line", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
|
||||
input_mapping->add(L"\x1B[C", L"forward-char", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
|
||||
input_mapping->add(L"\x1B[D", L"backward-char", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE,
|
||||
false);
|
||||
// emacs-style ctrl-p/n/b/f
|
||||
input_mapping->add(L"\x10", L"up-line", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
|
||||
input_mapping->add(L"\x0e", L"down-line", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
|
||||
input_mapping->add(L"\x02", L"backward-char", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
|
||||
input_mapping->add(L"\x06", L"forward-char", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE, false);
|
||||
}
|
||||
}
|
||||
|
||||
inputter_t::inputter_t(const parser_t &parser, int in)
|
||||
: input_event_queue_t(in), parser_(parser.shared()) {}
|
||||
|
||||
void inputter_t::prepare_to_select() /* override */ {
|
||||
// Fire any pending events and reap stray processes, including printing exit status messages.
|
||||
auto &parser = this->parser_->deref();
|
||||
event_fire_delayed(parser);
|
||||
if (job_reap(parser, true)) reader_schedule_prompt_repaint();
|
||||
}
|
||||
|
||||
void inputter_t::select_interrupted() /* override */ {
|
||||
// Readline commands may be bound to \cc which also sets the cancel flag.
|
||||
// See #6937, #8125.
|
||||
signal_clear_cancel();
|
||||
|
||||
// Fire any pending events and reap stray processes, including printing exit status messages.
|
||||
auto &parser = this->parser_->deref();
|
||||
event_fire_delayed(parser);
|
||||
if (job_reap(parser, true)) reader_schedule_prompt_repaint();
|
||||
|
||||
// Tell the reader an event occurred.
|
||||
if (reader_reading_interrupted()) {
|
||||
auto vintr = shell_modes.c_cc[VINTR];
|
||||
if (vintr != 0) {
|
||||
this->push_front(char_event_t{vintr});
|
||||
}
|
||||
return;
|
||||
}
|
||||
this->push_front(char_event_t{char_event_type_t::check_exit});
|
||||
}
|
||||
|
||||
void inputter_t::uvar_change_notified() /* override */ {
|
||||
this->parser_->deref().sync_uvars_and_fire(true /* always */);
|
||||
}
|
||||
|
||||
void inputter_t::function_push_arg(wchar_t arg) { input_function_args_.push_back(arg); }
|
||||
|
||||
wchar_t inputter_t::function_pop_arg() {
|
||||
assert(!input_function_args_.empty() && "function_pop_arg underflow");
|
||||
auto result = input_function_args_.back();
|
||||
input_function_args_.pop_back();
|
||||
return result;
|
||||
}
|
||||
|
||||
void inputter_t::function_push_args(readline_cmd_t code) {
|
||||
int arity = input_function_arity(code);
|
||||
assert(event_storage_.empty() && "event_storage_ should be empty");
|
||||
auto &skipped = event_storage_;
|
||||
|
||||
for (int i = 0; i < arity; i++) {
|
||||
// Skip and queue up any function codes. See issue #2357.
|
||||
wchar_t arg{};
|
||||
for (;;) {
|
||||
auto evt = this->readch();
|
||||
if (evt.is_char()) {
|
||||
arg = evt.get_char();
|
||||
break;
|
||||
}
|
||||
skipped.push_back(evt);
|
||||
}
|
||||
function_push_arg(arg);
|
||||
}
|
||||
|
||||
// Push the function codes back into the input stream.
|
||||
this->insert_front(skipped.begin(), skipped.end());
|
||||
event_storage_.clear();
|
||||
}
|
||||
|
||||
/// Perform the action of the specified binding. allow_commands controls whether fish commands
|
||||
/// should be executed, or should be deferred until later.
|
||||
void inputter_t::mapping_execute(const input_mapping_t &m,
|
||||
const command_handler_t &command_handler) {
|
||||
// has_functions: there are functions that need to be put on the input queue
|
||||
// has_commands: there are shell commands that need to be evaluated
|
||||
bool has_commands = false, has_functions = false;
|
||||
|
||||
for (const wcstring &cmd : m.commands) {
|
||||
if (input_function_get_code(cmd)) {
|
||||
has_functions = true;
|
||||
} else {
|
||||
has_commands = true;
|
||||
}
|
||||
|
||||
if (has_functions && has_commands) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// !has_functions && !has_commands: only set bind mode
|
||||
if (!has_commands && !has_functions) {
|
||||
if (!m.sets_mode.empty()) input_set_bind_mode(parser_->deref(), m.sets_mode);
|
||||
return;
|
||||
}
|
||||
|
||||
if (has_commands && !command_handler) {
|
||||
// We don't want to run commands yet. Put the characters back and return check_exit.
|
||||
this->insert_front(m.seq.cbegin(), m.seq.cend());
|
||||
this->push_front(char_event_type_t::check_exit);
|
||||
return; // skip the input_set_bind_mode
|
||||
} else if (has_functions && !has_commands) {
|
||||
// Functions are added at the head of the input queue.
|
||||
for (auto it = m.commands.rbegin(), end = m.commands.rend(); it != end; ++it) {
|
||||
readline_cmd_t code = input_function_get_code(*it).value();
|
||||
function_push_args(code);
|
||||
this->push_front(char_event_t(code, m.seq));
|
||||
}
|
||||
} else if (has_commands && !has_functions) {
|
||||
// Execute all commands.
|
||||
//
|
||||
// FIXME(snnw): if commands add stuff to input queue (e.g. commandline -f execute), we won't
|
||||
// see that until all other commands have also been run.
|
||||
command_handler(m.commands);
|
||||
this->push_front(char_event_type_t::check_exit);
|
||||
} else {
|
||||
// Invalid binding, mixed commands and functions. We would need to execute these one by
|
||||
// one.
|
||||
this->push_front(char_event_type_t::check_exit);
|
||||
}
|
||||
|
||||
// Empty bind mode indicates to not reset the mode (#2871)
|
||||
if (!m.sets_mode.empty()) input_set_bind_mode(parser_->deref(), m.sets_mode);
|
||||
}
|
||||
|
||||
void inputter_t::queue_char(const char_event_t &ch) {
|
||||
if (ch.is_readline()) {
|
||||
function_push_args(ch.get_readline());
|
||||
}
|
||||
this->push_back(ch);
|
||||
}
|
||||
|
||||
/// A class which allows accumulating input events, or returns them to the queue.
|
||||
/// This contains a list of events which have been dequeued, and a current index into that list.
|
||||
class event_queue_peeker_t {
|
||||
public:
|
||||
explicit event_queue_peeker_t(input_event_queue_t &event_queue) : event_queue_(event_queue) {}
|
||||
|
||||
/// \return the next event.
|
||||
char_event_t next() {
|
||||
assert(idx_ <= peeked_.size() && "Index must not be larger than dequeued event count");
|
||||
if (idx_ == peeked_.size()) {
|
||||
auto event = event_queue_.readch();
|
||||
peeked_.push_back(event);
|
||||
}
|
||||
return peeked_.at(idx_++);
|
||||
}
|
||||
|
||||
/// Check if the next event is the given character. This advances the index on success only.
|
||||
/// If \p timed is set, then return false if this (or any other) character had a timeout.
|
||||
bool next_is_char(wchar_t c, bool escaped = false) {
|
||||
assert(idx_ <= peeked_.size() && "Index must not be larger than dequeued event count");
|
||||
// See if we had a timeout already.
|
||||
if (escaped && had_timeout_) {
|
||||
return false;
|
||||
}
|
||||
// Grab a new event if we have exhausted what we have already peeked.
|
||||
// Use either readch or readch_timed, per our param.
|
||||
if (idx_ == peeked_.size()) {
|
||||
char_event_t newevt{L'\0'};
|
||||
if (!escaped) {
|
||||
if (auto mevt = event_queue_.readch_timed_sequence_key()) {
|
||||
newevt = mevt.acquire();
|
||||
} else {
|
||||
had_timeout_ = true;
|
||||
return false;
|
||||
}
|
||||
} else if (auto mevt = event_queue_.readch_timed_esc()) {
|
||||
newevt = mevt.acquire();
|
||||
} else {
|
||||
had_timeout_ = true;
|
||||
return false;
|
||||
}
|
||||
peeked_.push_back(newevt);
|
||||
}
|
||||
// Now we have peeked far enough; check the event.
|
||||
// If it matches the char, then increment the index.
|
||||
if (peeked_.at(idx_).maybe_char() == c) {
|
||||
idx_++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// \return the current index.
|
||||
size_t len() const { return idx_; }
|
||||
|
||||
/// Consume all events up to the current index.
|
||||
/// Remaining events are returned to the queue.
|
||||
void consume() {
|
||||
event_queue_.insert_front(peeked_.cbegin() + idx_, peeked_.cend());
|
||||
peeked_.clear();
|
||||
idx_ = 0;
|
||||
}
|
||||
|
||||
/// Test if any of our peeked events are readline or check_exit.
|
||||
bool char_sequence_interrupted() const {
|
||||
for (const auto &evt : peeked_) {
|
||||
if (evt.is_readline() || evt.is_check_exit()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Reset our index back to 0.
|
||||
void restart() { idx_ = 0; }
|
||||
|
||||
~event_queue_peeker_t() {
|
||||
assert(idx_ == 0 && "Events left on the queue - missing restart or consume?");
|
||||
consume();
|
||||
}
|
||||
|
||||
private:
|
||||
/// The list of events which have been dequeued.
|
||||
std::vector<char_event_t> peeked_{};
|
||||
|
||||
/// If set, then some previous timed event timed out.
|
||||
bool had_timeout_{false};
|
||||
|
||||
/// The current index. This never exceeds peeked_.size().
|
||||
size_t idx_{0};
|
||||
|
||||
/// The queue from which to read more events.
|
||||
input_event_queue_t &event_queue_;
|
||||
};
|
||||
|
||||
/// Try reading a mouse-tracking CSI sequence, using the given \p peeker.
|
||||
/// Events are left on the peeker and the caller must restart or consume it.
|
||||
/// \return true if matched, false if not.
|
||||
static bool have_mouse_tracking_csi(event_queue_peeker_t *peeker) {
|
||||
// Maximum length of any CSI is NPAR (which is nominally 16), although this does not account for
|
||||
// user input intermixed with pseudo input generated by the tty emulator.
|
||||
// Check for the CSI first.
|
||||
if (!peeker->next_is_char(L'\x1b') || !peeker->next_is_char(L'[', true /* escaped */)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto next = peeker->next().maybe_char();
|
||||
size_t length = 0;
|
||||
if (next == L'M') {
|
||||
// Generic X10 or modified VT200 sequence. It doesn't matter which, they're both 6 chars
|
||||
// (although in mode 1005, the characters may be unicode and not necessarily just one byte
|
||||
// long) reporting the button that was clicked and its location.
|
||||
length = 6;
|
||||
} else if (next == L'<') {
|
||||
// Extended (SGR/1006) mouse reporting mode, with semicolon-separated parameters for button
|
||||
// code, Px, and Py, ending with 'M' for button press or 'm' for button release.
|
||||
while (true) {
|
||||
next = peeker->next().maybe_char();
|
||||
if (next == L'M' || next == L'm') {
|
||||
// However much we've read, we've consumed the CSI in its entirety.
|
||||
length = peeker->len();
|
||||
break;
|
||||
}
|
||||
if (peeker->len() >= 16) {
|
||||
// This is likely a malformed mouse-reporting CSI but we can't do anything about it.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else if (next == L't') {
|
||||
// VT200 button released in mouse highlighting mode at valid text location. 5 chars.
|
||||
length = 5;
|
||||
} else if (next == L'T') {
|
||||
// VT200 button released in mouse highlighting mode past end-of-line. 9 characters.
|
||||
length = 9;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Consume however many characters it takes to prevent the mouse tracking sequence from reaching
|
||||
// the prompt, dependent on the class of mouse reporting as detected above.
|
||||
while (peeker->len() < length) {
|
||||
(void)peeker->next();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// \return true if a given \p peeker matches a given sequence of char events given by \p str.
|
||||
static bool try_peek_sequence(event_queue_peeker_t *peeker, const wcstring &str) {
|
||||
assert(!str.empty() && "Empty string passed to try_peek_sequence");
|
||||
wchar_t prev = L'\0';
|
||||
for (wchar_t c : str) {
|
||||
// If we just read an escape, we need to add a timeout for the next char,
|
||||
// to distinguish between the actual escape key and an "alt"-modifier.
|
||||
bool escaped = prev == L'\x1B';
|
||||
if (!peeker->next_is_char(c, escaped)) {
|
||||
return false;
|
||||
}
|
||||
prev = c;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/// \return the first mapping that matches, walking first over the user's mapping list, then the
|
||||
/// preset list.
|
||||
/// \return none if nothing matches, or if we may have matched a longer sequence but it was
|
||||
/// interrupted by a readline event.
|
||||
maybe_t<input_mapping_t> inputter_t::find_mapping(event_queue_peeker_t *peeker) {
|
||||
const input_mapping_t *generic = nullptr;
|
||||
env_stack_t vars{parser_->deref().vars_boxed()};
|
||||
const wcstring bind_mode = input_get_bind_mode(vars);
|
||||
const input_mapping_t *escape = nullptr;
|
||||
|
||||
auto ml = input_mappings()->all_mappings();
|
||||
for (const auto &m : *ml) {
|
||||
if (m.mode != bind_mode) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Defer generic mappings until the end.
|
||||
if (m.is_generic()) {
|
||||
if (!generic) generic = &m;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (try_peek_sequence(peeker, m.seq)) {
|
||||
// A binding for just escape should also be deferred
|
||||
// so escape sequences take precedence.
|
||||
if (m.seq == L"\x1B") {
|
||||
if (!escape) {
|
||||
escape = &m;
|
||||
}
|
||||
} else {
|
||||
return m;
|
||||
}
|
||||
}
|
||||
peeker->restart();
|
||||
}
|
||||
|
||||
if (peeker->char_sequence_interrupted()) {
|
||||
// We might have matched a longer sequence, but we were interrupted, e.g. by a signal.
|
||||
FLOG(reader, "torn sequence, rearranging events");
|
||||
return none();
|
||||
}
|
||||
|
||||
if (escape) {
|
||||
// We need to reconsume the escape.
|
||||
peeker->next();
|
||||
return *escape;
|
||||
}
|
||||
|
||||
return generic ? maybe_t<input_mapping_t>(*generic) : none();
|
||||
}
|
||||
|
||||
void inputter_t::mapping_execute_matching_or_generic(const command_handler_t &command_handler) {
|
||||
event_queue_peeker_t peeker(*this);
|
||||
|
||||
// Check for mouse-tracking CSI before mappings to prevent the generic mapping handler from
|
||||
// taking over.
|
||||
if (have_mouse_tracking_csi(&peeker)) {
|
||||
// fish recognizes but does not actually support mouse reporting. We never turn it on, and
|
||||
// it's only ever enabled if a program we spawned enabled it and crashed or forgot to turn
|
||||
// it off before exiting. We swallow the events to prevent garbage from piling up at the
|
||||
// prompt, but don't do anything further with the received codes. To prevent this from
|
||||
// breaking user interaction with the tty emulator, wasting CPU, and adding latency to the
|
||||
// event queue, we turn off mouse reporting here.
|
||||
//
|
||||
// Since this is only called when we detect an incoming mouse reporting payload, we know the
|
||||
// terminal emulator supports the xterm ANSI extensions for mouse reporting and can safely
|
||||
// issue this without worrying about termcap.
|
||||
FLOGF(reader, "Disabling mouse tracking");
|
||||
|
||||
// We can't/shouldn't directly manipulate stdout from `input.cpp`, so request the execution
|
||||
// of a helper function to disable mouse tracking.
|
||||
// writembs(outputter_t::stdoutput(), "\x1B[?1000l");
|
||||
peeker.consume();
|
||||
this->push_front(char_event_t(readline_cmd_t::disable_mouse_tracking, L""));
|
||||
return;
|
||||
}
|
||||
peeker.restart();
|
||||
|
||||
// Check for ordinary mappings.
|
||||
if (auto mapping = find_mapping(&peeker)) {
|
||||
peeker.consume();
|
||||
mapping_execute(*mapping, command_handler);
|
||||
return;
|
||||
}
|
||||
peeker.restart();
|
||||
|
||||
if (peeker.char_sequence_interrupted()) {
|
||||
// This may happen if we received a signal in the middle of an escape sequence or other
|
||||
// multi-char binding. Move these non-char events to the front of the queue, handle them
|
||||
// first, and then later we'll return and try the sequence again. See #8628.
|
||||
peeker.consume();
|
||||
this->promote_interruptions_to_front();
|
||||
return;
|
||||
}
|
||||
|
||||
FLOGF(reader, L"no generic found, ignoring char...");
|
||||
auto evt = peeker.next();
|
||||
peeker.consume();
|
||||
}
|
||||
|
||||
/// Helper function. Picks through the queue of incoming characters until we get to one that's not a
|
||||
/// readline function.
|
||||
char_event_t inputter_t::read_characters_no_readline() {
|
||||
assert(event_storage_.empty() && "saved_events_storage should be empty");
|
||||
auto &saved_events = event_storage_;
|
||||
|
||||
char_event_t evt_to_return{0};
|
||||
for (;;) {
|
||||
auto evt = this->readch();
|
||||
if (evt.is_readline()) {
|
||||
saved_events.push_back(evt);
|
||||
} else {
|
||||
evt_to_return = evt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Restore any readline functions
|
||||
this->insert_front(saved_events.cbegin(), saved_events.cend());
|
||||
event_storage_.clear();
|
||||
return evt_to_return;
|
||||
}
|
||||
|
||||
char_event_t inputter_t::read_char(const command_handler_t &command_handler) {
|
||||
// Clear the interrupted flag.
|
||||
reader_reset_interrupted();
|
||||
// Search for sequence in mapping tables.
|
||||
while (true) {
|
||||
auto evt = this->readch();
|
||||
|
||||
if (evt.is_readline()) {
|
||||
switch (evt.get_readline()) {
|
||||
case readline_cmd_t::self_insert:
|
||||
case readline_cmd_t::self_insert_notfirst: {
|
||||
// Typically self-insert is generated by the generic (empty) binding.
|
||||
// However if it is generated by a real sequence, then insert that sequence.
|
||||
this->insert_front(evt.seq.cbegin(), evt.seq.cend());
|
||||
// Issue #1595: ensure we only insert characters, not readline functions. The
|
||||
// common case is that this will be empty.
|
||||
char_event_t res = read_characters_no_readline();
|
||||
|
||||
// Hackish: mark the input style.
|
||||
res.input_style = evt.get_readline() == readline_cmd_t::self_insert_notfirst
|
||||
? char_input_style_t::notfirst
|
||||
: char_input_style_t::normal;
|
||||
return res;
|
||||
}
|
||||
case readline_cmd_t::func_and:
|
||||
case readline_cmd_t::func_or: {
|
||||
// If previous function has bad status, we want to skip all functions that
|
||||
// follow us.
|
||||
if ((evt.get_readline() == readline_cmd_t::func_and) != function_status_) {
|
||||
drop_leading_readline_events();
|
||||
}
|
||||
continue;
|
||||
}
|
||||
default: {
|
||||
return evt;
|
||||
}
|
||||
}
|
||||
} else if (evt.is_eof()) {
|
||||
// If we have EOF, we need to immediately quit.
|
||||
// There's no need to go through the input functions.
|
||||
return evt;
|
||||
} else if (evt.is_check_exit()) {
|
||||
// Allow the reader to check for exit conditions.
|
||||
return evt;
|
||||
} else {
|
||||
assert(evt.is_char() && "Should be char event");
|
||||
this->push_front(evt);
|
||||
mapping_execute_matching_or_generic(command_handler);
|
||||
// Regarding allow_commands, we're in a loop, but if a fish command is executed,
|
||||
// check_exit is unread, so the next pass through the loop we'll break out and return
|
||||
// it.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<input_mapping_name_t> input_mapping_set_t::get_names(bool user) const {
|
||||
// Sort the mappings by the user specification order, so we can return them in the same order
|
||||
// that the user specified them in.
|
||||
std::vector<input_mapping_t> local_list = user ? mapping_list_ : preset_mapping_list_;
|
||||
std::sort(local_list.begin(), local_list.end(), specification_order_is_less_than);
|
||||
std::vector<input_mapping_name_t> result;
|
||||
result.reserve(local_list.size());
|
||||
|
||||
for (const auto &m : local_list) {
|
||||
result.push_back((input_mapping_name_t){m.seq, m.mode});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void input_mapping_set_t::clear(const wchar_t *mode, bool user) {
|
||||
all_mappings_cache_.reset();
|
||||
mapping_list_t &ml = user ? mapping_list_ : preset_mapping_list_;
|
||||
auto should_erase = [=](const input_mapping_t &m) { return mode == nullptr || mode == m.mode; };
|
||||
ml.erase(std::remove_if(ml.begin(), ml.end(), should_erase), ml.end());
|
||||
}
|
||||
|
||||
bool input_mapping_set_t::erase(const wcstring &sequence, const wcstring &mode, bool user) {
|
||||
// Clear cached mappings.
|
||||
all_mappings_cache_.reset();
|
||||
|
||||
bool result = false;
|
||||
mapping_list_t &ml = user ? mapping_list_ : preset_mapping_list_;
|
||||
for (auto it = ml.begin(), end = ml.end(); it != end; ++it) {
|
||||
if (sequence == it->seq && mode == it->mode) {
|
||||
ml.erase(it);
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool input_mapping_set_t::get(const wcstring &sequence, const wcstring &mode,
|
||||
std::vector<wcstring> *out_cmds, bool user,
|
||||
wcstring *out_sets_mode) const {
|
||||
bool result = false;
|
||||
const auto &ml = user ? mapping_list_ : preset_mapping_list_;
|
||||
for (const input_mapping_t &m : ml) {
|
||||
if (sequence == m.seq && mode == m.mode) {
|
||||
*out_cmds = m.commands;
|
||||
*out_sets_mode = m.sets_mode;
|
||||
result = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::shared_ptr<const mapping_list_t> input_mapping_set_t::all_mappings() {
|
||||
// Populate the cache if needed.
|
||||
if (!all_mappings_cache_) {
|
||||
mapping_list_t all_mappings = mapping_list_;
|
||||
all_mappings.insert(all_mappings.end(), preset_mapping_list_.begin(),
|
||||
preset_mapping_list_.end());
|
||||
all_mappings_cache_ = std::make_shared<const mapping_list_t>(std::move(all_mappings));
|
||||
}
|
||||
return all_mappings_cache_;
|
||||
}
|
||||
|
||||
/// Create a list of terminfo mappings.
|
||||
static std::vector<terminfo_mapping_t> create_input_terminfo() {
|
||||
assert(CURSES_INITIALIZED);
|
||||
if (!cur_term) return {}; // setupterm() failed so we can't referency any key definitions
|
||||
|
||||
#define TERMINFO_ADD(key) \
|
||||
{ (L## #key) + 4, key }
|
||||
|
||||
return {
|
||||
TERMINFO_ADD(key_a1), TERMINFO_ADD(key_a3), TERMINFO_ADD(key_b2),
|
||||
TERMINFO_ADD(key_backspace), TERMINFO_ADD(key_beg), TERMINFO_ADD(key_btab),
|
||||
TERMINFO_ADD(key_c1), TERMINFO_ADD(key_c3), TERMINFO_ADD(key_cancel),
|
||||
TERMINFO_ADD(key_catab), TERMINFO_ADD(key_clear), TERMINFO_ADD(key_close),
|
||||
TERMINFO_ADD(key_command), TERMINFO_ADD(key_copy), TERMINFO_ADD(key_create),
|
||||
TERMINFO_ADD(key_ctab), TERMINFO_ADD(key_dc), TERMINFO_ADD(key_dl), TERMINFO_ADD(key_down),
|
||||
TERMINFO_ADD(key_eic), TERMINFO_ADD(key_end), TERMINFO_ADD(key_enter),
|
||||
TERMINFO_ADD(key_eol), TERMINFO_ADD(key_eos), TERMINFO_ADD(key_exit), TERMINFO_ADD(key_f0),
|
||||
TERMINFO_ADD(key_f1), TERMINFO_ADD(key_f2), TERMINFO_ADD(key_f3), TERMINFO_ADD(key_f4),
|
||||
TERMINFO_ADD(key_f5), TERMINFO_ADD(key_f6), TERMINFO_ADD(key_f7), TERMINFO_ADD(key_f8),
|
||||
TERMINFO_ADD(key_f9), TERMINFO_ADD(key_f10), TERMINFO_ADD(key_f11), TERMINFO_ADD(key_f12),
|
||||
TERMINFO_ADD(key_f13), TERMINFO_ADD(key_f14), TERMINFO_ADD(key_f15), TERMINFO_ADD(key_f16),
|
||||
TERMINFO_ADD(key_f17), TERMINFO_ADD(key_f18), TERMINFO_ADD(key_f19), TERMINFO_ADD(key_f20),
|
||||
// Note key_f21 through key_f63 are available but no actual keyboard supports them.
|
||||
TERMINFO_ADD(key_find), TERMINFO_ADD(key_help), TERMINFO_ADD(key_home),
|
||||
TERMINFO_ADD(key_ic), TERMINFO_ADD(key_il), TERMINFO_ADD(key_left), TERMINFO_ADD(key_ll),
|
||||
TERMINFO_ADD(key_mark), TERMINFO_ADD(key_message), TERMINFO_ADD(key_move),
|
||||
TERMINFO_ADD(key_next), TERMINFO_ADD(key_npage), TERMINFO_ADD(key_open),
|
||||
TERMINFO_ADD(key_options), TERMINFO_ADD(key_ppage), TERMINFO_ADD(key_previous),
|
||||
TERMINFO_ADD(key_print), TERMINFO_ADD(key_redo), TERMINFO_ADD(key_reference),
|
||||
TERMINFO_ADD(key_refresh), TERMINFO_ADD(key_replace), TERMINFO_ADD(key_restart),
|
||||
TERMINFO_ADD(key_resume), TERMINFO_ADD(key_right), TERMINFO_ADD(key_save),
|
||||
TERMINFO_ADD(key_sbeg), TERMINFO_ADD(key_scancel), TERMINFO_ADD(key_scommand),
|
||||
TERMINFO_ADD(key_scopy), TERMINFO_ADD(key_screate), TERMINFO_ADD(key_sdc),
|
||||
TERMINFO_ADD(key_sdl), TERMINFO_ADD(key_select), TERMINFO_ADD(key_send),
|
||||
TERMINFO_ADD(key_seol), TERMINFO_ADD(key_sexit), TERMINFO_ADD(key_sf),
|
||||
TERMINFO_ADD(key_sfind), TERMINFO_ADD(key_shelp), TERMINFO_ADD(key_shome),
|
||||
TERMINFO_ADD(key_sic), TERMINFO_ADD(key_sleft), TERMINFO_ADD(key_smessage),
|
||||
TERMINFO_ADD(key_smove), TERMINFO_ADD(key_snext), TERMINFO_ADD(key_soptions),
|
||||
TERMINFO_ADD(key_sprevious), TERMINFO_ADD(key_sprint), TERMINFO_ADD(key_sr),
|
||||
TERMINFO_ADD(key_sredo), TERMINFO_ADD(key_sreplace), TERMINFO_ADD(key_sright),
|
||||
TERMINFO_ADD(key_srsume), TERMINFO_ADD(key_ssave), TERMINFO_ADD(key_ssuspend),
|
||||
TERMINFO_ADD(key_stab), TERMINFO_ADD(key_sundo), TERMINFO_ADD(key_suspend),
|
||||
TERMINFO_ADD(key_undo), TERMINFO_ADD(key_up),
|
||||
|
||||
// We introduce our own name for the string containing only the nul character - see
|
||||
// #3189. This can typically be generated via control-space.
|
||||
terminfo_mapping_t(k_nul_mapping_name, std::string{'\0'})};
|
||||
#undef TERMINFO_ADD
|
||||
}
|
||||
|
||||
bool input_terminfo_get_sequence(const wcstring &name, wcstring *out_seq) {
|
||||
assert(s_terminfo_mappings.is_set());
|
||||
for (const terminfo_mapping_t &m : *s_terminfo_mappings) {
|
||||
if (name == m.name) {
|
||||
// Found the mapping.
|
||||
if (!m.seq) {
|
||||
errno = EILSEQ;
|
||||
return false;
|
||||
} else {
|
||||
*out_seq = str2wcstring(*m.seq);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
errno = ENOENT;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool input_terminfo_get_name(const wcstring &seq, wcstring *out_name) {
|
||||
assert(s_terminfo_mappings.is_set());
|
||||
for (const terminfo_mapping_t &m : *s_terminfo_mappings) {
|
||||
if (m.seq && seq == str2wcstring(*m.seq)) {
|
||||
out_name->assign(m.name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<wcstring> input_terminfo_get_names(bool skip_null) {
|
||||
assert(s_terminfo_mappings.is_set());
|
||||
std::vector<wcstring> result;
|
||||
const auto &mappings = *s_terminfo_mappings;
|
||||
result.reserve(mappings.size());
|
||||
for (const terminfo_mapping_t &m : mappings) {
|
||||
if (skip_null && !m.seq) {
|
||||
continue;
|
||||
}
|
||||
result.emplace_back(m.name);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const std::vector<wcstring> &input_function_get_names() {
|
||||
// The list and names of input functions are hard-coded and never change
|
||||
static std::vector<wcstring> result = ([&]() {
|
||||
std::vector<wcstring> result;
|
||||
result.reserve(input_function_count);
|
||||
for (const auto &md : input_function_metadata) {
|
||||
if (md.name[0]) {
|
||||
result.push_back(md.name);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
maybe_t<readline_cmd_t> input_function_get_code(const wcstring &name) {
|
||||
// `input_function_metadata` is required to be kept in asciibetical order, making it OK to do
|
||||
// a binary search for the matching name.
|
||||
if (const input_function_metadata_t *md = get_by_sorted_name(name, input_function_metadata)) {
|
||||
return md->code;
|
||||
}
|
||||
return none();
|
||||
}
|
162
src/input.h
162
src/input.h
|
@ -1,162 +0,0 @@
|
|||
// Functions for reading a character of input from stdin, using the inputrc information for key
|
||||
// bindings.
|
||||
#ifndef FISH_INPUT_H
|
||||
#define FISH_INPUT_H
|
||||
|
||||
#include <stddef.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "common.h"
|
||||
#include "input_common.h"
|
||||
#include "maybe.h"
|
||||
#include "parser.h"
|
||||
|
||||
#define FISH_BIND_MODE_VAR L"fish_bind_mode"
|
||||
#define DEFAULT_BIND_MODE L"default"
|
||||
|
||||
class event_queue_peeker_t;
|
||||
|
||||
wcstring describe_char(wint_t c);
|
||||
|
||||
/// Set up arrays used by readch to detect escape sequences for special keys and perform related
|
||||
/// initializations for our input subsystem.
|
||||
void init_input();
|
||||
|
||||
struct input_mapping_t;
|
||||
class inputter_t final : private input_event_queue_t {
|
||||
public:
|
||||
/// Construct from a parser, and the fd from which to read.
|
||||
explicit inputter_t(const parser_t &parser, int in = STDIN_FILENO);
|
||||
|
||||
/// Read a character from stdin. Try to convert some escape sequences into character constants,
|
||||
/// but do not permanently block the escape character.
|
||||
///
|
||||
/// This is performed in the same way vim does it, i.e. if an escape character is read, wait for
|
||||
/// more input for a short time (a few milliseconds). If more input is available, it is assumed
|
||||
/// to be an escape sequence for a special character (such as an arrow key), and readch attempts
|
||||
/// to parse it. If no more input follows after the escape key, it is assumed to be an actual
|
||||
/// escape key press, and is returned as such.
|
||||
///
|
||||
/// \p command_handler is used to run commands. If empty (in the std::function sense), when a
|
||||
/// character is encountered that would invoke a fish command, it is unread and
|
||||
/// char_event_type_t::check_exit is returned. Note the handler is not stored.
|
||||
using command_handler_t = std::function<void(const std::vector<wcstring> &)>;
|
||||
char_event_t read_char(const command_handler_t &command_handler = {});
|
||||
|
||||
/// Enqueue a char event to the queue of unread characters that input_readch will return before
|
||||
/// actually reading from fd 0.
|
||||
void queue_char(const char_event_t &ch);
|
||||
|
||||
/// Sets the return status of the most recently executed input function.
|
||||
void function_set_status(bool status) { function_status_ = status; }
|
||||
|
||||
/// Pop an argument from the function argument stack.
|
||||
wchar_t function_pop_arg();
|
||||
|
||||
private:
|
||||
// Called right before potentially blocking in select().
|
||||
void prepare_to_select() override;
|
||||
|
||||
// Called when select() is interrupted by a signal.
|
||||
void select_interrupted() override;
|
||||
|
||||
// Called when we are notified of a uvar change.
|
||||
void uvar_change_notified() override;
|
||||
|
||||
void function_push_arg(wchar_t arg);
|
||||
void function_push_args(readline_cmd_t code);
|
||||
void mapping_execute(const input_mapping_t &m, const command_handler_t &command_handler);
|
||||
void mapping_execute_matching_or_generic(const command_handler_t &command_handler);
|
||||
maybe_t<input_mapping_t> find_mapping(event_queue_peeker_t *peeker);
|
||||
char_event_t read_characters_no_readline();
|
||||
|
||||
#if INCLUDE_RUST_HEADERS
|
||||
// We need a parser to evaluate bindings.
|
||||
const rust::Box<ParserRef> parser_;
|
||||
#endif
|
||||
|
||||
std::vector<wchar_t> input_function_args_{};
|
||||
bool function_status_{false};
|
||||
|
||||
// Transient storage to avoid repeated allocations.
|
||||
std::vector<char_event_t> event_storage_{};
|
||||
};
|
||||
|
||||
struct input_mapping_name_t {
|
||||
wcstring seq;
|
||||
wcstring mode;
|
||||
};
|
||||
|
||||
/// The input mapping set is the set of mappings from character sequences to commands.
|
||||
class input_mapping_set_t {
|
||||
friend acquired_lock<input_mapping_set_t> input_mappings();
|
||||
friend void init_input();
|
||||
|
||||
using mapping_list_t = std::vector<input_mapping_t>;
|
||||
|
||||
mapping_list_t mapping_list_;
|
||||
mapping_list_t preset_mapping_list_;
|
||||
std::shared_ptr<const mapping_list_t> all_mappings_cache_;
|
||||
|
||||
input_mapping_set_t();
|
||||
|
||||
public:
|
||||
~input_mapping_set_t();
|
||||
|
||||
/// Erase all bindings.
|
||||
void clear(const wchar_t *mode = nullptr, bool user = true);
|
||||
|
||||
/// Erase binding for specified key sequence.
|
||||
bool erase(const wcstring &sequence, const wcstring &mode = DEFAULT_BIND_MODE,
|
||||
bool user = true);
|
||||
|
||||
/// Gets the command bound to the specified key sequence in the specified mode. Returns true if
|
||||
/// it exists, false if not.
|
||||
bool get(const wcstring &sequence, const wcstring &mode, std::vector<wcstring> *out_cmds,
|
||||
bool user, wcstring *out_sets_mode) const;
|
||||
|
||||
/// Returns all mapping names and modes.
|
||||
std::vector<input_mapping_name_t> get_names(bool user = true) const;
|
||||
|
||||
/// Add a key mapping from the specified sequence to the specified command.
|
||||
///
|
||||
/// \param sequence the sequence to bind
|
||||
/// \param command an input function that will be run whenever the key sequence occurs
|
||||
void add(wcstring sequence, const wchar_t *command, const wchar_t *mode = DEFAULT_BIND_MODE,
|
||||
const wchar_t *sets_mode = DEFAULT_BIND_MODE, bool user = true);
|
||||
|
||||
void add(wcstring sequence, const wchar_t *const *commands, size_t commands_len,
|
||||
const wchar_t *mode = DEFAULT_BIND_MODE, const wchar_t *sets_mode = DEFAULT_BIND_MODE,
|
||||
bool user = true);
|
||||
|
||||
/// \return a snapshot of the list of input mappings.
|
||||
std::shared_ptr<const mapping_list_t> all_mappings();
|
||||
};
|
||||
|
||||
/// Access the singleton input mapping set.
|
||||
acquired_lock<input_mapping_set_t> input_mappings();
|
||||
|
||||
/// Return the sequence for the terminfo variable of the specified name.
|
||||
///
|
||||
/// If no terminfo variable of the specified name could be found, return false and set errno to
|
||||
/// ENOENT. If the terminfo variable does not have a value, return false and set errno to EILSEQ.
|
||||
bool input_terminfo_get_sequence(const wcstring &name, wcstring *out_seq);
|
||||
|
||||
/// Return the name of the terminfo variable with the specified sequence, in out_name. Returns true
|
||||
/// if found, false if not found.
|
||||
bool input_terminfo_get_name(const wcstring &seq, wcstring *out_name);
|
||||
|
||||
/// Return a list of all known terminfo names.
|
||||
std::vector<wcstring> input_terminfo_get_names(bool skip_null);
|
||||
|
||||
/// Returns the input function code for the given input function name.
|
||||
maybe_t<readline_cmd_t> input_function_get_code(const wcstring &name);
|
||||
|
||||
/// Returns a list of all existing input function names.
|
||||
const std::vector<wcstring> &input_function_get_names(void);
|
||||
|
||||
#endif
|
|
@ -1,341 +0,0 @@
|
|||
// Implementation file for the low level input library.
|
||||
#include "config.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <pthread.h> // IWYU pragma: keep
|
||||
#include <signal.h>
|
||||
#include <stdio.h>
|
||||
#include <sys/types.h>
|
||||
#ifdef HAVE_SYS_SELECT_H
|
||||
#include <sys/select.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <climits>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <cwchar>
|
||||
#include <deque>
|
||||
#include <utility>
|
||||
|
||||
#include "common.h"
|
||||
#include "env.h"
|
||||
#include "env_universal_common.h"
|
||||
#include "fallback.h" // IWYU pragma: keep
|
||||
#include "fd_readable_set.rs.h"
|
||||
#include "fds.h"
|
||||
#include "flog.h"
|
||||
#include "input_common.h"
|
||||
#include "iothread.h"
|
||||
#include "wutil.h"
|
||||
|
||||
/// Time in milliseconds to wait for another byte to be available for reading
|
||||
/// after \x1B is read before assuming that escape key was pressed, and not an
|
||||
/// escape sequence.
|
||||
#define WAIT_ON_ESCAPE_DEFAULT 30
|
||||
static int wait_on_escape_ms = WAIT_ON_ESCAPE_DEFAULT;
|
||||
|
||||
#define WAIT_ON_SEQUENCE_KEY_INFINITE (-1)
|
||||
static int wait_on_sequence_key_ms = WAIT_ON_SEQUENCE_KEY_INFINITE;
|
||||
|
||||
input_event_queue_t::input_event_queue_t(int in) : in_(in) {}
|
||||
|
||||
/// Internal function used by readch to read one byte.
|
||||
/// This calls select() on three fds: input (e.g. stdin), the ioport notifier fd (for main thread
|
||||
/// requests), and the uvar notifier. This returns either the byte which was read, or one of the
|
||||
/// special values below.
|
||||
enum {
|
||||
// The in fd has been closed.
|
||||
readb_eof = -1,
|
||||
|
||||
// select() was interrupted by a signal.
|
||||
readb_interrupted = -2,
|
||||
|
||||
// Our uvar notifier reported a change (either through poll() or its fd).
|
||||
readb_uvar_notified = -3,
|
||||
|
||||
// Our ioport reported a change, so service main thread requests.
|
||||
readb_ioport_notified = -4,
|
||||
};
|
||||
using readb_result_t = int;
|
||||
|
||||
static readb_result_t readb(int in_fd) {
|
||||
assert(in_fd >= 0 && "Invalid in fd");
|
||||
auto notifier_box = default_notifier();
|
||||
const auto& notifier = *notifier_box;
|
||||
auto fdset_box = new_fd_readable_set();
|
||||
fd_readable_set_t& fdset = *fdset_box;
|
||||
for (;;) {
|
||||
fdset.clear();
|
||||
fdset.add(in_fd);
|
||||
|
||||
// Add the completion ioport.
|
||||
int ioport_fd = iothread_port();
|
||||
fdset.add(ioport_fd);
|
||||
|
||||
// Get the uvar notifier fd (possibly none).
|
||||
int notifier_fd = notifier.notification_fd();
|
||||
fdset.add(notifier_fd);
|
||||
|
||||
// Here's where we call select().
|
||||
int select_res = fdset.check_readable(kNoTimeout);
|
||||
if (select_res < 0) {
|
||||
if (errno == EINTR || errno == EAGAIN) {
|
||||
// A signal.
|
||||
return readb_interrupted;
|
||||
} else {
|
||||
// Some fd was invalid, so probably the tty has been closed.
|
||||
return readb_eof;
|
||||
}
|
||||
}
|
||||
|
||||
// select() did not return an error, so we may have a readable fd.
|
||||
// The priority order is: uvars, stdin, ioport.
|
||||
// Check to see if we want a universal variable barrier.
|
||||
// This may come about through readability, or through a call to poll().
|
||||
if (fdset.test(notifier_fd) && notifier.notification_fd_became_readable(notifier_fd)) {
|
||||
return readb_uvar_notified;
|
||||
}
|
||||
|
||||
// Check stdin.
|
||||
if (fdset.test(in_fd)) {
|
||||
unsigned char arr[1];
|
||||
if (read_blocked(in_fd, arr, 1) != 1) {
|
||||
// The terminal has been closed.
|
||||
return readb_eof;
|
||||
}
|
||||
// The common path is to return a (non-negative) char.
|
||||
return static_cast<int>(arr[0]);
|
||||
}
|
||||
|
||||
// Check for iothread completions only if there is no data to be read from the stdin.
|
||||
// This gives priority to the foreground.
|
||||
if (fdset.test(ioport_fd)) {
|
||||
return readb_ioport_notified;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the wait_on_escape_ms value in response to the fish_escape_delay_ms user variable being
|
||||
// set.
|
||||
void update_wait_on_escape_ms(const environment_t& vars) {
|
||||
auto escape_time_ms = vars.get_unless_empty(L"fish_escape_delay_ms");
|
||||
if (!escape_time_ms) {
|
||||
wait_on_escape_ms = WAIT_ON_ESCAPE_DEFAULT;
|
||||
return;
|
||||
}
|
||||
|
||||
long tmp = fish_wcstol(escape_time_ms->as_string().c_str());
|
||||
if (errno || tmp < 10 || tmp >= 5000) {
|
||||
std::fwprintf(stderr,
|
||||
L"ignoring fish_escape_delay_ms: value '%ls' "
|
||||
L"is not an integer or is < 10 or >= 5000 ms\n",
|
||||
escape_time_ms->as_string().c_str());
|
||||
} else {
|
||||
wait_on_escape_ms = static_cast<int>(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void update_wait_on_escape_ms_ffi(bool empty, const wcstring& fish_escape_delay_ms) {
|
||||
if (empty) {
|
||||
wait_on_escape_ms = WAIT_ON_ESCAPE_DEFAULT;
|
||||
return;
|
||||
}
|
||||
|
||||
long tmp = fish_wcstol(fish_escape_delay_ms.c_str());
|
||||
if (errno || tmp < 10 || tmp >= 5000) {
|
||||
std::fwprintf(stderr,
|
||||
L"ignoring fish_escape_delay_ms: value '%ls' "
|
||||
L"is not an integer or is < 10 or >= 5000 ms\n",
|
||||
fish_escape_delay_ms.c_str());
|
||||
} else {
|
||||
wait_on_escape_ms = static_cast<int>(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
// Update the wait_on_sequence_key_ms value in response to the fish_sequence_key_delay_ms user
|
||||
// variable being set.
|
||||
void update_wait_on_sequence_key_ms(const environment_t& vars) {
|
||||
auto sequence_key_time_ms = vars.get_unless_empty(L"fish_sequence_key_delay_ms");
|
||||
if (!sequence_key_time_ms) {
|
||||
wait_on_sequence_key_ms = WAIT_ON_SEQUENCE_KEY_INFINITE;
|
||||
return;
|
||||
}
|
||||
|
||||
long tmp = fish_wcstol(sequence_key_time_ms->as_string().c_str());
|
||||
if (errno || tmp < 10 || tmp >= 5000) {
|
||||
std::fwprintf(stderr,
|
||||
L"ignoring fish_sequence_key_delay_ms: value '%ls' "
|
||||
L"is not an integer or is < 10 or >= 5000 ms\n",
|
||||
sequence_key_time_ms->as_string().c_str());
|
||||
} else {
|
||||
wait_on_sequence_key_ms = static_cast<int>(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void update_wait_on_sequence_key_ms_ffi(bool empty, const wcstring& fish_sequence_key_delay_ms) {
|
||||
if (empty) {
|
||||
wait_on_sequence_key_ms = WAIT_ON_SEQUENCE_KEY_INFINITE;
|
||||
return;
|
||||
}
|
||||
|
||||
long tmp = fish_wcstol(fish_sequence_key_delay_ms.c_str());
|
||||
if (errno || tmp < 10 || tmp >= 5000) {
|
||||
std::fwprintf(stderr,
|
||||
L"ignoring fish_sequence_key_delay_ms: value '%ls' "
|
||||
L"is not an integer or is < 10 or >= 5000 ms\n",
|
||||
fish_sequence_key_delay_ms.c_str());
|
||||
} else {
|
||||
wait_on_sequence_key_ms = static_cast<int>(tmp);
|
||||
}
|
||||
}
|
||||
|
||||
maybe_t<char_event_t> input_event_queue_t::try_pop() {
|
||||
if (queue_.empty()) {
|
||||
return none();
|
||||
}
|
||||
auto result = std::move(queue_.front());
|
||||
queue_.pop_front();
|
||||
return result;
|
||||
}
|
||||
|
||||
char_event_t input_event_queue_t::readch() {
|
||||
wchar_t res{};
|
||||
mbstate_t state = {};
|
||||
for (;;) {
|
||||
// Do we have something enqueued already?
|
||||
// Note this may be initially true, or it may become true through calls to
|
||||
// iothread_service_main() or env_universal_barrier() below.
|
||||
if (auto mevt = try_pop()) {
|
||||
return mevt.acquire();
|
||||
}
|
||||
|
||||
// We are going to block; but first allow any override to inject events.
|
||||
this->prepare_to_select();
|
||||
if (auto mevt = try_pop()) {
|
||||
return mevt.acquire();
|
||||
}
|
||||
|
||||
readb_result_t rr = readb(in_);
|
||||
switch (rr) {
|
||||
case readb_eof:
|
||||
return char_event_type_t::eof;
|
||||
|
||||
case readb_interrupted:
|
||||
// FIXME: here signals may break multibyte sequences.
|
||||
this->select_interrupted();
|
||||
break;
|
||||
|
||||
case readb_uvar_notified:
|
||||
this->uvar_change_notified();
|
||||
break;
|
||||
|
||||
case readb_ioport_notified:
|
||||
iothread_service_main();
|
||||
break;
|
||||
|
||||
default: {
|
||||
assert(rr >= 0 && rr <= UCHAR_MAX &&
|
||||
"Read byte out of bounds - missing error case?");
|
||||
char read_byte = static_cast<char>(static_cast<unsigned char>(rr));
|
||||
if (MB_CUR_MAX == 1) {
|
||||
// single-byte locale, all values are legal
|
||||
res = read_byte;
|
||||
return res;
|
||||
}
|
||||
size_t sz = std::mbrtowc(&res, &read_byte, 1, &state);
|
||||
switch (sz) {
|
||||
case static_cast<size_t>(-1):
|
||||
std::memset(&state, '\0', sizeof(state));
|
||||
FLOG(reader, L"Illegal input");
|
||||
return char_event_type_t::check_exit;
|
||||
|
||||
case static_cast<size_t>(-2):
|
||||
// Sequence not yet complete.
|
||||
break;
|
||||
|
||||
case 0:
|
||||
// Actual nul char.
|
||||
return 0;
|
||||
|
||||
default:
|
||||
// Sequence complete.
|
||||
return res;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
maybe_t<char_event_t> input_event_queue_t::readch_timed_esc() {
|
||||
return readch_timed(wait_on_escape_ms);
|
||||
}
|
||||
|
||||
maybe_t<char_event_t> input_event_queue_t::readch_timed_sequence_key() {
|
||||
if (wait_on_sequence_key_ms == WAIT_ON_SEQUENCE_KEY_INFINITE) {
|
||||
return readch();
|
||||
}
|
||||
return readch_timed(wait_on_sequence_key_ms);
|
||||
}
|
||||
|
||||
maybe_t<char_event_t> input_event_queue_t::readch_timed(const int wait_time_ms) {
|
||||
if (auto evt = try_pop()) {
|
||||
return evt;
|
||||
}
|
||||
// We are not prepared to handle a signal immediately; we only want to know if we get input on
|
||||
// our fd before the timeout. Use pselect to block all signals; we will handle signals
|
||||
// before the next call to readch().
|
||||
sigset_t sigs;
|
||||
sigfillset(&sigs);
|
||||
|
||||
// pselect expects timeouts in nanoseconds.
|
||||
const uint64_t nsec_per_msec = 1000 * 1000;
|
||||
const uint64_t nsec_per_sec = nsec_per_msec * 1000;
|
||||
const uint64_t wait_nsec = wait_time_ms * nsec_per_msec;
|
||||
struct timespec timeout;
|
||||
timeout.tv_sec = (wait_nsec) / nsec_per_sec;
|
||||
timeout.tv_nsec = (wait_nsec) % nsec_per_sec;
|
||||
|
||||
// We have one fd of interest.
|
||||
fd_set fdset;
|
||||
FD_ZERO(&fdset);
|
||||
FD_SET(in_, &fdset);
|
||||
|
||||
int res = pselect(in_ + 1, &fdset, nullptr, nullptr, &timeout, &sigs);
|
||||
|
||||
// Prevent signal starvation on WSL causing the `torn_escapes.py` test to fail
|
||||
if (is_windows_subsystem_for_linux()) {
|
||||
// Merely querying the current thread's sigmask is sufficient to deliver a pending signal
|
||||
pthread_sigmask(0, nullptr, &sigs);
|
||||
}
|
||||
|
||||
if (res > 0) {
|
||||
return readch();
|
||||
}
|
||||
return none();
|
||||
}
|
||||
|
||||
void input_event_queue_t::push_back(const char_event_t& ch) { queue_.push_back(ch); }
|
||||
|
||||
void input_event_queue_t::push_front(const char_event_t& ch) { queue_.push_front(ch); }
|
||||
|
||||
void input_event_queue_t::promote_interruptions_to_front() {
|
||||
// Find the first sequence of non-char events.
|
||||
// EOF is considered a char: we don't want to pull EOF in front of real chars.
|
||||
auto is_char = [](const char_event_t& ch) { return ch.is_char() || ch.is_eof(); };
|
||||
auto first = std::find_if_not(queue_.begin(), queue_.end(), is_char);
|
||||
auto last = std::find_if(first, queue_.end(), is_char);
|
||||
std::rotate(queue_.begin(), first, last);
|
||||
}
|
||||
|
||||
void input_event_queue_t::drop_leading_readline_events() {
|
||||
queue_.erase(queue_.begin(),
|
||||
std::find_if(queue_.begin(), queue_.end(),
|
||||
[](const char_event_t& evt) { return !evt.is_readline(); }));
|
||||
}
|
||||
|
||||
void input_event_queue_t::prepare_to_select() {}
|
||||
void input_event_queue_t::select_interrupted() {}
|
||||
void input_event_queue_t::uvar_change_notified() {}
|
||||
input_event_queue_t::~input_event_queue_t() = default;
|
|
@ -1,263 +0,0 @@
|
|||
// Header file for the low level input library.
|
||||
#ifndef INPUT_COMMON_H
|
||||
#define INPUT_COMMON_H
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <deque>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
|
||||
#include "common.h"
|
||||
#include "env.h"
|
||||
#include "maybe.h"
|
||||
|
||||
enum class readline_cmd_t {
|
||||
beginning_of_line,
|
||||
end_of_line,
|
||||
forward_char,
|
||||
backward_char,
|
||||
forward_single_char,
|
||||
forward_word,
|
||||
backward_word,
|
||||
forward_bigword,
|
||||
backward_bigword,
|
||||
nextd_or_forward_word,
|
||||
prevd_or_backward_word,
|
||||
history_search_backward,
|
||||
history_search_forward,
|
||||
history_prefix_search_backward,
|
||||
history_prefix_search_forward,
|
||||
history_pager,
|
||||
history_pager_delete,
|
||||
delete_char,
|
||||
backward_delete_char,
|
||||
kill_line,
|
||||
yank,
|
||||
yank_pop,
|
||||
complete,
|
||||
complete_and_search,
|
||||
pager_toggle_search,
|
||||
beginning_of_history,
|
||||
end_of_history,
|
||||
backward_kill_line,
|
||||
kill_whole_line,
|
||||
kill_inner_line,
|
||||
kill_word,
|
||||
kill_bigword,
|
||||
backward_kill_word,
|
||||
backward_kill_path_component,
|
||||
backward_kill_bigword,
|
||||
history_token_search_backward,
|
||||
history_token_search_forward,
|
||||
self_insert,
|
||||
self_insert_notfirst,
|
||||
transpose_chars,
|
||||
transpose_words,
|
||||
upcase_word,
|
||||
downcase_word,
|
||||
capitalize_word,
|
||||
togglecase_char,
|
||||
togglecase_selection,
|
||||
execute,
|
||||
beginning_of_buffer,
|
||||
end_of_buffer,
|
||||
repaint_mode,
|
||||
repaint,
|
||||
force_repaint,
|
||||
up_line,
|
||||
down_line,
|
||||
suppress_autosuggestion,
|
||||
accept_autosuggestion,
|
||||
begin_selection,
|
||||
swap_selection_start_stop,
|
||||
end_selection,
|
||||
kill_selection,
|
||||
insert_line_under,
|
||||
insert_line_over,
|
||||
forward_jump,
|
||||
backward_jump,
|
||||
forward_jump_till,
|
||||
backward_jump_till,
|
||||
func_and,
|
||||
func_or,
|
||||
expand_abbr,
|
||||
delete_or_exit,
|
||||
exit,
|
||||
cancel_commandline,
|
||||
cancel,
|
||||
undo,
|
||||
redo,
|
||||
begin_undo_group,
|
||||
end_undo_group,
|
||||
repeat_jump,
|
||||
disable_mouse_tracking,
|
||||
// ncurses uses the obvious name
|
||||
clear_screen_and_repaint,
|
||||
// NOTE: This one has to be last.
|
||||
reverse_repeat_jump
|
||||
};
|
||||
|
||||
// The range of key codes for inputrc-style keyboard functions.
|
||||
enum { R_END_INPUT_FUNCTIONS = static_cast<int>(readline_cmd_t::reverse_repeat_jump) + 1 };
|
||||
|
||||
/// Represents an event on the character input stream.
|
||||
enum class char_event_type_t : uint8_t {
|
||||
/// A character was entered.
|
||||
charc,
|
||||
|
||||
/// A readline event.
|
||||
readline,
|
||||
|
||||
/// end-of-file was reached.
|
||||
eof,
|
||||
|
||||
/// An event was handled internally, or an interrupt was received. Check to see if the reader
|
||||
/// loop should exit.
|
||||
check_exit,
|
||||
};
|
||||
|
||||
/// Hackish: the input style, which describes how char events (only) are applied to the command
|
||||
/// line. Note this is set only after applying bindings; it is not set from readb().
|
||||
enum class char_input_style_t : uint8_t {
|
||||
// Insert characters normally.
|
||||
normal,
|
||||
|
||||
// Insert characters only if the cursor is not at the beginning. Otherwise, discard them.
|
||||
notfirst,
|
||||
};
|
||||
|
||||
class char_event_t {
|
||||
union {
|
||||
/// Set if the type is charc.
|
||||
wchar_t c;
|
||||
|
||||
/// Set if the type is readline.
|
||||
readline_cmd_t rl;
|
||||
} v_{};
|
||||
|
||||
public:
|
||||
/// The type of event.
|
||||
char_event_type_t type;
|
||||
|
||||
/// The style to use when inserting characters into the command line.
|
||||
char_input_style_t input_style{char_input_style_t::normal};
|
||||
|
||||
/// The sequence of characters in the input mapping which generated this event.
|
||||
/// Note that the generic self-insert case does not have any characters, so this would be empty.
|
||||
wcstring seq{};
|
||||
|
||||
bool is_char() const { return type == char_event_type_t::charc; }
|
||||
|
||||
bool is_eof() const { return type == char_event_type_t::eof; }
|
||||
|
||||
bool is_check_exit() const { return type == char_event_type_t::check_exit; }
|
||||
|
||||
bool is_readline() const { return type == char_event_type_t::readline; }
|
||||
|
||||
wchar_t get_char() const {
|
||||
assert(type == char_event_type_t::charc && "Not a char type");
|
||||
return v_.c;
|
||||
}
|
||||
|
||||
maybe_t<wchar_t> maybe_char() const {
|
||||
if (type == char_event_type_t::charc) {
|
||||
return v_.c;
|
||||
} else {
|
||||
return none();
|
||||
}
|
||||
}
|
||||
|
||||
readline_cmd_t get_readline() const {
|
||||
assert(type == char_event_type_t::readline && "Not a readline type");
|
||||
return v_.rl;
|
||||
}
|
||||
|
||||
/* implicit */ char_event_t(wchar_t c) : type(char_event_type_t::charc) { v_.c = c; }
|
||||
|
||||
/* implicit */ char_event_t(readline_cmd_t rl, wcstring seq = {})
|
||||
: type(char_event_type_t::readline), seq(std::move(seq)) {
|
||||
v_.rl = rl;
|
||||
}
|
||||
|
||||
/* implicit */ char_event_t(char_event_type_t type) : type(type) {
|
||||
assert(type != char_event_type_t::charc && type != char_event_type_t::readline &&
|
||||
"Cannot create a char event with this constructor");
|
||||
}
|
||||
};
|
||||
|
||||
/// Adjust the escape timeout.
|
||||
void update_wait_on_escape_ms(const environment_t &vars);
|
||||
void update_wait_on_escape_ms_ffi(bool empty, const wcstring &fish_escape_delay_ms);
|
||||
|
||||
void update_wait_on_sequence_key_ms(const environment_t &vars);
|
||||
void update_wait_on_sequence_key_ms_ffi(bool empty, const wcstring &fish_sequence_key_delay_ms);
|
||||
|
||||
/// A class which knows how to produce a stream of input events.
|
||||
/// This is a base class; you may subclass it for its override points.
|
||||
class input_event_queue_t {
|
||||
public:
|
||||
/// Construct from a file descriptor \p in, and an interrupt handler \p handler.
|
||||
explicit input_event_queue_t(int in = STDIN_FILENO);
|
||||
|
||||
/// Function used by input_readch to read bytes from stdin until enough bytes have been read to
|
||||
/// convert them to a wchar_t. Conversion is done using mbrtowc. If a character has previously
|
||||
/// been read and then 'unread' using \c input_common_unreadch, that character is returned.
|
||||
char_event_t readch();
|
||||
|
||||
/// Like readch(), except it will wait at most WAIT_ON_ESCAPE milliseconds for a
|
||||
/// character to be available for reading.
|
||||
/// \return none on timeout, the event on success.
|
||||
maybe_t<char_event_t> readch_timed(const int wait_time_ms);
|
||||
|
||||
maybe_t<char_event_t> readch_timed_esc();
|
||||
maybe_t<char_event_t> readch_timed_sequence_key();
|
||||
|
||||
/// Enqueue a character or a readline function to the queue of unread characters that
|
||||
/// readch will return before actually reading from fd 0.
|
||||
void push_back(const char_event_t &ch);
|
||||
|
||||
/// Add a character or a readline function to the front of the queue of unread characters. This
|
||||
/// will be the next character returned by readch.
|
||||
void push_front(const char_event_t &ch);
|
||||
|
||||
/// Find the first sequence of non-char events, and promote them to the front.
|
||||
void promote_interruptions_to_front();
|
||||
|
||||
/// Add multiple characters or readline events to the front of the queue of unread characters.
|
||||
/// The order of the provided events is not changed, i.e. they are not inserted in reverse
|
||||
/// order.
|
||||
template <typename Iterator>
|
||||
void insert_front(const Iterator begin, const Iterator end) {
|
||||
queue_.insert(queue_.begin(), begin, end);
|
||||
}
|
||||
|
||||
/// Forget all enqueued readline events in the front of the queue.
|
||||
void drop_leading_readline_events();
|
||||
|
||||
/// Override point for when we are about to (potentially) block in select(). The default does
|
||||
/// nothing.
|
||||
virtual void prepare_to_select();
|
||||
|
||||
/// Override point for when when select() is interrupted by a signal. The default does nothing.
|
||||
virtual void select_interrupted();
|
||||
|
||||
/// Override point for when when select() is interrupted by the universal variable notifier.
|
||||
/// The default does nothing.
|
||||
virtual void uvar_change_notified();
|
||||
|
||||
virtual ~input_event_queue_t();
|
||||
|
||||
private:
|
||||
/// \return if we have any lookahead.
|
||||
bool has_lookahead() const { return !queue_.empty(); }
|
||||
|
||||
/// \return the next event in the queue, or none if the queue is empty.
|
||||
maybe_t<char_event_t> try_pop();
|
||||
|
||||
int in_{0};
|
||||
std::deque<char_event_t> queue_;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
struct Parser;
|
||||
using parser_t = Parser;
|
||||
struct ParserRef;
|
||||
|
||||
#if INCLUDE_RUST_HEADERS
|
||||
#include "parser.rs.h"
|
||||
|
|
464
src/reader.cpp
464
src/reader.cpp
File diff suppressed because it is too large
Load diff
|
@ -20,6 +20,7 @@
|
|||
#include "maybe.h"
|
||||
#include "parse_constants.h"
|
||||
#include "parser.h"
|
||||
#include "wutil.h"
|
||||
|
||||
#if INCLUDE_RUST_HEADERS
|
||||
#include "reader.rs.h"
|
||||
|
@ -79,8 +80,9 @@ void reader_write_title_ffi(const wcstring &cmd, const void *parser, bool reset_
|
|||
void reader_schedule_prompt_repaint();
|
||||
|
||||
/// Enqueue an event to the back of the reader's input queue.
|
||||
class char_event_t;
|
||||
void reader_queue_ch(const char_event_t &ch);
|
||||
struct CharEvent;
|
||||
using char_event_t = CharEvent;
|
||||
void reader_queue_ch(rust::Box<char_event_t> ch);
|
||||
|
||||
/// Return the value of the interrupted flag, which is set by the sigint handler, and clear it if it
|
||||
/// was set. If the current reader is interruptible, call \c reader_exit().
|
||||
|
|
Loading…
Reference in a new issue