diff --git a/doc_src/interactive.rst b/doc_src/interactive.rst index ecb4c2ed4..3f5d5e2cf 100644 --- a/doc_src/interactive.rst +++ b/doc_src/interactive.rst @@ -507,7 +507,13 @@ Command mode is also known as normal mode. - :kbd:`backspace` moves the cursor left. -- :kbd:`g` / :kbd:`G` moves the cursor to the beginning/end of the commandline, respectively. +- :kbd:`g,g` / :kbd:`G` moves the cursor to the beginning/end of the commandline, respectively. + +- :kbd:`~` toggles the case (upper/lower) of the character and moves to the next character. + +- :kbd:`g,u` lowercases to the end of the word. + +- :kbd:`g,U` uppercases to the end of the word. - :kbd:`:,q` exits fish. @@ -551,6 +557,10 @@ Visual mode - :kbd:`~` toggles the case (upper/lower) on the selection, and enters :ref:`command mode `. +- :kbd:`g,u` lowercases the selection, and enters :ref:`command mode `. + +- :kbd:`g,U` uppercases the selection, and enters :ref:`command mode `. + - :kbd:`",*,y` copies the selection to the clipboard, and enters :ref:`command mode `. .. _custom-binds: diff --git a/share/functions/fish_vi_key_bindings.fish b/share/functions/fish_vi_key_bindings.fish index a25bcaa1c..4cde94038 100644 --- a/share/functions/fish_vi_key_bindings.fish +++ b/share/functions/fish_vi_key_bindings.fish @@ -400,7 +400,8 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish' bind -s --preset -M visual -m default '",*,y' "fish_clipboard_copy; commandline -f end-selection repaint-mode" bind -s --preset -M visual -m default '",+,y' "fish_clipboard_copy; 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 g,U togglecase-selection end-selection repaint-mode + bind -s --preset -M visual -m default g,u downcase-selection end-selection repaint-mode + bind -s --preset -M visual -m default g,U upcase-selection end-selection repaint-mode bind -s --preset -M visual -m default ctrl-c end-selection repaint-mode bind -s --preset -M visual -m default escape end-selection repaint-mode diff --git a/src/input.rs b/src/input.rs index bf87aa6f7..ee0e2dac4 100644 --- a/src/input.rs +++ b/src/input.rs @@ -144,6 +144,7 @@ const fn make_md(name: &'static wstr, code: ReadlineCmd) -> InputFunctionMetadat make_md(L!("delete-char"), ReadlineCmd::DeleteChar), make_md(L!("delete-or-exit"), ReadlineCmd::DeleteOrExit), make_md(L!("down-line"), ReadlineCmd::DownLine), + make_md(L!("downcase-selection"), ReadlineCmd::DowncaseSelection), make_md(L!("downcase-word"), ReadlineCmd::DowncaseWord), make_md(L!("end-of-buffer"), ReadlineCmd::EndOfBuffer), make_md(L!("end-of-history"), ReadlineCmd::EndOfHistory), @@ -205,6 +206,7 @@ const fn make_md(name: &'static wstr, code: ReadlineCmd) -> InputFunctionMetadat make_md(L!("transpose-words"), ReadlineCmd::TransposeWords), make_md(L!("undo"), ReadlineCmd::Undo), make_md(L!("up-line"), ReadlineCmd::UpLine), + make_md(L!("upcase-selection"), ReadlineCmd::UpcaseSelection), make_md(L!("upcase-word"), ReadlineCmd::UpcaseWord), make_md(L!("yank"), ReadlineCmd::Yank), make_md(L!("yank-pop"), ReadlineCmd::YankPop), diff --git a/src/input_common.rs b/src/input_common.rs index 9cc69e802..49f00c892 100644 --- a/src/input_common.rs +++ b/src/input_common.rs @@ -108,6 +108,8 @@ pub enum ReadlineCmd { DowncaseWord, CapitalizeWord, TogglecaseChar, + UpcaseSelection, + DowncaseSelection, TogglecaseSelection, Execute, BeginningOfBuffer, diff --git a/src/reader.rs b/src/reader.rs index 00b44b703..f40f7034d 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -3574,6 +3574,33 @@ fn handle_readline_command(&mut self, c: ReadlineCmd) { self.update_buff_pos(elt, Some(buff_pos)); } } + rl::UpcaseSelection | rl::DowncaseSelection => { + let (elt, el) = self.active_edit_line(); + + // Check that we have an active selection and get the bounds. + if let Some(selection) = self.get_selection() { + let text = &el.text().as_char_slice()[selection.clone()]; + let replacement = if c == rl::UpcaseSelection { + WString::from_iter(text.iter().flat_map(|c| c.to_uppercase())) + } else { + WString::from_iter(text.iter().flat_map(|c| c.to_lowercase())) + }; + + let buff_pos = el.position(); + self.replace_substring(elt, selection, replacement); + + // Restore the buffer position since replace_substring moves + // the buffer position ahead of the replaced text. + // Note: This does not take string length changes into account. + // E.g.: When the cursor was at the right of the selection, + // the selection contains 'ẞ', which is uppercased into 'SS', + // the cursor will stay at the same offset, but it will not be on the same + // character as before. + // The position calculations work on codepoints rather than graphemes, which can + // result in additional issues. + self.update_buff_pos(elt, Some(buff_pos)); + } + } rl::UpcaseWord | rl::DowncaseWord | rl::CapitalizeWord => { let (elt, el) = self.active_edit_line(); // For capitalize_word, whether we've capitalized a character so far.