feat: add counted forward and backward character seek motions

This commit is contained in:
2026-10-11 12:34:38 +02:00
parent 5186714ef5
commit 0ca0d89b9e
7 changed files with 837 additions and 13 deletions
+10 -1
View File
@@ -16,6 +16,7 @@ Default controls and editing behavior. For custom bindings, see [configuration](
| Normal, Visual | `I` (Shift+i) / `A` (Shift+a) insert at first nonblank / line end; Visual splits each touched row into a caret first |
| Normal | `h`/`j`/`k`/`l` or arrows move every caret; `0`/`$` or Home/End |
| Normal | `w`/`b`/`e` move every caret to next word start, previous/current word start, word end; `W`/`B`/`E` for whitespace-delimited WORDs |
| Normal, Visual | `f<char>` seeks forward on the current line; `F<char>` seeks backward; `2fx` finds the second `x` after each caret |
| Normal | `gf` follows a Markdown file link or heading fragment at the primary caret |
| Normal, Visual | `gt` goes to the first line; `gb` goes to the last line, at first nonblank; Visual extends every selection |
| Normal | Ctrl+o goes to an older jump location; Ctrl+i/Tab goes forward; counts select multiple locations |
@@ -61,9 +62,17 @@ Word motions separate runs of Unicode letters/numbers/underscore from runs of pu
These keys type literal text in Insert mode. The corresponding actions can also be bound in Insert mode to move all insertion carets without splitting their undo group. Motions never edit text, add history, or clear an active search highlight.
## Character seeks
`f<char>` / `F<char>` moves every caret to the next / previous matching grapheme on its current line, excluding the current position. A count seeks that occurrence independently for each caret: `2fx` finds the second following `x`, and `2Fx` the second preceding `x`. With too few matches, that caret stays unchanged. Seeks never wrap or cross lines, and Unicode targets land on whole graphemes, including combining sequences and ZWJ emoji.
Normal and both Visual shapes support these motions, including read-only diff panes. Visual keeps its anchors; native edit replay retains the seek's direction, target, and count rather than its original distance. The following character is literal even if it is a digit, leader key, remapped key, or diff expansion bracket. Escape cancels only the pending seek without moving or collapsing selections. `f`/`F` remain literal text in Insert and command/search prompts; `gf` retains its Markdown-link behavior.
Bind `config.core.seek_forward` / `config.core.seek_backward` to another Normal or Visual key or leader sequence to start the same target-character prompt.
## Motion counts
Counts apply to `h`/`j`/`k`/`l`, arrows, `w`/`b`/`e`, `W`/`B`/`E`, and `n`/`N`, including remapped keys for those actions. Every caret moves independently before duplicate/overlapping targets normalize. Vertical counts preserve the clamping behavior of repeated steps through short rows; horizontal and word motions stop at buffer boundaries. Search counts wrap through the available matches.
Counts apply to `h`/`j`/`k`/`l`, arrows, `w`/`b`/`e`, `W`/`B`/`E`, `f<char>`/`F<char>`, and `n`/`N`, including remapped keys for those actions. Every caret moves independently before duplicate/overlapping targets normalize. Vertical counts preserve the clamping behavior of repeated steps through short rows; horizontal and word motions stop at buffer boundaries. Character seeks require the requested occurrence on the current line or leave that caret unchanged. Search counts wrap through the available matches.
Unbound ASCII digits `1`–`9` start a count in Normal/Visual; subsequent digits extend it. `0` retains line-start behavior without a count and extends an existing count when bound to line-start. Explicit digit bindings, including disabled keys, take precedence. Count arithmetic saturates rather than overflowing; large counts stop at motion boundaries or wrap for search, without executing an unbounded repeat loop.