2016-05-01 12:31:25 +08:00
|
|
|
// Functions for reading a character of input from stdin.
|
2005-09-20 21:26:39 +08:00
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include <errno.h>
|
2016-11-27 08:35:48 +08:00
|
|
|
#include <wctype.h>
|
2019-10-14 06:50:48 +08:00
|
|
|
|
2019-05-05 18:09:25 +08:00
|
|
|
#include <cwchar>
|
2006-01-19 20:22:07 +08:00
|
|
|
#if HAVE_TERM_H
|
2018-02-04 16:59:37 +08:00
|
|
|
#include <curses.h>
|
2005-09-20 21:26:39 +08:00
|
|
|
#include <term.h>
|
2006-01-19 20:22:07 +08:00
|
|
|
#elif HAVE_NCURSES_TERM_H
|
|
|
|
#include <ncurses/term.h>
|
|
|
|
#endif
|
2018-02-04 16:59:37 +08:00
|
|
|
#include <termios.h>
|
2016-11-27 08:35:48 +08:00
|
|
|
|
2016-04-21 14:00:54 +08:00
|
|
|
#include <algorithm>
|
2019-03-04 04:59:55 +08:00
|
|
|
#include <atomic>
|
2016-04-21 14:00:54 +08:00
|
|
|
#include <memory>
|
|
|
|
#include <string>
|
2019-12-27 13:54:21 +08:00
|
|
|
#include <utility>
|
2016-05-01 12:31:25 +08:00
|
|
|
#include <vector>
|
2005-09-20 21:26:39 +08:00
|
|
|
|
|
|
|
#include "common.h"
|
|
|
|
#include "env.h"
|
2005-11-25 22:18:26 +08:00
|
|
|
#include "event.h"
|
2016-05-01 12:31:25 +08:00
|
|
|
#include "fallback.h" // IWYU pragma: keep
|
2019-04-29 10:16:55 +08:00
|
|
|
#include "global_safety.h"
|
2016-05-01 12:31:25 +08:00
|
|
|
#include "input.h"
|
|
|
|
#include "input_common.h"
|
2015-07-25 23:14:25 +08:00
|
|
|
#include "io.h"
|
2016-05-01 12:31:25 +08:00
|
|
|
#include "parser.h"
|
|
|
|
#include "proc.h"
|
|
|
|
#include "reader.h"
|
|
|
|
#include "signal.h" // IWYU pragma: keep
|
|
|
|
#include "wutil.h" // IWYU pragma: keep
|
2007-09-26 00:14:47 +08:00
|
|
|
|
2019-09-15 07:36:57 +08:00
|
|
|
/// A name for our own key mapping for nul.
|
|
|
|
static const wchar_t *k_nul_mapping_name = L"nul";
|
|
|
|
|
2016-05-01 12:31:25 +08:00
|
|
|
/// 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.
|
|
|
|
wcstring_list_t commands;
|
|
|
|
/// We wish to preserve the user-specified order. This is just an incrementing value.
|
2014-02-13 04:49:32 +08:00
|
|
|
unsigned int specification_order;
|
2016-05-01 12:31:25 +08:00
|
|
|
/// Mode in which this command should be evaluated.
|
|
|
|
wcstring mode;
|
|
|
|
/// New mode that should be switched to after command evaluation.
|
|
|
|
wcstring sets_mode;
|
2014-04-01 01:01:39 +08:00
|
|
|
|
2019-03-15 02:15:50 +08:00
|
|
|
input_mapping_t(wcstring s, wcstring_list_t c, wcstring m, wcstring sm)
|
2018-02-19 10:39:03 +08:00
|
|
|
: seq(std::move(s)), commands(std::move(c)), mode(std::move(m)), sets_mode(std::move(sm)) {
|
2016-11-03 12:54:57 +08:00
|
|
|
static unsigned int s_last_input_map_spec_order = 0;
|
|
|
|
specification_order = ++s_last_input_map_spec_order;
|
2014-02-13 04:49:32 +08:00
|
|
|
}
|
2019-03-15 16:11:15 +08:00
|
|
|
|
|
|
|
/// \return true if this is a generic mapping, i.e. acts as a fallback.
|
|
|
|
bool is_generic() const { return seq.empty(); }
|
2012-01-01 07:57:30 +08:00
|
|
|
};
|
2005-09-20 21:26:39 +08:00
|
|
|
|
2016-05-01 12:31:25 +08:00
|
|
|
/// A struct representing the mapping from a terminfo key name to a terminfo character sequence.
|
|
|
|
struct terminfo_mapping_t {
|
2019-09-15 07:36:57 +08:00
|
|
|
// 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)) {}
|
2012-02-08 15:15:32 +08:00
|
|
|
};
|
2007-09-26 00:14:47 +08:00
|
|
|
|
2019-05-30 02:36:01 +08:00
|
|
|
static constexpr size_t input_function_count = R_END_INPUT_FUNCTIONS;
|
2018-01-30 11:15:16 +08:00
|
|
|
|
|
|
|
/// 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;
|
2021-02-08 06:59:07 +08:00
|
|
|
readline_cmd_t code;
|
2018-01-30 11:15:16 +08:00
|
|
|
};
|
2019-03-17 08:56:35 +08:00
|
|
|
|
2021-02-08 06:59:07 +08:00
|
|
|
/// A static mapping of all readline commands as strings to their readline_cmd_t equivalent.
|
|
|
|
/// Keep this list sorted alphabetically!
|
2018-01-30 11:15:16 +08:00
|
|
|
static const input_function_metadata_t input_function_metadata[] = {
|
2021-02-08 02:49:51 +08:00
|
|
|
// NULL makes it unusable - this is specially inserted when we detect mouse input
|
2021-02-08 06:59:07 +08:00
|
|
|
{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"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-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-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"or", readline_cmd_t::func_or},
|
|
|
|
{L"pager-toggle-search", readline_cmd_t::pager_toggle_search},
|
|
|
|
{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},
|
2020-02-04 19:47:44 +08:00
|
|
|
};
|
2018-01-30 11:15:16 +08:00
|
|
|
|
|
|
|
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?");
|
2016-05-01 12:31:25 +08:00
|
|
|
|
|
|
|
wcstring describe_char(wint_t c) {
|
2019-05-30 02:36:01 +08:00
|
|
|
if (c < R_END_INPUT_FUNCTIONS) {
|
|
|
|
return format_string(L"%02x (%ls)", c, input_function_metadata[c].name);
|
2014-01-22 08:08:35 +08:00
|
|
|
}
|
|
|
|
return format_string(L"%02x", c);
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2019-04-29 10:16:55 +08:00
|
|
|
using mapping_list_t = std::vector<input_mapping_t>;
|
2019-06-03 13:36:11 +08:00
|
|
|
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();
|
|
|
|
}
|
2012-02-08 15:15:32 +08:00
|
|
|
|
2016-05-01 12:31:25 +08:00
|
|
|
/// Terminfo map list.
|
2019-04-29 10:16:55 +08:00
|
|
|
static latch_t<std::vector<terminfo_mapping_t>> s_terminfo_mappings;
|
2012-02-08 15:15:32 +08:00
|
|
|
|
2019-04-29 10:16:55 +08:00
|
|
|
/// \return the input terminfo.
|
|
|
|
static std::vector<terminfo_mapping_t> create_input_terminfo();
|
2012-02-08 15:15:32 +08:00
|
|
|
|
2016-05-01 12:31:25 +08:00
|
|
|
/// Return the current bind mode.
|
2019-06-03 07:57:52 +08:00
|
|
|
static wcstring input_get_bind_mode(const environment_t &vars) {
|
2018-09-14 15:36:26 +08:00
|
|
|
auto mode = vars.get(FISH_BIND_MODE_VAR);
|
2017-08-28 15:25:41 +08:00
|
|
|
return mode ? mode->as_string() : DEFAULT_BIND_MODE;
|
2013-12-31 08:52:41 +08:00
|
|
|
}
|
|
|
|
|
2016-05-01 12:31:25 +08:00
|
|
|
/// Set the current bind mode.
|
2020-03-08 11:44:58 +08:00
|
|
|
static void input_set_bind_mode(parser_t &parser, const wcstring &bm) {
|
2016-09-05 06:54:25 +08:00
|
|
|
// Only set this if it differs to not execute variable handlers all the time.
|
2017-02-01 06:49:56 +08:00
|
|
|
// modes may not be empty - empty is a sentinel value meaning to not change the mode
|
2017-02-08 13:52:35 +08:00
|
|
|
assert(!bm.empty());
|
2020-03-08 11:44:58 +08:00
|
|
|
if (input_get_bind_mode(parser.vars()) != bm) {
|
|
|
|
// Must send events here - see #6653.
|
|
|
|
parser.set_var_and_fire(FISH_BIND_MODE_VAR, ENV_GLOBAL, bm);
|
2016-09-05 06:54:25 +08:00
|
|
|
}
|
2013-12-31 08:52:41 +08:00
|
|
|
}
|
2006-07-24 04:52:03 +08:00
|
|
|
|
2016-05-01 12:31:25 +08:00
|
|
|
/// Returns the arity of a given input function.
|
2019-03-17 08:56:35 +08:00
|
|
|
static int input_function_arity(readline_cmd_t function) {
|
2018-08-11 15:05:49 +08:00
|
|
|
switch (function) {
|
2019-03-24 08:32:39 +08:00
|
|
|
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:
|
2018-08-11 15:05:49 +08:00
|
|
|
return 1;
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
2014-01-22 17:00:44 +08:00
|
|
|
}
|
|
|
|
|
2016-05-01 12:31:25 +08:00
|
|
|
/// Helper function to compare the lengths of sequences.
|
|
|
|
static bool length_is_greater_than(const input_mapping_t &m1, const input_mapping_t &m2) {
|
2014-02-13 04:49:32 +08:00
|
|
|
return m1.seq.size() > m2.seq.size();
|
|
|
|
}
|
2005-09-20 21:26:39 +08:00
|
|
|
|
2016-05-01 12:31:25 +08:00
|
|
|
static bool specification_order_is_less_than(const input_mapping_t &m1, const input_mapping_t &m2) {
|
2014-02-13 04:49:32 +08:00
|
|
|
return m1.specification_order < m2.specification_order;
|
|
|
|
}
|
2006-07-24 04:52:03 +08:00
|
|
|
|
2016-05-01 12:31:25 +08:00
|
|
|
/// Inserts an input mapping at the correct position. We sort them in descending order by length, so
|
|
|
|
/// that we test longer sequences first.
|
2019-06-03 13:36:11 +08:00
|
|
|
static void input_mapping_insert_sorted(mapping_list_t &ml, input_mapping_t new_mapping) {
|
2019-04-29 10:16:55 +08:00
|
|
|
auto loc = std::lower_bound(ml.begin(), ml.end(), new_mapping, length_is_greater_than);
|
2019-06-03 13:36:11 +08:00
|
|
|
ml.insert(loc, std::move(new_mapping));
|
2014-02-13 04:49:32 +08:00
|
|
|
}
|
|
|
|
|
2016-05-01 12:31:25 +08:00
|
|
|
/// Adds an input mapping.
|
2019-09-15 07:36:57 +08:00
|
|
|
void input_mapping_set_t::add(wcstring sequence, const wchar_t *const *commands,
|
2019-06-03 13:36:11 +08:00
|
|
|
size_t commands_len, const wchar_t *mode, const wchar_t *sets_mode,
|
|
|
|
bool user) {
|
2019-09-15 07:36:57 +08:00
|
|
|
assert(commands && mode && sets_mode && "Null parameter");
|
2019-06-03 13:36:11 +08:00
|
|
|
|
|
|
|
// Clear cached mappings.
|
|
|
|
all_mappings_cache_.reset();
|
|
|
|
|
2016-05-01 12:31:25 +08:00
|
|
|
// Remove existing mappings with this sequence.
|
2014-03-31 04:13:35 +08:00
|
|
|
const wcstring_list_t commands_vector(commands, commands + commands_len);
|
2014-01-01 07:11:32 +08:00
|
|
|
|
2019-06-03 13:36:11 +08:00
|
|
|
mapping_list_t &ml = user ? mapping_list_ : preset_mapping_list_;
|
2018-11-19 07:37:54 +08:00
|
|
|
|
2019-05-05 18:09:25 +08:00
|
|
|
for (input_mapping_t &m : ml) {
|
2016-05-01 12:31:25 +08:00
|
|
|
if (m.seq == sequence && m.mode == mode) {
|
2014-01-01 07:11:32 +08:00
|
|
|
m.commands = commands_vector;
|
2014-01-15 18:39:19 +08:00
|
|
|
m.sets_mode = sets_mode;
|
2012-11-19 08:30:30 +08:00
|
|
|
return;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
}
|
2014-04-01 01:01:39 +08:00
|
|
|
|
2016-05-01 12:31:25 +08:00
|
|
|
// Add a new mapping, using the next order.
|
2019-09-15 07:36:57 +08:00
|
|
|
input_mapping_t new_mapping =
|
|
|
|
input_mapping_t(std::move(sequence), commands_vector, mode, sets_mode);
|
2019-06-03 13:36:11 +08:00
|
|
|
input_mapping_insert_sorted(ml, std::move(new_mapping));
|
2014-01-01 07:11:32 +08:00
|
|
|
}
|
|
|
|
|
2019-09-15 07:36:57 +08:00
|
|
|
void input_mapping_set_t::add(wcstring sequence, const wchar_t *command, const wchar_t *mode,
|
2019-06-03 13:36:11 +08:00
|
|
|
const wchar_t *sets_mode, bool user) {
|
2019-09-15 07:36:57 +08:00
|
|
|
input_mapping_set_t::add(std::move(sequence), &command, 1, mode, sets_mode, user);
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2019-06-03 17:31:13 +08:00
|
|
|
/// Handle interruptions to key reading by reaping finished jobs and propagating the interrupt to
|
|
|
|
/// the reader.
|
2019-03-17 05:45:36 +08:00
|
|
|
static maybe_t<char_event_t> interrupt_handler() {
|
2016-05-01 12:31:25 +08:00
|
|
|
// Fire any pending events.
|
2019-06-03 17:31:13 +08:00
|
|
|
// TODO: eliminate this principal_parser().
|
|
|
|
auto &parser = parser_t::principal_parser();
|
|
|
|
event_fire_delayed(parser);
|
2016-05-01 12:31:25 +08:00
|
|
|
// Reap stray processes, including printing exit status messages.
|
2019-04-30 11:58:58 +08:00
|
|
|
// TODO: shouldn't need this parser here.
|
2020-08-24 06:12:47 +08:00
|
|
|
if (job_reap(parser, true)) reader_schedule_prompt_repaint();
|
2019-11-25 19:03:25 +08:00
|
|
|
// Tell the reader an event occurred.
|
2016-05-01 12:31:25 +08:00
|
|
|
if (reader_reading_interrupted()) {
|
2019-03-17 04:52:07 +08:00
|
|
|
auto vintr = shell_modes.c_cc[VINTR];
|
|
|
|
if (vintr == 0) {
|
|
|
|
return none();
|
|
|
|
}
|
2019-03-17 05:45:36 +08:00
|
|
|
return char_event_t{vintr};
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2019-03-17 06:56:37 +08:00
|
|
|
return char_event_t{char_event_type_t::check_exit};
|
2007-09-26 00:14:47 +08:00
|
|
|
}
|
2005-09-20 21:26:39 +08:00
|
|
|
|
2019-05-05 08:18:27 +08:00
|
|
|
static relaxed_atomic_bool_t s_input_initialized{false};
|
2019-03-04 04:59:55 +08:00
|
|
|
|
2017-02-16 12:09:26 +08:00
|
|
|
/// Set up arrays used by readch to detect escape sequences for special keys and perform related
|
|
|
|
/// initializations for our input subsystem.
|
|
|
|
void init_input() {
|
2019-03-04 04:59:55 +08:00
|
|
|
ASSERT_IS_MAIN_THREAD();
|
2019-05-05 08:18:27 +08:00
|
|
|
if (s_input_initialized) return;
|
|
|
|
s_input_initialized = true;
|
2019-03-04 04:59:55 +08:00
|
|
|
|
2012-11-19 08:30:30 +08:00
|
|
|
input_common_init(&interrupt_handler);
|
2019-04-29 10:16:55 +08:00
|
|
|
s_terminfo_mappings = create_input_terminfo();
|
2012-03-06 02:44:08 +08:00
|
|
|
|
2019-06-03 13:36:11 +08:00
|
|
|
auto input_mapping = input_mappings();
|
|
|
|
|
2016-05-01 12:31:25 +08:00
|
|
|
// If we have no keybindings, add a few simple defaults.
|
2019-06-03 13:36:11 +08:00
|
|
|
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"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);
|
|
|
|
input_mapping->add(L"\x7f", L"backward-delete-char", DEFAULT_BIND_MODE, DEFAULT_BIND_MODE,
|
|
|
|
false);
|
2018-05-17 07:22:36 +08:00
|
|
|
// Arrows - can't have functions, so *-or-search isn't available.
|
2019-06-03 13:36:11 +08:00
|
|
|
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);
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2005-10-02 21:40:46 +08:00
|
|
|
}
|
|
|
|
|
2020-11-01 01:14:50 +08:00
|
|
|
inputter_t::inputter_t(parser_t &parser, int in) : event_queue_(in), parser_(parser.shared()) {}
|
2019-06-03 07:41:13 +08:00
|
|
|
|
2019-06-03 06:41:23 +08:00
|
|
|
void inputter_t::function_push_arg(wchar_t arg) { input_function_args_.push_back(arg); }
|
2014-01-01 07:11:32 +08:00
|
|
|
|
2019-06-03 06:41:23 +08:00
|
|
|
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;
|
|
|
|
}
|
2014-01-22 17:00:44 +08:00
|
|
|
|
2019-06-03 06:41:23 +08:00
|
|
|
void inputter_t::function_push_args(readline_cmd_t code) {
|
2014-01-22 17:00:44 +08:00
|
|
|
int arity = input_function_arity(code);
|
2021-02-09 02:09:22 +08:00
|
|
|
// Use a thread-local to prevent constant heap thrashing in the main input loop
|
|
|
|
static FISH_THREAD_LOCAL std::vector<char_event_t> skipped;
|
|
|
|
skipped.clear();
|
2015-09-04 03:12:56 +08:00
|
|
|
|
2016-05-01 12:31:25 +08:00
|
|
|
for (int i = 0; i < arity; i++) {
|
|
|
|
// Skip and queue up any function codes. See issue #2357.
|
2019-03-15 16:11:15 +08:00
|
|
|
wchar_t arg{};
|
|
|
|
for (;;) {
|
2019-06-03 06:41:23 +08:00
|
|
|
auto evt = event_queue_.readch();
|
2019-03-17 07:48:23 +08:00
|
|
|
if (evt.is_char()) {
|
2019-03-15 16:11:15 +08:00
|
|
|
arg = evt.get_char();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
skipped.push_back(evt);
|
2015-09-04 03:12:56 +08:00
|
|
|
}
|
2019-06-03 06:41:23 +08:00
|
|
|
function_push_arg(arg);
|
2014-01-22 17:00:44 +08:00
|
|
|
}
|
2015-09-04 03:12:56 +08:00
|
|
|
|
2016-05-01 12:31:25 +08:00
|
|
|
// Push the function codes back into the input stream.
|
2021-02-09 02:09:17 +08:00
|
|
|
event_queue_.insert_front(skipped.begin(), skipped.end());
|
2014-01-22 17:00:44 +08:00
|
|
|
}
|
2014-01-01 07:11:32 +08:00
|
|
|
|
2016-05-01 12:31:25 +08:00
|
|
|
/// Perform the action of the specified binding. allow_commands controls whether fish commands
|
|
|
|
/// should be executed, or should be deferred until later.
|
2020-11-16 04:26:06 +08:00
|
|
|
void inputter_t::mapping_execute(const input_mapping_t &m,
|
|
|
|
const command_handler_t &command_handler) {
|
2016-05-01 12:31:25 +08:00
|
|
|
// 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
|
2015-04-17 21:49:52 +08:00
|
|
|
bool has_commands = false, has_functions = false;
|
2014-04-01 01:01:39 +08:00
|
|
|
|
2018-09-17 06:49:18 +08:00
|
|
|
for (const wcstring &cmd : m.commands) {
|
2020-11-16 04:26:06 +08:00
|
|
|
if (input_function_get_code(cmd)) {
|
2015-04-17 21:49:52 +08:00
|
|
|
has_functions = true;
|
2020-11-16 04:26:06 +08:00
|
|
|
} else {
|
2015-04-17 21:49:52 +08:00
|
|
|
has_commands = true;
|
2020-11-16 04:26:06 +08:00
|
|
|
}
|
2021-02-08 06:59:07 +08:00
|
|
|
|
|
|
|
if (has_functions && has_commands) {
|
|
|
|
break;
|
|
|
|
}
|
2015-04-17 21:49:52 +08:00
|
|
|
}
|
|
|
|
|
2016-05-01 12:31:25 +08:00
|
|
|
// !has_functions && !has_commands: only set bind mode
|
|
|
|
if (!has_commands && !has_functions) {
|
2020-03-08 11:44:58 +08:00
|
|
|
if (!m.sets_mode.empty()) input_set_bind_mode(*parser_, m.sets_mode);
|
2015-04-17 21:49:52 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-11-16 04:26:06 +08:00
|
|
|
if (has_commands && !command_handler) {
|
2019-03-17 06:49:35 +08:00
|
|
|
// We don't want to run commands yet. Put the characters back and return check_exit.
|
2021-02-07 07:05:46 +08:00
|
|
|
event_queue_.insert_front(m.seq.cbegin(), m.seq.cend());
|
2019-06-03 06:41:23 +08:00
|
|
|
event_queue_.push_front(char_event_type_t::check_exit);
|
2016-05-01 12:31:25 +08:00
|
|
|
return; // skip the input_set_bind_mode
|
|
|
|
} else if (has_functions && !has_commands) {
|
|
|
|
// Functions are added at the head of the input queue.
|
2020-02-21 13:50:59 +08:00
|
|
|
for (auto it = m.commands.rbegin(), end = m.commands.rend(); it != end; ++it) {
|
2019-03-17 08:56:35 +08:00
|
|
|
readline_cmd_t code = input_function_get_code(*it).value();
|
2019-06-03 06:41:23 +08:00
|
|
|
function_push_args(code);
|
2019-09-22 06:57:21 +08:00
|
|
|
event_queue_.push_front(char_event_t(code, m.seq));
|
2014-03-31 04:13:35 +08:00
|
|
|
}
|
2016-05-01 12:31:25 +08:00
|
|
|
} 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.
|
2020-11-16 04:26:06 +08:00
|
|
|
command_handler(m.commands);
|
2019-06-03 06:41:23 +08:00
|
|
|
event_queue_.push_front(char_event_type_t::check_exit);
|
2016-05-01 12:31:25 +08:00
|
|
|
} else {
|
|
|
|
// Invalid binding, mixed commands and functions. We would need to execute these one by
|
|
|
|
// one.
|
2019-06-03 06:41:23 +08:00
|
|
|
event_queue_.push_front(char_event_type_t::check_exit);
|
2012-11-18 18:23:22 +08:00
|
|
|
}
|
2014-04-01 01:01:39 +08:00
|
|
|
|
2017-02-01 06:49:56 +08:00
|
|
|
// Empty bind mode indicates to not reset the mode (#2871)
|
2020-03-08 11:44:58 +08:00
|
|
|
if (!m.sets_mode.empty()) input_set_bind_mode(*parser_, m.sets_mode);
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2016-05-01 12:31:25 +08:00
|
|
|
/// Try reading the specified function mapping.
|
2019-06-03 06:41:23 +08:00
|
|
|
bool inputter_t::mapping_is_match(const input_mapping_t &m) {
|
2018-03-10 03:20:32 +08:00
|
|
|
const wcstring &str = m.seq;
|
2012-11-19 08:30:30 +08:00
|
|
|
|
2019-11-19 08:56:46 +08:00
|
|
|
assert(!str.empty() && "zero-length input string passed to mapping_is_match!");
|
2013-12-31 21:53:29 +08:00
|
|
|
|
2018-05-29 21:30:00 +08:00
|
|
|
bool timed = false;
|
2018-04-01 08:06:13 +08:00
|
|
|
for (size_t i = 0; i < str.size(); ++i) {
|
2019-06-03 06:41:23 +08:00
|
|
|
auto evt = timed ? event_queue_.readch_timed() : event_queue_.readch();
|
2019-03-15 16:11:15 +08:00
|
|
|
if (!evt.is_char() || evt.get_char() != str[i]) {
|
|
|
|
// We didn't match the bind sequence/input mapping, (it timed out or they entered
|
2021-02-07 07:05:46 +08:00
|
|
|
// something else). Undo consumption of the read characters since we didn't match the
|
2019-03-15 16:11:15 +08:00
|
|
|
// bind sequence and abort.
|
2019-06-03 06:41:23 +08:00
|
|
|
event_queue_.push_front(evt);
|
2021-02-09 02:09:17 +08:00
|
|
|
event_queue_.insert_front(str.begin(), str.begin() + i);
|
2018-03-10 03:20:32 +08:00
|
|
|
return false;
|
2013-12-31 21:53:29 +08:00
|
|
|
}
|
2018-05-29 21:30:00 +08:00
|
|
|
|
|
|
|
// 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.
|
2018-06-18 13:01:32 +08:00
|
|
|
timed = (str[i] == L'\x1B');
|
2012-06-03 06:43:18 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2018-03-10 03:20:32 +08:00
|
|
|
return true;
|
2007-09-26 00:14:47 +08:00
|
|
|
}
|
2005-09-20 21:26:39 +08:00
|
|
|
|
Fix underflow in `commandline` jump functions
This patch fixes an underflow in the jump family of readline commands
when called via `commandline -f` outside of a bind context such as
`commandline -f backward-jump`. To reproduce, run that command at a
prompt and the shell will crash with a buffer underlow.
This happens because the jump commands have non-zero arity, requiring a
character event to be pushed on the function args stack. Pushing the
character event is handled in `function_push_args`, called by
`inputter_t::mapping_execute`, which checks the arity of the function
and enqueues the required number of charcter events. However,
`builtin_commandline` calls `reader_queue_ch`, which in turn calls
`inputter_t::queue_ch`, which immediately enqueues the readline event
without calling `function_push_args`, so the character event is never
pushed on the arg stack.
This patch adds a check in inputter_t::queue_ch which checks if the
character event is a readline event, and if so, calls
`function_push_args`.
2020-04-18 23:48:08 +08:00
|
|
|
void inputter_t::queue_ch(const char_event_t &ch) {
|
|
|
|
if (ch.is_readline()) {
|
|
|
|
function_push_args(ch.get_readline());
|
|
|
|
}
|
|
|
|
event_queue_.push_back(ch);
|
|
|
|
}
|
2019-06-03 06:41:23 +08:00
|
|
|
|
2020-04-03 08:07:36 +08:00
|
|
|
void inputter_t::push_front(const char_event_t &ch) { event_queue_.push_front(ch); }
|
2005-09-20 21:26:39 +08:00
|
|
|
|
2019-03-15 16:11:15 +08:00
|
|
|
/// \return the first mapping that matches, walking first over the user's mapping list, then the
|
|
|
|
/// preset list. \return null if nothing matches.
|
2019-06-03 13:36:11 +08:00
|
|
|
maybe_t<input_mapping_t> inputter_t::find_mapping() {
|
2019-11-19 10:34:50 +08:00
|
|
|
const input_mapping_t *generic = nullptr;
|
2019-06-03 07:41:13 +08:00
|
|
|
const auto &vars = parser_->vars();
|
2018-09-14 15:36:26 +08:00
|
|
|
const wcstring bind_mode = input_get_bind_mode(vars);
|
2014-01-15 19:04:52 +08:00
|
|
|
|
2019-06-03 13:36:11 +08:00
|
|
|
auto ml = input_mappings()->all_mappings();
|
|
|
|
for (const auto &m : *ml) {
|
|
|
|
if (m.mode != bind_mode) {
|
|
|
|
continue;
|
|
|
|
}
|
2014-01-01 07:11:32 +08:00
|
|
|
|
2019-06-03 13:36:11 +08:00
|
|
|
if (m.is_generic()) {
|
|
|
|
if (!generic) generic = &m;
|
|
|
|
} else if (mapping_is_match(m)) {
|
|
|
|
return m;
|
2014-01-01 07:11:32 +08:00
|
|
|
}
|
|
|
|
}
|
2019-06-03 13:36:11 +08:00
|
|
|
return generic ? maybe_t<input_mapping_t>(*generic) : none();
|
2019-03-15 16:11:15 +08:00
|
|
|
}
|
2014-01-01 07:11:32 +08:00
|
|
|
|
2021-02-07 07:11:05 +08:00
|
|
|
template <size_t N = 16>
|
|
|
|
class event_queue_peeker_t {
|
|
|
|
private:
|
|
|
|
input_event_queue_t &event_queue_;
|
|
|
|
std::array<char_event_t, N> peeked_;
|
|
|
|
size_t count = 0;
|
|
|
|
bool consumed_ = false;
|
|
|
|
|
|
|
|
public:
|
|
|
|
event_queue_peeker_t(input_event_queue_t &event_queue)
|
|
|
|
: event_queue_(event_queue) {
|
|
|
|
}
|
|
|
|
|
|
|
|
char_event_t next(bool timed = false) {
|
|
|
|
assert(count < N && "Insufficient backing array size!");
|
|
|
|
auto event = timed ? event_queue_.readch_timed() : event_queue_.readch();
|
|
|
|
peeked_[count++] = event;
|
|
|
|
return event;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t len() {
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
void consume() {
|
|
|
|
consumed_ = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void restart() {
|
|
|
|
if (count > 0) {
|
|
|
|
event_queue_.insert_front(peeked_.cbegin(), peeked_.cbegin() + count);
|
|
|
|
count = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
~event_queue_peeker_t() {
|
|
|
|
if (!consumed_) {
|
|
|
|
restart();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2021-02-07 07:13:27 +08:00
|
|
|
bool inputter_t::have_mouse_tracking_csi() {
|
|
|
|
event_queue_peeker_t<12> peeker(event_queue_);
|
|
|
|
|
|
|
|
// Check for the CSI first
|
|
|
|
if (peeker.next().maybe_char() != L'\x1B'
|
|
|
|
|| peeker.next(true /* timed */).maybe_char() != L'[') {
|
|
|
|
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
|
|
|
|
// reporting the button that was clicked and its location.
|
|
|
|
length = 6;
|
|
|
|
} else if (next == L'P') {
|
|
|
|
// VT200 mouse highlighting. 12 characters, comes after generic button press event.
|
|
|
|
length = 12;
|
|
|
|
} 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.
|
|
|
|
peeker.consume();
|
|
|
|
while (peeker.len() != length) {
|
|
|
|
auto _ = peeker.next();
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2020-11-16 04:26:06 +08:00
|
|
|
void inputter_t::mapping_execute_matching_or_generic(const command_handler_t &command_handler) {
|
2021-02-07 07:13:27 +08:00
|
|
|
// Check for mouse-tracking CSI before mappings to prevent the generic mapping handler from
|
|
|
|
// taking over.
|
|
|
|
if (have_mouse_tracking_csi()) {
|
|
|
|
// 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");
|
|
|
|
event_queue_.push_front(char_event_t(readline_cmd_t::disable_mouse_tracking, L""));
|
|
|
|
}
|
|
|
|
else if (auto mapping = find_mapping()) {
|
2020-11-16 04:26:06 +08:00
|
|
|
mapping_execute(*mapping, command_handler);
|
2016-05-01 12:31:25 +08:00
|
|
|
} else {
|
2020-01-19 21:27:23 +08:00
|
|
|
FLOGF(reader, L"no generic found, ignoring char...");
|
2019-06-03 06:41:23 +08:00
|
|
|
auto evt = event_queue_.readch();
|
2019-03-17 03:35:49 +08:00
|
|
|
if (evt.is_eof()) {
|
2019-06-03 06:41:23 +08:00
|
|
|
event_queue_.push_front(evt);
|
2016-07-12 11:31:30 +08:00
|
|
|
}
|
2014-01-01 07:11:32 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-05-01 12:31:25 +08:00
|
|
|
/// Helper function. Picks through the queue of incoming characters until we get to one that's not a
|
2019-03-17 04:33:42 +08:00
|
|
|
/// readline function.
|
2019-06-03 06:41:23 +08:00
|
|
|
char_event_t inputter_t::read_characters_no_readline() {
|
2021-02-09 02:09:22 +08:00
|
|
|
// Use a thread-local vector to prevent repeated heap allocation, as this is called in the main
|
|
|
|
// input loop.
|
|
|
|
static FISH_THREAD_LOCAL std::vector<char_event_t> saved_events;
|
|
|
|
saved_events.clear();
|
|
|
|
|
2019-03-17 04:33:42 +08:00
|
|
|
char_event_t evt_to_return{0};
|
2016-05-01 12:31:25 +08:00
|
|
|
for (;;) {
|
2019-06-03 06:41:23 +08:00
|
|
|
auto evt = event_queue_.readch();
|
2019-03-17 04:33:42 +08:00
|
|
|
if (evt.is_readline()) {
|
|
|
|
saved_events.push_back(evt);
|
|
|
|
} else {
|
|
|
|
evt_to_return = evt;
|
|
|
|
break;
|
2015-07-20 15:10:58 +08:00
|
|
|
}
|
|
|
|
}
|
2021-02-09 02:09:17 +08:00
|
|
|
|
2021-02-07 07:05:46 +08:00
|
|
|
// Restore any readline functions
|
|
|
|
event_queue_.insert_front(saved_events.cbegin(), saved_events.cend());
|
2019-03-17 04:33:42 +08:00
|
|
|
return evt_to_return;
|
2015-07-20 15:10:58 +08:00
|
|
|
}
|
|
|
|
|
2020-11-16 04:26:06 +08:00
|
|
|
char_event_t inputter_t::readch(const command_handler_t &command_handler) {
|
2016-05-01 12:31:25 +08:00
|
|
|
// Clear the interrupted flag.
|
2013-01-21 05:23:27 +08:00
|
|
|
reader_reset_interrupted();
|
2016-05-01 12:31:25 +08:00
|
|
|
// Search for sequence in mapping tables.
|
2018-12-11 20:22:04 +08:00
|
|
|
while (true) {
|
2019-06-03 06:41:23 +08:00
|
|
|
auto evt = event_queue_.readch();
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2019-03-15 16:11:15 +08:00
|
|
|
if (evt.is_readline()) {
|
2019-03-17 07:48:23 +08:00
|
|
|
switch (evt.get_readline()) {
|
2020-03-05 06:10:24 +08:00
|
|
|
case readline_cmd_t::self_insert:
|
|
|
|
case readline_cmd_t::self_insert_notfirst: {
|
2019-09-22 06:57:21 +08:00
|
|
|
// Typically self-insert is generated by the generic (empty) binding.
|
|
|
|
// However if it is generated by a real sequence, then insert that sequence.
|
2021-02-07 07:05:46 +08:00
|
|
|
event_queue_.insert_front(evt.seq.cbegin(), evt.seq.cend());
|
2016-05-01 12:31:25 +08:00
|
|
|
// Issue #1595: ensure we only insert characters, not readline functions. The
|
|
|
|
// common case is that this will be empty.
|
2020-03-05 06:10:24 +08:00
|
|
|
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
|
2020-03-08 05:55:19 +08:00
|
|
|
? char_input_style_t::notfirst
|
|
|
|
: char_input_style_t::normal;
|
2020-03-05 06:10:24 +08:00
|
|
|
return res;
|
2014-04-01 01:01:39 +08:00
|
|
|
}
|
2020-07-23 01:18:24 +08:00
|
|
|
case readline_cmd_t::func_and:
|
|
|
|
case readline_cmd_t::func_or: {
|
|
|
|
// If previous function has right status, we keep reading tokens
|
|
|
|
if (evt.get_readline() == readline_cmd_t::func_and) {
|
2020-08-05 03:41:14 +08:00
|
|
|
if (function_status_) return readch();
|
2020-07-23 01:18:24 +08:00
|
|
|
} else {
|
|
|
|
assert(evt.get_readline() == readline_cmd_t::func_or);
|
2020-08-05 03:41:14 +08:00
|
|
|
if (!function_status_) return readch();
|
2014-04-01 01:01:39 +08:00
|
|
|
}
|
2020-07-23 01:18:24 +08:00
|
|
|
// Else we flush remaining tokens
|
2019-03-15 16:11:15 +08:00
|
|
|
do {
|
2019-06-03 06:41:23 +08:00
|
|
|
evt = event_queue_.readch();
|
2019-03-15 16:11:15 +08:00
|
|
|
} while (evt.is_readline());
|
2019-06-03 06:41:23 +08:00
|
|
|
event_queue_.push_front(evt);
|
|
|
|
return readch();
|
2014-04-01 01:01:39 +08:00
|
|
|
}
|
2019-05-05 18:09:25 +08:00
|
|
|
default: {
|
|
|
|
return evt;
|
|
|
|
}
|
2014-04-01 01:01:39 +08:00
|
|
|
}
|
2019-03-17 03:35:49 +08:00
|
|
|
} else if (evt.is_eof()) {
|
|
|
|
// If we have EOF, we need to immediately quit.
|
2019-01-29 01:10:41 +08:00
|
|
|
// There's no need to go through the input functions.
|
2019-03-15 16:11:15 +08:00
|
|
|
return evt;
|
2016-05-01 12:31:25 +08:00
|
|
|
} else {
|
2019-06-03 06:41:23 +08:00
|
|
|
event_queue_.push_front(evt);
|
2020-11-16 04:26:06 +08:00
|
|
|
mapping_execute_matching_or_generic(command_handler);
|
2019-03-17 06:49:35 +08:00
|
|
|
// 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.
|
2012-07-07 07:25:10 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
}
|
2007-09-26 00:14:47 +08:00
|
|
|
}
|
|
|
|
|
2019-06-03 13:36:11 +08:00
|
|
|
std::vector<input_mapping_name_t> input_mapping_set_t::get_names(bool user) const {
|
2016-05-01 12:31:25 +08:00
|
|
|
// Sort the mappings by the user specification order, so we can return them in the same order
|
|
|
|
// that the user specified them in.
|
2019-06-03 13:36:11 +08:00
|
|
|
std::vector<input_mapping_t> local_list = user ? mapping_list_ : preset_mapping_list_;
|
2014-02-13 04:49:32 +08:00
|
|
|
std::sort(local_list.begin(), local_list.end(), specification_order_is_less_than);
|
2014-09-23 12:04:06 +08:00
|
|
|
std::vector<input_mapping_name_t> result;
|
2014-03-31 04:13:35 +08:00
|
|
|
result.reserve(local_list.size());
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2019-11-20 05:46:47 +08:00
|
|
|
for (const auto &m : local_list) {
|
2014-09-23 12:04:06 +08:00
|
|
|
result.push_back((input_mapping_name_t){m.seq, m.mode});
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2014-03-31 04:13:35 +08:00
|
|
|
return result;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2019-06-03 13:36:11 +08:00
|
|
|
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_;
|
2019-11-19 10:34:50 +08:00
|
|
|
auto should_erase = [=](const input_mapping_t &m) { return mode == nullptr || mode == m.mode; };
|
2018-10-01 06:12:55 +08:00
|
|
|
ml.erase(std::remove_if(ml.begin(), ml.end(), should_erase), ml.end());
|
2018-09-18 17:52:25 +08:00
|
|
|
}
|
|
|
|
|
2019-06-03 13:36:11 +08:00
|
|
|
bool input_mapping_set_t::erase(const wcstring &sequence, const wcstring &mode, bool user) {
|
|
|
|
// Clear cached mappings.
|
|
|
|
all_mappings_cache_.reset();
|
|
|
|
|
2012-11-19 08:30:30 +08:00
|
|
|
bool result = false;
|
2019-06-03 13:36:11 +08:00
|
|
|
mapping_list_t &ml = user ? mapping_list_ : preset_mapping_list_;
|
2020-02-21 13:50:59 +08:00
|
|
|
for (auto it = ml.begin(), end = ml.end(); it != end; ++it) {
|
2016-05-01 12:31:25 +08:00
|
|
|
if (sequence == it->seq && mode == it->mode) {
|
2018-09-18 17:52:25 +08:00
|
|
|
ml.erase(it);
|
2012-11-19 08:30:30 +08:00
|
|
|
result = true;
|
|
|
|
break;
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
return result;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2019-06-03 13:36:11 +08:00
|
|
|
bool input_mapping_set_t::get(const wcstring &sequence, const wcstring &mode,
|
2020-03-14 04:59:10 +08:00
|
|
|
wcstring_list_t *out_cmds, bool user, wcstring *out_sets_mode) const {
|
2014-03-31 04:13:35 +08:00
|
|
|
bool result = false;
|
2020-03-14 04:59:10 +08:00
|
|
|
const auto &ml = user ? mapping_list_ : preset_mapping_list_;
|
2018-10-01 06:12:55 +08:00
|
|
|
for (const input_mapping_t &m : ml) {
|
|
|
|
if (sequence == m.seq && mode == m.mode) {
|
|
|
|
*out_cmds = m.commands;
|
|
|
|
*out_sets_mode = m.sets_mode;
|
2014-03-31 04:13:35 +08:00
|
|
|
result = true;
|
|
|
|
break;
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
}
|
2014-03-31 04:13:35 +08:00
|
|
|
return result;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
2007-09-29 05:32:27 +08:00
|
|
|
|
2019-06-03 13:36:11 +08:00
|
|
|
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_;
|
|
|
|
}
|
|
|
|
|
2019-04-29 10:16:55 +08:00
|
|
|
/// Create a list of terminfo mappings.
|
|
|
|
static std::vector<terminfo_mapping_t> create_input_terminfo() {
|
2017-02-16 12:09:26 +08:00
|
|
|
assert(curses_initialized);
|
2019-04-29 10:16:55 +08:00
|
|
|
if (!cur_term) return {}; // setupterm() failed so we can't referency any key definitions
|
2019-09-15 07:36:57 +08:00
|
|
|
|
|
|
|
#define TERMINFO_ADD(key) \
|
|
|
|
{ (L## #key) + 4, key }
|
|
|
|
|
2019-04-29 10:16:55 +08:00
|
|
|
return {
|
2019-05-05 18:09:25 +08:00
|
|
|
TERMINFO_ADD(key_a1), TERMINFO_ADD(key_a3), TERMINFO_ADD(key_b2),
|
2020-08-10 06:06:50 +08:00
|
|
|
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'})};
|
2019-09-15 07:36:57 +08:00
|
|
|
#undef TERMINFO_ADD
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2019-09-15 07:36:57 +08:00
|
|
|
bool input_terminfo_get_sequence(const wcstring &name, wcstring *out_seq) {
|
2012-02-08 15:17:20 +08:00
|
|
|
ASSERT_IS_MAIN_THREAD();
|
2019-05-05 08:18:27 +08:00
|
|
|
assert(s_input_initialized);
|
2019-04-29 10:16:55 +08:00
|
|
|
for (const terminfo_mapping_t &m : *s_terminfo_mappings) {
|
2019-09-15 07:36:57 +08:00
|
|
|
if (name == m.name) {
|
2019-09-15 05:59:01 +08:00
|
|
|
// Found the mapping.
|
|
|
|
if (!m.seq) {
|
|
|
|
errno = EILSEQ;
|
|
|
|
return false;
|
|
|
|
} else {
|
2019-09-15 07:36:57 +08:00
|
|
|
*out_seq = str2wcstring(*m.seq);
|
2019-09-15 05:59:01 +08:00
|
|
|
return true;
|
|
|
|
}
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
}
|
2019-09-15 05:59:01 +08:00
|
|
|
errno = ENOENT;
|
|
|
|
return false;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2016-05-01 12:31:25 +08:00
|
|
|
bool input_terminfo_get_name(const wcstring &seq, wcstring *out_name) {
|
2019-05-05 08:18:27 +08:00
|
|
|
assert(s_input_initialized);
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2019-04-29 10:16:55 +08:00
|
|
|
for (const terminfo_mapping_t &m : *s_terminfo_mappings) {
|
2019-09-15 07:36:57 +08:00
|
|
|
if (m.seq && seq == str2wcstring(*m.seq)) {
|
2014-07-08 01:45:26 +08:00
|
|
|
out_name->assign(m.name);
|
2012-01-01 07:57:30 +08:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
2012-11-18 18:23:22 +08:00
|
|
|
|
2012-11-19 08:30:30 +08:00
|
|
|
return false;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2016-05-01 12:31:25 +08:00
|
|
|
wcstring_list_t input_terminfo_get_names(bool skip_null) {
|
2019-05-05 08:18:27 +08:00
|
|
|
assert(s_input_initialized);
|
2021-02-09 02:09:17 +08:00
|
|
|
|
2012-02-08 09:06:45 +08:00
|
|
|
wcstring_list_t result;
|
2019-04-29 10:16:55 +08:00
|
|
|
const auto &mappings = *s_terminfo_mappings;
|
|
|
|
result.reserve(mappings.size());
|
|
|
|
for (const terminfo_mapping_t &m : mappings) {
|
2016-05-01 12:31:25 +08:00
|
|
|
if (skip_null && !m.seq) {
|
2012-11-19 08:30:30 +08:00
|
|
|
continue;
|
|
|
|
}
|
2012-02-08 15:15:32 +08:00
|
|
|
result.push_back(wcstring(m.name));
|
2012-11-19 08:30:30 +08:00
|
|
|
}
|
2012-02-08 09:06:45 +08:00
|
|
|
return result;
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|
|
|
|
|
2021-02-08 06:59:07 +08:00
|
|
|
const wcstring_list_t &input_function_get_names() {
|
|
|
|
// The list and names of input functions are hard-coded and never change
|
|
|
|
static wcstring_list_t result = ([&]() {
|
|
|
|
wcstring_list_t result;
|
|
|
|
result.reserve(input_function_count);
|
|
|
|
for (const auto &md : input_function_metadata) {
|
|
|
|
if (md.name[0]) {
|
|
|
|
result.push_back(md.name);
|
|
|
|
}
|
2021-02-08 02:49:51 +08:00
|
|
|
}
|
2021-02-08 06:59:07 +08:00
|
|
|
return result;
|
|
|
|
})();
|
|
|
|
|
2018-01-30 11:15:16 +08:00
|
|
|
return result;
|
2007-09-26 00:14:47 +08:00
|
|
|
}
|
2005-09-20 21:26:39 +08:00
|
|
|
|
2019-03-17 08:56:35 +08:00
|
|
|
maybe_t<readline_cmd_t> input_function_get_code(const wcstring &name) {
|
2021-02-08 06:59:07 +08:00
|
|
|
// `input_function_metadata` is required to be kept in asciibetical order, making it OK to do
|
|
|
|
// a binary search for the matching name.
|
|
|
|
constexpr auto end = &input_function_metadata[0] + input_function_count;
|
|
|
|
auto result = std::lower_bound(
|
|
|
|
&input_function_metadata[0], end,
|
|
|
|
input_function_metadata_t{name.data(), static_cast<readline_cmd_t>(-1)},
|
|
|
|
[&](const input_function_metadata_t &lhs, const input_function_metadata_t &rhs) {
|
|
|
|
return wcscmp(lhs.name, rhs.name) < 0;
|
|
|
|
});
|
|
|
|
|
|
|
|
if (result != end && result->name[0] && name == result->name) {
|
|
|
|
return result->code;
|
2012-11-18 18:23:22 +08:00
|
|
|
}
|
2019-03-17 08:05:33 +08:00
|
|
|
return none();
|
2005-09-20 21:26:39 +08:00
|
|
|
}
|