diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f8fd1087e..e72c6209f 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -181,6 +181,7 @@ New or improved bindings - When the cursor is at the start of a line, escaping from insert mode no longer moves the cursor to the previous line. - Added bindings for clipboard interaction, like :kbd:`",+,p` and :kbd:`",+,y,y`. - Deleting in visual mode now moves the cursor back, matching vi (:issue:`10394`). + - Support :kbd:`%` motion. Completions ^^^^^^^^^^^ diff --git a/doc_src/cmds/bind.rst b/doc_src/cmds/bind.rst index d98d2daea..227034846 100644 --- a/doc_src/cmds/bind.rst +++ b/doc_src/cmds/bind.rst @@ -258,6 +258,11 @@ The following special input functions are available: ``repeat-jump`` and ``repeat-jump-reverse`` redo the last jump in the same/opposite direction +``jump-to-matching-bracket`` + jump to matching bracket if the character under the cursor is bracket; + otherwise, jump to the next occurence of *any right* bracket after the cursor. + The following brackets are considered: ``([{}])`` + ``kill-bigword`` move the next whitespace-delimited word to the killring diff --git a/share/functions/fish_vi_key_bindings.fish b/share/functions/fish_vi_key_bindings.fish index ef137358e..c7ecc9c85 100644 --- a/share/functions/fish_vi_key_bindings.fish +++ b/share/functions/fish_vi_key_bindings.fish @@ -238,6 +238,7 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish' bind -s --preset y,i backward-jump-till and repeat-jump-reverse and begin-selection repeat-jump kill-selection yank end-selection bind -s --preset y,a backward-jump and repeat-jump-reverse and begin-selection repeat-jump kill-selection yank end-selection + bind -s --preset % jump-to-matching-bracket bind -s --preset f forward-jump bind -s --preset F backward-jump bind -s --preset t forward-jump-till @@ -305,6 +306,7 @@ function fish_vi_key_bindings --description 'vi-like key bindings for fish' bind -s --preset -M visual E 'set fish_cursor_end_mode exclusive' forward-single-char forward-bigword backward-char 'set fish_cursor_end_mode inclusive' bind -s --preset -M visual o swap-selection-start-stop repaint-mode + bind -s --preset -M visual % jump-to-matching-bracket bind -s --preset -M visual f forward-jump bind -s --preset -M visual t forward-jump-till bind -s --preset -M visual F backward-jump diff --git a/src/input.rs b/src/input.rs index 905ab4f77..c1f86d806 100644 --- a/src/input.rs +++ b/src/input.rs @@ -178,6 +178,7 @@ const INPUT_FUNCTION_METADATA: &[InputFunctionMetadata] = &[ make_md(L!("history-token-search-forward"), ReadlineCmd::HistoryTokenSearchForward), make_md(L!("insert-line-over"), ReadlineCmd::InsertLineOver), make_md(L!("insert-line-under"), ReadlineCmd::InsertLineUnder), + make_md(L!("jump-to-matching-bracket"), ReadlineCmd::JumpToMatchingBracket), make_md(L!("kill-bigword"), ReadlineCmd::KillBigword), make_md(L!("kill-inner-line"), ReadlineCmd::KillInnerLine), make_md(L!("kill-line"), ReadlineCmd::KillLine), diff --git a/src/input_common.rs b/src/input_common.rs index 01da00110..1501ec9be 100644 --- a/src/input_common.rs +++ b/src/input_common.rs @@ -109,6 +109,7 @@ pub enum ReadlineCmd { BackwardJump, ForwardJumpTill, BackwardJumpTill, + JumpToMatchingBracket, FuncAnd, FuncOr, ExpandAbbr, diff --git a/src/reader.rs b/src/reader.rs index b4528d9e6..476c4acbf 100644 --- a/src/reader.rs +++ b/src/reader.rs @@ -1739,6 +1739,42 @@ impl ReaderData { } } + fn jump_to_matching_bracket( + &mut self, + precision: JumpPrecision, + elt: EditableLineTag, + jump_from: usize, + l_bracket: char, + r_bracket: char, + ) -> bool { + let el = self.edit_line(elt); + let mut tmp_r_pos: usize = 0; + let mut brackets_stack = Vec::new(); + while tmp_r_pos < el.len() { + if el.at(tmp_r_pos) == l_bracket { + brackets_stack.push(tmp_r_pos); + } else if el.at(tmp_r_pos) == r_bracket { + match brackets_stack.pop() { + Some(tmp_l_pos) if jump_from == tmp_l_pos => { + return match precision { + JumpPrecision::Till => self.update_buff_pos(elt, Some(tmp_r_pos - 1)), + JumpPrecision::To => self.update_buff_pos(elt, Some(tmp_r_pos)), + }; + } + Some(tmp_l_pos) if jump_from == tmp_r_pos => { + return match precision { + JumpPrecision::Till => self.update_buff_pos(elt, Some(tmp_l_pos + 1)), + JumpPrecision::To => self.update_buff_pos(elt, Some(tmp_l_pos)), + }; + } + _ => {} + } + } + tmp_r_pos += 1; + } + return false; + } + fn jump_and_remember_last_jump( &mut self, direction: JumpDirection, @@ -3113,6 +3149,41 @@ impl<'a> Reader<'a> { self.input_data.function_set_status(success); } } + rl::JumpToMatchingBracket => { + let (elt, _el) = self.active_edit_line(); + let el = self.edit_line(elt); + let l_brackets = ['(', '[', '{']; + let r_brackets = [')', ']', '}']; + let jump_from_pos = el.position(); + let precision = JumpPrecision::To; + let success = if l_brackets.contains(&el.at(jump_from_pos)) + || r_brackets.contains(&el.at(jump_from_pos)) + { + let l_bracket = match el.at(jump_from_pos) { + '(' | ')' => '(', + '[' | ']' => '[', + '{' | '}' => '{', + _ => unreachable!(), + }; + let r_bracket = match l_bracket { + '(' => ')', + '[' => ']', + '{' => '}', + _ => unreachable!(), + }; + self.jump_to_matching_bracket( + precision, + elt, + jump_from_pos, + l_bracket, + r_bracket, + ) + } else { + // If we stand on non-bracket character, we prefer to jump forward + self.jump(JumpDirection::Forward, precision, elt, r_brackets.to_vec()) + }; + self.input_data.function_set_status(success); + } rl::RepeatJump => { let (elt, _el) = self.active_edit_line(); let mut success = false;