diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-28 16:46:46 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:15 -0300 |
| commit | ec4f300060ec0c93cc920dd5562eae1e3153d8b7 (patch) | |
| tree | 1a5da619f2e607c0805ce7f04eacb18ffe6d72f6 /src/modal.zig | |
| parent | 71c5b400ced873cddd77497b7a9fd06184954813 (diff) | |
| download | pardes-ec4f300060ec0c93cc920dd5562eae1e3153d8b7.tar.gz pardes-ec4f300060ec0c93cc920dd5562eae1e3153d8b7.zip | |
gw labels the words in view and jumps to the one whose label is typed
helix goto_word: the words of two or more word characters between the
view's first and last lines get two-letter labels, nearest the cursor first,
one forward and one back in turn (modal.jumpWords). The next two keys choose
one and select it, stretching the selection to it in select mode; any other
key takes the labels away. The words live on Pane.jump, and renderBody paints
their labels over the body's cells in the selection colours, the one change
on the render side.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/modal.zig')
| -rw-r--r-- | src/modal.zig | 51 |
1 files changed, 51 insertions, 0 deletions
diff --git a/src/modal.zig b/src/modal.zig index 16196b4f..6371475c 100644 --- a/src/modal.zig +++ b/src/modal.zig @@ -30,6 +30,7 @@ pub const Normal = struct { goto_view_center, goto_view_bottom, goto_last_modification, + goto_word, goto_definition, goto_declaration, goto_type_definition, @@ -346,6 +347,7 @@ pub const Normal = struct { pipe_selection: PipeBehavior, search, search_step: Direction, + goto_word, macro_record, macro_replay: u32, node: NodeWalk, @@ -364,6 +366,7 @@ pub const Normal = struct { .macro_record, .macro_replay, .node, + .goto_word, .repeat_insert, => .once, .edit => |edit| switch (edit.kind) { @@ -447,6 +450,7 @@ pub const Normal = struct { if (key.has(.goto_view_top)) return resultAction(.{ .goto = .{ .target = .view_top, .count = count, .explicit_count = counted.explicit } }); if (key.has(.goto_view_center)) return resultAction(.{ .goto = .{ .target = .view_center, .count = count, .explicit_count = counted.explicit } }); if (key.has(.goto_view_bottom)) return resultAction(.{ .goto = .{ .target = .view_bottom, .count = count, .explicit_count = counted.explicit } }); + if (key.has(.goto_word)) return resultAction(.goto_word); if (key.has(.goto_last_modification)) return resultAction(.{ .goto = .{ .target = .last_modification, .count = count, .explicit_count = counted.explicit } }); if (key.has(.goto_definition)) return resultAction(.{ .lsp = .definition }); if (key.has(.goto_declaration)) return resultAction(.{ .lsp = .declaration }); @@ -1382,6 +1386,53 @@ fn wmIsPrev(t: WordTarget) bool { } /// w/b/e/W/B/E: helix word_move — each step selects the traversed span. +/// helix jump_to_word's candidates: the words of two or more word +/// characters between `start` and `end`, nearest the cursor first, taking one +/// forward and one back in turn, each over its word pointing the way it was +/// found, as many as `out` holds. +pub fn jumpWords(text: []const u8, cursor: usize, start: usize, end: usize, out: []Selection) usize { + var fwd: Selection = .{ .anchor = cursor, .head = cursor }; + var rev = fwd; + if (cursor < text.len and !isWordWhitespace(wordClassAt(text, cursor))) { + const word_end = moveWord(text, fwd, 1, .next_word_end); + if (word_end.anchor == cursor) fwd = word_end; // a word of one grapheme + const word_start = moveWord(text, rev, 1, .prev_word_start); + if (word_start.anchor == nextGrapheme(text, cursor)) rev = word_start; + } + var n: usize = 0; + while (n < out.len) { + var changed = false; + while (fwd.head < end) { + fwd = moveWord(text, fwd, 1, .next_word_end); + // the two graphemes before the head are word characters + const one = prevGrapheme(text, fwd.head); + if (fwd.head == 0 or one == 0 or wordClassAt(text, one) != .word or wordClassAt(text, prevGrapheme(text, one)) != .word) continue; + changed = true; + var from = fwd.anchor; + while (from < fwd.head and wordClassAt(text, from) != .word) from = nextGrapheme(text, from); + fwd.anchor = from; + out[n] = fwd; + n += 1; + break; + } + if (n == out.len) break; + while (rev.head > start) { + rev = moveWord(text, rev, 1, .prev_word_start); + // the two graphemes from the head are word characters + if (wordClassAt(text, rev.head) != .word or wordClassAt(text, nextGrapheme(text, rev.head)) != .word) continue; + changed = true; + var to = rev.anchor; + while (to > rev.head and wordClassAt(text, prevGrapheme(text, to)) != .word) to = prevGrapheme(text, to); + rev.anchor = to; + out[n] = rev; + n += 1; + break; + } + if (!changed) break; + } + return n; +} + pub fn moveWord(text: []const u8, r0: Selection, count: usize, target: WordTarget) Selection { const is_prev = wmIsPrev(target); if ((is_prev and r0.head == 0) or (!is_prev and r0.head == text.len)) return r0; |
