From a3dfa217374a5c7aa4cff953ce98ecad19c47b18 Mon Sep 17 00:00:00 2001 From: Joel Kuhn Date: Thu, 16 Apr 2020 23:24:25 -0400 Subject: [PATCH] Change vi-mode tilde to toggle character case This updates the behavior of tilde to match the behavior found in vim. In vim, tilde toggles the case of the character under the cursor and advances one character. In visual mode, the case of each selected character is toggled, the cursor position moves to the beginning of the selection, and the mode is changed to normal. In fish, tilde capitalizes the current letter and advances one word. There is no current tilde command for visual mode in fish. This patch adds the readline commands `togglecase-letter` and `togglecase-selection` to match the behavior of vim more closely. The only difference is that in visual mode, the cursor is not modified. Modifying the cursor in visual mode would require either moving it in `togglecase-selection`, which seems outside its scope or adding something like a `move-to-selection-start` readline command. --- share/functions/fish_vi_key_bindings.fish | 3 +- src/input.cpp | 2 + src/input_common.h | 2 + src/reader.cpp | 66 +++++++++++++++++++++++ 4 files changed, 72 insertions(+), 1 deletion(-) diff --git a/share/functions/fish_vi_key_bindings.fish b/share/functions/fish_vi_key_bindings.fish index ddfaf6e86..f83bfe0a3 100644 --- a/share/functions/fish_vi_key_bindings.fish +++ b/share/functions/fish_vi_key_bindings.fish @@ -187,7 +187,7 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish' bind -s --preset -m insert ci backward-jump-till and repeat-jump-reverse and begin-selection repeat-jump kill-selection end-selection repaint-mode bind -s --preset -m insert ca backward-jump and repeat-jump-reverse and begin-selection repeat-jump kill-selection end-selection repaint-mode - bind -s --preset '~' capitalize-word + bind -s --preset '~' togglecase-char forward-char bind -s --preset gu downcase-word bind -s --preset gU upcase-word @@ -291,6 +291,7 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish' bind -s --preset -M visual -m default X kill-whole-line end-selection repaint-mode bind -s --preset -M visual -m default y kill-selection yank end-selection repaint-mode bind -s --preset -M visual -m default '"*y' "commandline -s | xsel -p; commandline -f end-selection repaint-mode" + bind -s --preset -M visual -m default '~' togglecase-selection end-selection repaint-mode bind -s --preset -M visual -m default \cc end-selection repaint-mode bind -s --preset -M visual -m default \e end-selection repaint-mode diff --git a/src/input.cpp b/src/input.cpp index 72f84a509..0c53d77ca 100644 --- a/src/input.cpp +++ b/src/input.cpp @@ -123,6 +123,8 @@ static const input_function_metadata_t input_function_metadata[] = { {readline_cmd_t::upcase_word, L"upcase-word"}, {readline_cmd_t::downcase_word, L"downcase-word"}, {readline_cmd_t::capitalize_word, L"capitalize-word"}, + {readline_cmd_t::togglecase_char, L"togglecase-char"}, + {readline_cmd_t::togglecase_selection, L"togglecase-selection"}, {readline_cmd_t::execute, L"execute"}, {readline_cmd_t::beginning_of_buffer, L"beginning-of-buffer"}, {readline_cmd_t::end_of_buffer, L"end-of-buffer"}, diff --git a/src/input_common.h b/src/input_common.h index 746b4f6d6..901acf678 100644 --- a/src/input_common.h +++ b/src/input_common.h @@ -48,6 +48,8 @@ enum class readline_cmd_t { upcase_word, downcase_word, capitalize_word, + togglecase_char, + togglecase_selection, execute, beginning_of_buffer, end_of_buffer, diff --git a/src/reader.cpp b/src/reader.cpp index f607cbff4..ce2a0bc3a 100644 --- a/src/reader.cpp +++ b/src/reader.cpp @@ -3219,6 +3219,72 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat } break; } + case rl::togglecase_char: { + editable_line_t *el = active_edit_line(); + size_t buff_pos = el->position(); + + // Check that the cursor is on a character + if (buff_pos < el->size()) { + wchar_t chr = el->text().at(buff_pos); + wcstring replacement; + + // Toggle the case of the current character + bool make_uppercase = iswlower(chr); + if (make_uppercase) { + chr = towupper(chr); + } else { + chr = tolower(chr); + } + + replacement.push_back(chr); + el->replace_substring(buff_pos, (size_t)1, std::move(replacement)); + + // Restore the buffer position since replace_substring moves + // the buffer position ahead of the replaced text. + update_buff_pos(el, buff_pos); + + command_line_changed(el); + super_highlight_me_plenty(); + reader_repaint_needed(); + } + break; + } + case rl::togglecase_selection: { + editable_line_t *el = active_edit_line(); + + // Check that we have an active selection and get the bounds. + size_t start, len; + if (reader_get_selection(&start, &len)) { + size_t buff_pos = el->position(); + wcstring replacement; + + // Loop through the selected characters and toggle their case. + for (size_t pos = start; pos < start + len && pos < el->size(); pos++) { + wchar_t chr = el->text().at(pos); + + // Toggle the case of the current character. + bool make_uppercase = iswlower(chr); + if (make_uppercase) { + chr = towupper(chr); + } else { + chr = tolower(chr); + } + + replacement.push_back(chr); + } + + el->replace_substring(start, len, std::move(replacement)); + + // Restore the buffer position since replace_substring moves + // the buffer position ahead of the replaced text. + update_buff_pos(el, buff_pos); + + command_line_changed(el); + super_highlight_me_plenty(); + reader_repaint_needed(); + } + break; + } case rl::upcase_word: case rl::downcase_word: case rl::capitalize_word: {