Merge pull request #11644

This commit is contained in:
Johannes Altmanninger
2025-07-11 12:10:49 +02:00
5 changed files with 44 additions and 2 deletions

View File

@@ -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 <vi-mode-command>`.
- :kbd:`g,u` lowercases the selection, and enters :ref:`command mode <vi-mode-command>`.
- :kbd:`g,U` uppercases the selection, and enters :ref:`command mode <vi-mode-command>`.
- :kbd:`",*,y` copies the selection to the clipboard, and enters :ref:`command mode <vi-mode-command>`.
.. _custom-binds:

View File

@@ -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

View File

@@ -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),

View File

@@ -108,6 +108,8 @@ pub enum ReadlineCmd {
DowncaseWord,
CapitalizeWord,
TogglecaseChar,
UpcaseSelection,
DowncaseSelection,
TogglecaseSelection,
Execute,
BeginningOfBuffer,

View File

@@ -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.