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