mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-15 22:44:01 +00:00
Instantize the character event queue
Wrap this stuff up in structs so it's no longer global.
This commit is contained in:
parent
6c1d29e14d
commit
a48dbf23b8
9 changed files with 177 additions and 138 deletions
|
@ -326,9 +326,8 @@ int builtin_commandline(parser_t &parser, io_streams_t &streams, wchar_t **argv)
|
||||||
|
|
||||||
for (i = w.woptind; i < argc; i++) {
|
for (i = w.woptind; i < argc; i++) {
|
||||||
if (auto mc = input_function_get_code(argv[i])) {
|
if (auto mc = input_function_get_code(argv[i])) {
|
||||||
// input_unreadch inserts the specified keypress or readline function at the back of
|
// Inserts the readline function at the back of the queue.
|
||||||
// the queue of unused keypresses.
|
reader_queue_ch(*mc);
|
||||||
input_queue_ch(*mc);
|
|
||||||
} else {
|
} else {
|
||||||
streams.err.append_format(_(L"%ls: Unknown input function '%ls'"), cmd, argv[i]);
|
streams.err.append_format(_(L"%ls: Unknown input function '%ls'"), cmd, argv[i]);
|
||||||
builtin_print_error_trailer(parser, streams.err, cmd);
|
builtin_print_error_trailer(parser, streams.err, cmd);
|
||||||
|
|
|
@ -201,6 +201,7 @@ static double output_elapsed_time(double prev_tstamp, bool first_char_seen) {
|
||||||
static void process_input(bool continuous_mode) {
|
static void process_input(bool continuous_mode) {
|
||||||
bool first_char_seen = false;
|
bool first_char_seen = false;
|
||||||
double prev_tstamp = 0.0;
|
double prev_tstamp = 0.0;
|
||||||
|
input_event_queue_t queue;
|
||||||
std::vector<wchar_t> bind_chars;
|
std::vector<wchar_t> bind_chars;
|
||||||
|
|
||||||
std::fwprintf(stderr, L"Press a key\n\n");
|
std::fwprintf(stderr, L"Press a key\n\n");
|
||||||
|
@ -209,7 +210,7 @@ static void process_input(bool continuous_mode) {
|
||||||
if (reader_test_and_clear_interrupted()) {
|
if (reader_test_and_clear_interrupted()) {
|
||||||
evt = char_event_t{shell_modes.c_cc[VINTR]};
|
evt = char_event_t{shell_modes.c_cc[VINTR]};
|
||||||
} else {
|
} else {
|
||||||
evt = input_common_readch_timed(true);
|
evt = queue.readch_timed(true);
|
||||||
}
|
}
|
||||||
if (!evt.is_char()) {
|
if (!evt.is_char()) {
|
||||||
output_bind_command(bind_chars);
|
output_bind_command(bind_chars);
|
||||||
|
|
|
@ -3003,6 +3003,7 @@ static bool history_contains(const std::unique_ptr<history_t> &history, const wc
|
||||||
|
|
||||||
static void test_input() {
|
static void test_input() {
|
||||||
say(L"Testing input");
|
say(L"Testing input");
|
||||||
|
inputter_t input{};
|
||||||
// Ensure sequences are order independent. Here we add two bindings where the first is a prefix
|
// Ensure sequences are order independent. Here we add two bindings where the first is a prefix
|
||||||
// of the second, and then emit the second key list. The second binding should be invoked, not
|
// of the second, and then emit the second key list. The second binding should be invoked, not
|
||||||
// the first!
|
// the first!
|
||||||
|
@ -3013,11 +3014,11 @@ static void test_input() {
|
||||||
|
|
||||||
// Push the desired binding to the queue.
|
// Push the desired binding to the queue.
|
||||||
for (size_t idx = 0; idx < desired_binding.size(); idx++) {
|
for (size_t idx = 0; idx < desired_binding.size(); idx++) {
|
||||||
input_queue_ch(desired_binding.at(idx));
|
input.queue_ch(desired_binding.at(idx));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now test.
|
// Now test.
|
||||||
auto evt = input_readch();
|
auto evt = input.readch();
|
||||||
if (!evt.is_readline()) {
|
if (!evt.is_readline()) {
|
||||||
err(L"Event is not a readline");
|
err(L"Event is not a readline");
|
||||||
} else if (evt.get_readline() != readline_cmd_t::down_line) {
|
} else if (evt.get_readline() != readline_cmd_t::down_line) {
|
||||||
|
|
|
@ -160,10 +160,6 @@ static latch_t<std::vector<terminfo_mapping_t>> s_terminfo_mappings;
|
||||||
/// \return the input terminfo.
|
/// \return the input terminfo.
|
||||||
static std::vector<terminfo_mapping_t> create_input_terminfo();
|
static std::vector<terminfo_mapping_t> create_input_terminfo();
|
||||||
|
|
||||||
static wchar_t input_function_args[MAX_INPUT_FUNCTION_ARGS];
|
|
||||||
static bool input_function_status;
|
|
||||||
static int input_function_args_index = 0;
|
|
||||||
|
|
||||||
/// Return the current bind mode.
|
/// Return the current bind mode.
|
||||||
wcstring input_get_bind_mode(const environment_t &vars) {
|
wcstring input_get_bind_mode(const environment_t &vars) {
|
||||||
auto mode = vars.get(FISH_BIND_MODE_VAR);
|
auto mode = vars.get(FISH_BIND_MODE_VAR);
|
||||||
|
@ -195,9 +191,6 @@ static int input_function_arity(readline_cmd_t function) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the return status of the most recently executed input function.
|
|
||||||
void input_function_set_status(bool status) { input_function_status = status; }
|
|
||||||
|
|
||||||
/// Helper function to compare the lengths of sequences.
|
/// Helper function to compare the lengths of sequences.
|
||||||
static bool length_is_greater_than(const input_mapping_t &m1, const input_mapping_t &m2) {
|
static bool length_is_greater_than(const input_mapping_t &m1, const input_mapping_t &m2) {
|
||||||
return m1.seq.size() > m2.seq.size();
|
return m1.seq.size() > m2.seq.size();
|
||||||
|
@ -294,13 +287,16 @@ void init_input() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void input_function_push_arg(wchar_t arg) {
|
void inputter_t::function_push_arg(wchar_t arg) { input_function_args_.push_back(arg); }
|
||||||
input_function_args[input_function_args_index++] = 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;
|
||||||
}
|
}
|
||||||
|
|
||||||
wchar_t input_function_pop_arg() { return input_function_args[--input_function_args_index]; }
|
void inputter_t::function_push_args(readline_cmd_t code) {
|
||||||
|
|
||||||
void input_function_push_args(readline_cmd_t code) {
|
|
||||||
int arity = input_function_arity(code);
|
int arity = input_function_arity(code);
|
||||||
std::vector<char_event_t> skipped;
|
std::vector<char_event_t> skipped;
|
||||||
|
|
||||||
|
@ -308,26 +304,26 @@ void input_function_push_args(readline_cmd_t code) {
|
||||||
// Skip and queue up any function codes. See issue #2357.
|
// Skip and queue up any function codes. See issue #2357.
|
||||||
wchar_t arg{};
|
wchar_t arg{};
|
||||||
for (;;) {
|
for (;;) {
|
||||||
auto evt = input_common_readch();
|
auto evt = event_queue_.readch();
|
||||||
if (evt.is_char()) {
|
if (evt.is_char()) {
|
||||||
arg = evt.get_char();
|
arg = evt.get_char();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
skipped.push_back(evt);
|
skipped.push_back(evt);
|
||||||
}
|
}
|
||||||
input_function_push_arg(arg);
|
function_push_arg(arg);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Push the function codes back into the input stream.
|
// Push the function codes back into the input stream.
|
||||||
size_t idx = skipped.size();
|
size_t idx = skipped.size();
|
||||||
while (idx--) {
|
while (idx--) {
|
||||||
input_common_next_ch(skipped.at(idx));
|
event_queue_.push_front(skipped.at(idx));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Perform the action of the specified binding. allow_commands controls whether fish commands
|
/// Perform the action of the specified binding. allow_commands controls whether fish commands
|
||||||
/// should be executed, or should be deferred until later.
|
/// should be executed, or should be deferred until later.
|
||||||
static void input_mapping_execute(const input_mapping_t &m, bool allow_commands) {
|
void inputter_t::mapping_execute(const input_mapping_t &m, bool allow_commands) {
|
||||||
// has_functions: there are functions that need to be put on the input queue
|
// 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
|
// has_commands: there are shell commands that need to be evaluated
|
||||||
bool has_commands = false, has_functions = false;
|
bool has_commands = false, has_functions = false;
|
||||||
|
@ -349,9 +345,9 @@ static void input_mapping_execute(const input_mapping_t &m, bool allow_commands)
|
||||||
// We don't want to run commands yet. Put the characters back and return check_exit.
|
// We don't want to run commands yet. Put the characters back and return check_exit.
|
||||||
for (wcstring::const_reverse_iterator it = m.seq.rbegin(), end = m.seq.rend(); it != end;
|
for (wcstring::const_reverse_iterator it = m.seq.rbegin(), end = m.seq.rend(); it != end;
|
||||||
++it) {
|
++it) {
|
||||||
input_common_next_ch(*it);
|
event_queue_.push_front(*it);
|
||||||
}
|
}
|
||||||
input_common_next_ch(char_event_type_t::check_exit);
|
event_queue_.push_front(char_event_type_t::check_exit);
|
||||||
return; // skip the input_set_bind_mode
|
return; // skip the input_set_bind_mode
|
||||||
} else if (has_functions && !has_commands) {
|
} else if (has_functions && !has_commands) {
|
||||||
// Functions are added at the head of the input queue.
|
// Functions are added at the head of the input queue.
|
||||||
|
@ -359,8 +355,8 @@ static void input_mapping_execute(const input_mapping_t &m, bool allow_commands)
|
||||||
end = m.commands.rend();
|
end = m.commands.rend();
|
||||||
it != end; ++it) {
|
it != end; ++it) {
|
||||||
readline_cmd_t code = input_function_get_code(*it).value();
|
readline_cmd_t code = input_function_get_code(*it).value();
|
||||||
input_function_push_args(code);
|
function_push_args(code);
|
||||||
input_common_next_ch(code);
|
event_queue_.push_front(code);
|
||||||
}
|
}
|
||||||
} else if (has_commands && !has_functions) {
|
} else if (has_commands && !has_functions) {
|
||||||
// Execute all commands.
|
// Execute all commands.
|
||||||
|
@ -373,11 +369,11 @@ static void input_mapping_execute(const input_mapping_t &m, bool allow_commands)
|
||||||
parser.eval(cmd, io_chain_t(), TOP);
|
parser.eval(cmd, io_chain_t(), TOP);
|
||||||
}
|
}
|
||||||
parser.set_last_statuses(std::move(last_statuses));
|
parser.set_last_statuses(std::move(last_statuses));
|
||||||
input_common_next_ch(char_event_type_t::check_exit);
|
event_queue_.push_front(char_event_type_t::check_exit);
|
||||||
} else {
|
} else {
|
||||||
// Invalid binding, mixed commands and functions. We would need to execute these one by
|
// Invalid binding, mixed commands and functions. We would need to execute these one by
|
||||||
// one.
|
// one.
|
||||||
input_common_next_ch(char_event_type_t::check_exit);
|
event_queue_.push_front(char_event_type_t::check_exit);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty bind mode indicates to not reset the mode (#2871)
|
// Empty bind mode indicates to not reset the mode (#2871)
|
||||||
|
@ -385,20 +381,20 @@ static void input_mapping_execute(const input_mapping_t &m, bool allow_commands)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try reading the specified function mapping.
|
/// Try reading the specified function mapping.
|
||||||
static bool input_mapping_is_match(const input_mapping_t &m) {
|
bool inputter_t::mapping_is_match(const input_mapping_t &m) {
|
||||||
const wcstring &str = m.seq;
|
const wcstring &str = m.seq;
|
||||||
|
|
||||||
assert(str.size() > 0 && "zero-length input string passed to input_mapping_is_match!");
|
assert(str.size() > 0 && "zero-length input string passed to input_mapping_is_match!");
|
||||||
|
|
||||||
bool timed = false;
|
bool timed = false;
|
||||||
for (size_t i = 0; i < str.size(); ++i) {
|
for (size_t i = 0; i < str.size(); ++i) {
|
||||||
auto evt = timed ? input_common_readch_timed() : input_common_readch();
|
auto evt = timed ? event_queue_.readch_timed() : event_queue_.readch();
|
||||||
if (!evt.is_char() || evt.get_char() != str[i]) {
|
if (!evt.is_char() || evt.get_char() != str[i]) {
|
||||||
// We didn't match the bind sequence/input mapping, (it timed out or they entered
|
// We didn't match the bind sequence/input mapping, (it timed out or they entered
|
||||||
// something else) Undo consumption of the read characters since we didn't match the
|
// something else) Undo consumption of the read characters since we didn't match the
|
||||||
// bind sequence and abort.
|
// bind sequence and abort.
|
||||||
input_common_next_ch(evt);
|
event_queue_.push_front(evt);
|
||||||
while (i--) input_common_next_ch(str[i]);
|
while (i--) event_queue_.push_front(str[i]);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -410,11 +406,13 @@ static bool input_mapping_is_match(const input_mapping_t &m) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void input_queue_ch(char_event_t ch) { input_common_queue_ch(ch); }
|
void inputter_t::queue_ch(char_event_t ch) { event_queue_.push_back(ch); }
|
||||||
|
|
||||||
|
void inputter_t::push_front(char_event_t ch) { event_queue_.push_front(ch); }
|
||||||
|
|
||||||
/// \return the first mapping that matches, walking first over the user's mapping list, then the
|
/// \return the first mapping that matches, walking first over the user's mapping list, then the
|
||||||
/// preset list. \return null if nothing matches.
|
/// preset list. \return null if nothing matches.
|
||||||
static const input_mapping_t *find_mapping() {
|
const input_mapping_t *inputter_t::find_mapping() {
|
||||||
const input_mapping_t *generic = NULL;
|
const input_mapping_t *generic = NULL;
|
||||||
const auto &vars = parser_t::principal_parser().vars();
|
const auto &vars = parser_t::principal_parser().vars();
|
||||||
const wcstring bind_mode = input_get_bind_mode(vars);
|
const wcstring bind_mode = input_get_bind_mode(vars);
|
||||||
|
@ -429,7 +427,7 @@ static const input_mapping_t *find_mapping() {
|
||||||
|
|
||||||
if (m.is_generic()) {
|
if (m.is_generic()) {
|
||||||
if (!generic) generic = &m;
|
if (!generic) generic = &m;
|
||||||
} else if (input_mapping_is_match(m)) {
|
} else if (mapping_is_match(m)) {
|
||||||
return &m;
|
return &m;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -437,26 +435,26 @@ static const input_mapping_t *find_mapping() {
|
||||||
return generic;
|
return generic;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void input_mapping_execute_matching_or_generic(bool allow_commands) {
|
void inputter_t::mapping_execute_matching_or_generic(bool allow_commands) {
|
||||||
const input_mapping_t *mapping = find_mapping();
|
const input_mapping_t *mapping = find_mapping();
|
||||||
if (mapping) {
|
if (mapping) {
|
||||||
input_mapping_execute(*mapping, allow_commands);
|
mapping_execute(*mapping, allow_commands);
|
||||||
} else {
|
} else {
|
||||||
debug(2, L"no generic found, ignoring char...");
|
debug(2, L"no generic found, ignoring char...");
|
||||||
auto evt = input_common_readch();
|
auto evt = event_queue_.readch();
|
||||||
if (evt.is_eof()) {
|
if (evt.is_eof()) {
|
||||||
input_common_next_ch(evt);
|
event_queue_.push_front(evt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Helper function. Picks through the queue of incoming characters until we get to one that's not a
|
/// Helper function. Picks through the queue of incoming characters until we get to one that's not a
|
||||||
/// readline function.
|
/// readline function.
|
||||||
static char_event_t input_read_characters_no_readline() {
|
char_event_t inputter_t::read_characters_no_readline() {
|
||||||
std::vector<char_event_t> saved_events;
|
std::vector<char_event_t> saved_events;
|
||||||
char_event_t evt_to_return{0};
|
char_event_t evt_to_return{0};
|
||||||
for (;;) {
|
for (;;) {
|
||||||
auto evt = input_common_readch();
|
auto evt = event_queue_.readch();
|
||||||
if (evt.is_readline()) {
|
if (evt.is_readline()) {
|
||||||
saved_events.push_back(evt);
|
saved_events.push_back(evt);
|
||||||
} else {
|
} else {
|
||||||
|
@ -466,34 +464,34 @@ static char_event_t input_read_characters_no_readline() {
|
||||||
}
|
}
|
||||||
// Restore any readline functions, in reverse to preserve their original order.
|
// Restore any readline functions, in reverse to preserve their original order.
|
||||||
for (auto iter = saved_events.rbegin(); iter != saved_events.rend(); ++iter) {
|
for (auto iter = saved_events.rbegin(); iter != saved_events.rend(); ++iter) {
|
||||||
input_common_next_ch(*iter);
|
event_queue_.push_front(*iter);
|
||||||
}
|
}
|
||||||
return evt_to_return;
|
return evt_to_return;
|
||||||
}
|
}
|
||||||
|
|
||||||
char_event_t input_readch(bool allow_commands) {
|
char_event_t inputter_t::readch(bool allow_commands) {
|
||||||
// Clear the interrupted flag.
|
// Clear the interrupted flag.
|
||||||
reader_reset_interrupted();
|
reader_reset_interrupted();
|
||||||
// Search for sequence in mapping tables.
|
// Search for sequence in mapping tables.
|
||||||
while (true) {
|
while (true) {
|
||||||
auto evt = input_common_readch();
|
auto evt = event_queue_.readch();
|
||||||
|
|
||||||
if (evt.is_readline()) {
|
if (evt.is_readline()) {
|
||||||
switch (evt.get_readline()) {
|
switch (evt.get_readline()) {
|
||||||
case readline_cmd_t::self_insert: {
|
case readline_cmd_t::self_insert: {
|
||||||
// Issue #1595: ensure we only insert characters, not readline functions. The
|
// Issue #1595: ensure we only insert characters, not readline functions. The
|
||||||
// common case is that this will be empty.
|
// common case is that this will be empty.
|
||||||
return input_read_characters_no_readline();
|
return read_characters_no_readline();
|
||||||
}
|
}
|
||||||
case readline_cmd_t::func_and: {
|
case readline_cmd_t::func_and: {
|
||||||
if (input_function_status) {
|
if (function_status_) {
|
||||||
return input_readch();
|
return readch();
|
||||||
}
|
}
|
||||||
do {
|
do {
|
||||||
evt = input_common_readch();
|
evt = event_queue_.readch();
|
||||||
} while (evt.is_readline());
|
} while (evt.is_readline());
|
||||||
input_common_next_ch(evt);
|
event_queue_.push_front(evt);
|
||||||
return input_readch();
|
return readch();
|
||||||
}
|
}
|
||||||
default: {
|
default: {
|
||||||
return evt;
|
return evt;
|
||||||
|
@ -504,8 +502,8 @@ char_event_t input_readch(bool allow_commands) {
|
||||||
// There's no need to go through the input functions.
|
// There's no need to go through the input functions.
|
||||||
return evt;
|
return evt;
|
||||||
} else {
|
} else {
|
||||||
input_common_next_ch(evt);
|
event_queue_.push_front(evt);
|
||||||
input_mapping_execute_matching_or_generic(allow_commands);
|
mapping_execute_matching_or_generic(allow_commands);
|
||||||
// Regarding allow_commands, we're in a loop, but if a fish command is executed,
|
// 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
|
// check_exit is unread, so the next pass through the loop we'll break out and return
|
||||||
// it.
|
// it.
|
||||||
|
|
61
src/input.h
61
src/input.h
|
@ -21,23 +21,50 @@ wcstring describe_char(wint_t c);
|
||||||
/// initializations for our input subsystem.
|
/// initializations for our input subsystem.
|
||||||
void init_input();
|
void init_input();
|
||||||
|
|
||||||
/// Read a character from fd 0. Try to convert some escape sequences into character constants, but
|
struct input_mapping_t;
|
||||||
/// do not permanently block the escape character.
|
class inputter_t {
|
||||||
///
|
input_event_queue_t event_queue_{};
|
||||||
/// This is performed in the same way vim does it, i.e. if an escape character is read, wait for
|
std::vector<wchar_t> input_function_args_{};
|
||||||
/// more input for a short time (a few milliseconds). If more input is avaialable, it is assumed to
|
bool function_status_{false};
|
||||||
/// 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.
|
|
||||||
///
|
|
||||||
/// The argument determines whether fish commands are allowed to be run as bindings. If false, when
|
|
||||||
/// a character is encountered that would invoke a fish command, it is unread and
|
|
||||||
/// char_event_type_t::check_exit is returned.
|
|
||||||
char_event_t input_readch(bool allow_commands = true);
|
|
||||||
|
|
||||||
/// Enqueue a character or a readline function to the queue of unread characters that input_readch
|
void function_push_arg(wchar_t arg);
|
||||||
/// will return before actually reading from fd 0.
|
void function_push_args(readline_cmd_t code);
|
||||||
void input_queue_ch(char_event_t ch);
|
void mapping_execute(const input_mapping_t &m, bool allow_commands);
|
||||||
|
void mapping_execute_matching_or_generic(bool allow_commands);
|
||||||
|
bool mapping_is_match(const input_mapping_t &m);
|
||||||
|
const input_mapping_t *find_mapping();
|
||||||
|
char_event_t read_characters_no_readline();
|
||||||
|
|
||||||
|
public:
|
||||||
|
inputter_t() = default;
|
||||||
|
|
||||||
|
/// Read a character from fd 0. 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 avaialable, 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.
|
||||||
|
///
|
||||||
|
/// The argument determines whether fish commands are allowed to be run as bindings. If false,
|
||||||
|
/// when a character is encountered that would invoke a fish command, it is unread and
|
||||||
|
/// char_event_type_t::check_exit is returned.
|
||||||
|
char_event_t readch(bool allow_commands = true);
|
||||||
|
|
||||||
|
/// Enqueue a char event to the queue of unread characters that input_readch will return before
|
||||||
|
/// actually reading from fd 0.
|
||||||
|
void queue_ch(char_event_t ch);
|
||||||
|
|
||||||
|
/// Enqueue a char event to the front of the queue; this will be the next event returned.
|
||||||
|
void push_front(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();
|
||||||
|
};
|
||||||
|
|
||||||
/// Add a key mapping from the specified sequence to the specified command.
|
/// Add a key mapping from the specified sequence to the specified command.
|
||||||
///
|
///
|
||||||
|
@ -77,8 +104,6 @@ wcstring input_get_bind_mode(const environment_t &vars);
|
||||||
/// Set the current bind mode.
|
/// Set the current bind mode.
|
||||||
void input_set_bind_mode(const wcstring &bind_mode);
|
void input_set_bind_mode(const wcstring &bind_mode);
|
||||||
|
|
||||||
wchar_t input_function_pop_arg();
|
|
||||||
|
|
||||||
/// Sets the return status of the most recently executed input function.
|
/// Sets the return status of the most recently executed input function.
|
||||||
void input_function_set_status(bool status);
|
void input_function_set_status(bool status);
|
||||||
|
|
||||||
|
|
|
@ -34,36 +34,6 @@
|
||||||
#define WAIT_ON_ESCAPE_DEFAULT 30
|
#define WAIT_ON_ESCAPE_DEFAULT 30
|
||||||
static int wait_on_escape_ms = WAIT_ON_ESCAPE_DEFAULT;
|
static int wait_on_escape_ms = WAIT_ON_ESCAPE_DEFAULT;
|
||||||
|
|
||||||
struct input_lookahead_t {
|
|
||||||
/// Events which have been read and returned by the sequence matching code.
|
|
||||||
std::deque<char_event_t> lookahead_list;
|
|
||||||
|
|
||||||
bool has_lookahead() const { return !lookahead_list.empty(); }
|
|
||||||
|
|
||||||
char_event_t pop() {
|
|
||||||
auto result = lookahead_list.front();
|
|
||||||
lookahead_list.pop_front();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// \return the next lookahead char, or none if none. Discards timeouts.
|
|
||||||
maybe_t<char_event_t> pop_evt() {
|
|
||||||
while (has_lookahead()) {
|
|
||||||
auto evt = pop();
|
|
||||||
if (!evt.is_timeout()) {
|
|
||||||
return evt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return none();
|
|
||||||
}
|
|
||||||
|
|
||||||
void push_back(char_event_t c) { lookahead_list.push_back(c); }
|
|
||||||
|
|
||||||
void push_front(char_event_t c) { lookahead_list.push_front(c); }
|
|
||||||
};
|
|
||||||
|
|
||||||
static mainthread_t<input_lookahead_t> s_lookahead;
|
|
||||||
|
|
||||||
/// Callback function for handling interrupts on reading.
|
/// Callback function for handling interrupts on reading.
|
||||||
static interrupt_func_t interrupt_handler;
|
static interrupt_func_t interrupt_handler;
|
||||||
|
|
||||||
|
@ -71,7 +41,7 @@ void input_common_init(interrupt_func_t func) { interrupt_handler = func; }
|
||||||
|
|
||||||
/// Internal function used by input_common_readch to read one byte from fd 0. This function should
|
/// Internal function used by input_common_readch to read one byte from fd 0. This function should
|
||||||
/// only be called by input_common_readch().
|
/// only be called by input_common_readch().
|
||||||
static char_event_t readb() {
|
char_event_t input_event_queue_t::readb() {
|
||||||
for (;;) {
|
for (;;) {
|
||||||
fd_set fdset;
|
fd_set fdset;
|
||||||
int fd_max = 0;
|
int fd_max = 0;
|
||||||
|
@ -110,7 +80,7 @@ static char_event_t readb() {
|
||||||
if (interrupt_handler) {
|
if (interrupt_handler) {
|
||||||
if (auto interrupt_evt = interrupt_handler()) {
|
if (auto interrupt_evt = interrupt_handler()) {
|
||||||
return *interrupt_evt;
|
return *interrupt_evt;
|
||||||
} else if (auto mc = s_lookahead->pop_evt()) {
|
} else if (auto mc = pop_discard_timeouts()) {
|
||||||
return *mc;
|
return *mc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -128,7 +98,7 @@ static char_event_t readb() {
|
||||||
if (barrier_from_poll || barrier_from_readability) {
|
if (barrier_from_poll || barrier_from_readability) {
|
||||||
if (env_universal_barrier()) {
|
if (env_universal_barrier()) {
|
||||||
// A variable change may have triggered a repaint, etc.
|
// A variable change may have triggered a repaint, etc.
|
||||||
if (auto mc = s_lookahead->pop_evt()) {
|
if (auto mc = pop_discard_timeouts()) {
|
||||||
return *mc;
|
return *mc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -136,7 +106,7 @@ static char_event_t readb() {
|
||||||
|
|
||||||
if (ioport > 0 && FD_ISSET(ioport, &fdset)) {
|
if (ioport > 0 && FD_ISSET(ioport, &fdset)) {
|
||||||
iothread_service_completion();
|
iothread_service_completion();
|
||||||
if (auto mc = s_lookahead->pop_evt()) {
|
if (auto mc = pop_discard_timeouts()) {
|
||||||
return *mc;
|
return *mc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -175,9 +145,25 @@ void update_wait_on_escape_ms(const environment_t &vars) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char_event_t input_common_readch() {
|
char_event_t input_event_queue_t::pop() {
|
||||||
|
auto result = queue_.front();
|
||||||
|
queue_.pop_front();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
maybe_t<char_event_t> input_event_queue_t::pop_discard_timeouts() {
|
||||||
|
while (has_lookahead()) {
|
||||||
|
auto evt = pop();
|
||||||
|
if (!evt.is_timeout()) {
|
||||||
|
return evt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return none();
|
||||||
|
}
|
||||||
|
|
||||||
|
char_event_t input_event_queue_t::readch() {
|
||||||
ASSERT_IS_MAIN_THREAD();
|
ASSERT_IS_MAIN_THREAD();
|
||||||
if (auto mc = s_lookahead->pop_evt()) {
|
if (auto mc = pop_discard_timeouts()) {
|
||||||
return *mc;
|
return *mc;
|
||||||
}
|
}
|
||||||
wchar_t res;
|
wchar_t res;
|
||||||
|
@ -215,27 +201,27 @@ char_event_t input_common_readch() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
char_event_t input_common_readch_timed(bool dequeue_timeouts) {
|
char_event_t input_event_queue_t::readch_timed(bool dequeue_timeouts) {
|
||||||
char_event_t result{char_event_type_t::timeout};
|
char_event_t result{char_event_type_t::timeout};
|
||||||
if (s_lookahead->has_lookahead()) {
|
if (has_lookahead()) {
|
||||||
result = s_lookahead->pop();
|
result = pop();
|
||||||
} else {
|
} else {
|
||||||
fd_set fds;
|
fd_set fds;
|
||||||
FD_ZERO(&fds);
|
FD_ZERO(&fds);
|
||||||
FD_SET(STDIN_FILENO, &fds);
|
FD_SET(STDIN_FILENO, &fds);
|
||||||
struct timeval tm = {wait_on_escape_ms / 1000, 1000 * (wait_on_escape_ms % 1000)};
|
struct timeval tm = {wait_on_escape_ms / 1000, 1000 * (wait_on_escape_ms % 1000)};
|
||||||
if (select(1, &fds, 0, 0, &tm) > 0) {
|
if (select(1, &fds, 0, 0, &tm) > 0) {
|
||||||
result = input_common_readch();
|
result = readch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If we got a timeout, either through dequeuing or creating, ensure it stays on the queue.
|
// If we got a timeout, either through dequeuing or creating, ensure it stays on the queue.
|
||||||
if (result.is_timeout()) {
|
if (result.is_timeout()) {
|
||||||
if (!dequeue_timeouts) s_lookahead->push_front(char_event_type_t::timeout);
|
if (!dequeue_timeouts) queue_.push_front(char_event_type_t::timeout);
|
||||||
return char_event_type_t::timeout;
|
return char_event_type_t::timeout;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void input_common_queue_ch(char_event_t ch) { s_lookahead->push_back(ch); }
|
void input_event_queue_t::push_back(char_event_t ch) { queue_.push_back(ch); }
|
||||||
|
|
||||||
void input_common_next_ch(char_event_t ch) { s_lookahead->push_front(ch); }
|
void input_event_queue_t::push_front(char_event_t ch) { queue_.push_front(ch); }
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
#include "maybe.h"
|
#include "maybe.h"
|
||||||
|
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
|
#include <queue>
|
||||||
|
|
||||||
enum class readline_cmd_t {
|
enum class readline_cmd_t {
|
||||||
beginning_of_line,
|
beginning_of_line,
|
||||||
|
@ -150,24 +151,40 @@ void input_common_init(interrupt_func_t func);
|
||||||
class environment_t;
|
class environment_t;
|
||||||
void update_wait_on_escape_ms(const environment_t &vars);
|
void update_wait_on_escape_ms(const environment_t &vars);
|
||||||
|
|
||||||
/// Function used by input_readch to read bytes from stdin until enough bytes have been read to
|
/// A class which knows how to produce a stream of input events.
|
||||||
/// convert them to a wchar_t. Conversion is done using mbrtowc. If a character has previously been
|
class input_event_queue_t {
|
||||||
/// read and then 'unread' using \c input_common_unreadch, that character is returned.
|
std::deque<char_event_t> queue_;
|
||||||
/// This function never returns a timeout.
|
|
||||||
char_event_t input_common_readch();
|
|
||||||
|
|
||||||
/// Like input_common_readch(), except it will wait at most WAIT_ON_ESCAPE milliseconds for a
|
/// \return if we have any lookahead.
|
||||||
/// character to be available for reading.
|
bool has_lookahead() { return !queue_.empty(); }
|
||||||
/// If \p dequeue_timeouts is set, remove any timeout from the queue; otherwise retain them.
|
|
||||||
char_event_t input_common_readch_timed(bool dequeue_timeouts = false);
|
|
||||||
|
|
||||||
/// Enqueue a character or a readline function to the queue of unread characters that input_readch
|
/// \return the next event in the queue.
|
||||||
/// will return before actually reading from fd 0.
|
char_event_t pop();
|
||||||
void input_common_queue_ch(char_event_t ch);
|
|
||||||
|
|
||||||
/// Add a character or a readline function to the front of the queue of unread characters. This
|
/// \return the next event in the queue, discarding timeouts.
|
||||||
/// will be the first character returned by input_readch (unless this function is called more than
|
maybe_t<char_event_t> pop_discard_timeouts();
|
||||||
/// once).
|
|
||||||
void input_common_next_ch(char_event_t ch);
|
char_event_t readb();
|
||||||
|
|
||||||
|
public:
|
||||||
|
/// 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. This
|
||||||
|
/// function never returns a timeout.
|
||||||
|
char_event_t readch();
|
||||||
|
|
||||||
|
/// Like readch(), except it will wait at most WAIT_ON_ESCAPE milliseconds for a
|
||||||
|
/// character to be available for reading.
|
||||||
|
/// If \p dequeue_timeouts is set, remove any timeout from the queue; otherwise retain them.
|
||||||
|
char_event_t readch_timed(bool dequeue_timeouts = false);
|
||||||
|
|
||||||
|
/// 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(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(char_event_t ch);
|
||||||
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -345,6 +345,8 @@ class reader_data_t : public std::enable_shared_from_this<reader_data_t> {
|
||||||
bool silent{false};
|
bool silent{false};
|
||||||
/// The representation of the current screen contents.
|
/// The representation of the current screen contents.
|
||||||
screen_t screen;
|
screen_t screen;
|
||||||
|
/// The source of input events.
|
||||||
|
inputter_t inputter;
|
||||||
/// The history.
|
/// The history.
|
||||||
history_t *history{nullptr};
|
history_t *history{nullptr};
|
||||||
/// The history search.
|
/// The history search.
|
||||||
|
@ -2371,7 +2373,7 @@ struct readline_loop_state_t {
|
||||||
/// Read normal characters, inserting them into the command line.
|
/// Read normal characters, inserting them into the command line.
|
||||||
/// \return the next unhandled event.
|
/// \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> reader_data_t::read_normal_chars(readline_loop_state_t &rls) {
|
||||||
maybe_t<char_event_t> event_needing_handling = input_readch();
|
maybe_t<char_event_t> event_needing_handling = inputter.readch();
|
||||||
|
|
||||||
if (!event_is_normal_char(*event_needing_handling) || !can_read(STDIN_FILENO))
|
if (!event_is_normal_char(*event_needing_handling) || !can_read(STDIN_FILENO))
|
||||||
return event_needing_handling;
|
return event_needing_handling;
|
||||||
|
@ -2391,7 +2393,7 @@ maybe_t<char_event_t> reader_data_t::read_normal_chars(readline_loop_state_t &rl
|
||||||
// Only allow commands on the first key; otherwise, we might have data we
|
// Only allow commands on the first key; otherwise, we might have data we
|
||||||
// need to insert on the commandline that the commmand might need to be able
|
// need to insert on the commandline that the commmand might need to be able
|
||||||
// to see.
|
// to see.
|
||||||
auto next_event = input_readch(false);
|
auto next_event = inputter.readch(false);
|
||||||
if (event_is_normal_char(next_event)) {
|
if (event_is_normal_char(next_event)) {
|
||||||
arr[i] = next_event.get_char();
|
arr[i] = next_event.get_char();
|
||||||
} else {
|
} else {
|
||||||
|
@ -3101,10 +3103,10 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
|
||||||
? jump_precision_t::to
|
? jump_precision_t::to
|
||||||
: jump_precision_t::till;
|
: jump_precision_t::till;
|
||||||
editable_line_t *el = active_edit_line();
|
editable_line_t *el = active_edit_line();
|
||||||
wchar_t target = input_function_pop_arg();
|
wchar_t target = inputter.function_pop_arg();
|
||||||
bool success = jump(direction, precision, el, target);
|
bool success = jump(direction, precision, el, target);
|
||||||
|
|
||||||
input_function_set_status(success);
|
inputter.function_set_status(success);
|
||||||
reader_repaint_needed();
|
reader_repaint_needed();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -3116,7 +3118,7 @@ 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);
|
success = jump(last_jump_direction, last_jump_precision, el, last_jump_target);
|
||||||
}
|
}
|
||||||
|
|
||||||
input_function_set_status(success);
|
inputter.function_set_status(success);
|
||||||
reader_repaint_needed();
|
reader_repaint_needed();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -3138,7 +3140,7 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
|
||||||
|
|
||||||
last_jump_direction = original_dir;
|
last_jump_direction = original_dir;
|
||||||
|
|
||||||
input_function_set_status(success);
|
inputter.function_set_status(success);
|
||||||
reader_repaint_needed();
|
reader_repaint_needed();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -3147,9 +3149,9 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
|
||||||
if (expand_abbreviation_as_necessary(1)) {
|
if (expand_abbreviation_as_necessary(1)) {
|
||||||
super_highlight_me_plenty();
|
super_highlight_me_plenty();
|
||||||
mark_repaint_needed();
|
mark_repaint_needed();
|
||||||
input_function_set_status(true);
|
inputter.function_set_status(true);
|
||||||
} else {
|
} else {
|
||||||
input_function_set_status(false);
|
inputter.function_set_status(false);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -3394,6 +3396,12 @@ void reader_repaint_if_needed() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void reader_queue_ch(const char_event_t &ch) {
|
||||||
|
if (reader_data_t *data = current_data_or_null()) {
|
||||||
|
data->inputter.queue_ch(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void reader_react_to_color_change() {
|
void reader_react_to_color_change() {
|
||||||
reader_data_t *data = current_data_or_null();
|
reader_data_t *data = current_data_or_null();
|
||||||
if (!data) return;
|
if (!data) return;
|
||||||
|
@ -3401,7 +3409,7 @@ void reader_react_to_color_change() {
|
||||||
if (!data->repaint_needed || !data->screen_reset_needed) {
|
if (!data->repaint_needed || !data->screen_reset_needed) {
|
||||||
data->repaint_needed = true;
|
data->repaint_needed = true;
|
||||||
data->screen_reset_needed = true;
|
data->screen_reset_needed = true;
|
||||||
input_common_queue_ch(readline_cmd_t::repaint);
|
data->inputter.queue_ch(readline_cmd_t::repaint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,10 @@ void reader_react_to_color_change();
|
||||||
/// Repaint immediately if needed.
|
/// Repaint immediately if needed.
|
||||||
void reader_repaint_if_needed();
|
void reader_repaint_if_needed();
|
||||||
|
|
||||||
|
/// 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);
|
||||||
|
|
||||||
/// Run the specified command with the correct terminal modes, and while taking care to perform job
|
/// Run the specified command with the correct terminal modes, and while taking care to perform job
|
||||||
/// notification, set the title, etc.
|
/// notification, set the title, etc.
|
||||||
void reader_run_command(const wcstring &buff);
|
void reader_run_command(const wcstring &buff);
|
||||||
|
|
Loading…
Reference in a new issue