Merge branch 'self-insert-notfirst-3.1.1' into Integration_3.1.1

This is the 3.1.1 set of changes to improve pasting behavior.

cherry-picked from #6713
This commit is contained in:
ridiculousfish 2020-03-07 14:21:32 -08:00
commit 4c30e5ad44
7 changed files with 75 additions and 44 deletions

View File

@ -161,7 +161,7 @@ function __fish_shared_key_bindings -d "Bindings shared between emacs and vi mod
bind --preset -M paste \\ "__fish_commandline_insert_escaped \\\ \$__fish_paste_quoted"
# Only insert spaces if we're either quoted or not at the beginning of the commandline
# - this strips leading spaces if they would trigger histignore.
bind --preset -M paste \ 'if set -q __fish_paste_quoted[1]; or string length -q -- (commandline -c); commandline -i " "; end'
bind --preset -M paste " " self-insert-notfirst
end
function __fish_commandline_insert_escaped --description 'Insert the first arg escaped if a second arg is given'

View File

@ -120,6 +120,8 @@ The following special input functions are available:
- ``execute`` run the current commandline
- ``force-repaint`` reexecute the prompt functions without coalescing
- ``forward-bigword``, move one whitespace-delimited word to the right
- ``forward-char``, move one character to the right
@ -160,7 +162,9 @@ The following special input functions are available:
- ``repaint-mode`` reexecutes the fish_mode_prompt function and redraws the prompt. This is useful for vi-mode. If no fish_mode_prompt exists, it acts like a normal repaint.
- ``force-repaint`` reexecute the prompt functions without coalescing.
- ``self-insert``, inserts the matching sequence into the command line
- ``self-insert-notfirst``, inserts the matching sequence into the command line, unless the cursor is at the beginning
- ``suppress-autosuggestion``, remove the current autosuggestion

View File

@ -117,6 +117,7 @@ static const input_function_metadata_t input_function_metadata[] = {
{readline_cmd_t::history_token_search_backward, L"history-token-search-backward"},
{readline_cmd_t::history_token_search_forward, L"history-token-search-forward"},
{readline_cmd_t::self_insert, L"self-insert"},
{readline_cmd_t::self_insert_notfirst, L"self-insert-notfirst"},
{readline_cmd_t::transpose_chars, L"transpose-chars"},
{readline_cmd_t::transpose_words, L"transpose-words"},
{readline_cmd_t::upcase_word, L"upcase-word"},
@ -496,7 +497,8 @@ char_event_t inputter_t::readch(bool allow_commands) {
if (evt.is_readline()) {
switch (evt.get_readline()) {
case readline_cmd_t::self_insert: {
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.
for (auto iter = evt.seq.crbegin(); iter != evt.seq.crend(); ++iter) {
@ -504,7 +506,13 @@ char_event_t inputter_t::readch(bool allow_commands) {
}
// Issue #1595: ensure we only insert characters, not readline functions. The
// common case is that this will be empty.
return read_characters_no_readline();
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: {
if (function_status_) {

View File

@ -42,6 +42,7 @@ enum class readline_cmd_t {
history_token_search_backward,
history_token_search_forward,
self_insert,
self_insert_notfirst,
transpose_chars,
transpose_words,
upcase_word,
@ -96,6 +97,16 @@ enum class char_event_type_t : uint8_t {
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.
@ -109,6 +120,9 @@ class char_event_t {
/// 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{};

View File

@ -1060,6 +1060,7 @@ static bool command_ends_paging(readline_cmd_t c, bool focused_on_search_field)
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:
@ -2390,47 +2391,36 @@ struct readline_loop_state_t {
/// 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 = inputter.readch();
if (!event_is_normal_char(*event_needing_handling) || !can_read(STDIN_FILENO))
return event_needing_handling;
// This is a normal character input.
// We are going to handle it directly, accumulating more.
char_event_t evt = event_needing_handling.acquire();
maybe_t<char_event_t> event_needing_handling{};
wcstring accumulated_chars;
size_t limit = std::min(rls.nchars - command_line.size(), READAHEAD_MAX);
wchar_t arr[READAHEAD_MAX + 1] = {};
arr[0] = evt.get_char();
for (size_t i = 1; i < limit; ++i) {
if (!can_read(0)) {
while (accumulated_chars.size() < limit) {
bool allow_commands = (accumulated_chars.empty());
auto evt = inputter.readch(allow_commands);
if (!event_is_normal_char(evt) || !can_read(STDIN_FILENO)) {
event_needing_handling = std::move(evt);
break;
}
// Only allow commands on the first key; otherwise, we might have data we
// need to insert on the commandline that the command might need to be able
// to see.
auto next_event = inputter.readch(false);
if (event_is_normal_char(next_event)) {
arr[i] = next_event.get_char();
} else if (evt.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 {
// We need to process this in the outer loop.
assert(!event_needing_handling && "Should not have an unhandled event");
event_needing_handling = next_event;
break;
accumulated_chars.push_back(evt.get_char());
}
}
editable_line_t *el = active_edit_line();
insert_string(el, arr);
if (!accumulated_chars.empty()) {
editable_line_t *el = active_edit_line();
insert_string(el, accumulated_chars);
// End paging upon inserting into the normal command line.
if (el == &command_line) {
clear_pager();
// End paging upon inserting into the normal command line.
if (el == &command_line) {
clear_pager();
}
// Since we handled a normal character, we don't have a last command.
rls.last_cmd.reset();
}
// Since we handled a normal character, we don't have a last command.
rls.last_cmd.reset();
return event_needing_handling;
}
@ -3179,12 +3169,11 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
}
break;
}
// Some commands should have been handled internally by input_readch().
case rl::self_insert: {
DIE("self-insert should have been handled by inputter_t::readch");
}
// Some commands should have been handled internally by inputter_t::readch().
case rl::self_insert:
case rl::self_insert_notfirst:
case rl::func_and: {
DIE("self-insert should have been handled by inputter_t::readch");
DIE("should have been handled by inputter_t::readch");
}
}
}
@ -3298,8 +3287,11 @@ maybe_t<wcstring> reader_data_t::readline(int nchars_or_0) {
} else {
// Ordinary char.
wchar_t c = event_needing_handling->get_char();
if (!fish_reserved_codepoint(c) && (c >= L' ' || c == L'\n' || c == L'\r') &&
c != 0x7F) {
if (event_needing_handling->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') &&
c != 0x7F) {
// Regular character.
editable_line_t *el = active_edit_line();
insert_char(active_edit_line(), c);

View File

@ -299,3 +299,15 @@ expect_prompt -re {nul seen\r\nnul seen\r\nnul seen} {
} unmatched {
puts stderr "nul not seen"
}
# Test self-insert-notfirst. (#6603)
# Here the leading 'q's should be stripped, but the trailing ones not.
send "bind q self-insert-notfirst\r"
expect_prompt
send "qqqecho qqq"
send "\r"
expect_prompt -re {qqq} {
puts "Leading q properly stripped"
} unmatched {
puts stderr "Leading qs not stripped"
}

View File

@ -23,3 +23,4 @@ ctrl-o seen
ctrl-w stops at :
ctrl-w stops at @
nul seen
Leading q properly stripped