summaryrefslogtreecommitdiff
path: root/src/edit.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 15:31:55 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:15 -0300
commit4315121885bebd48f8c03a2b1c7b0bd943c67afd (patch)
treeb9072c654cd486a7e817c7c3377bc49716e3afd2 /src/edit.zig
parente35e4654c34cf303cf3bc6077a3cbffee70f7909 (diff)
downloadpardes-4315121885bebd48f8c03a2b1c7b0bd943c67afd.tar.gz
pardes-4315121885bebd48f8c03a2b1c7b0bd943c67afd.zip
Alt-J joins lines and selects the spaces it put in
helix-golf's text_into_array turns lines into an array with %<A-s>ms"<A-J>i,: join_selections_space joins like J and leaves one cursor on each space it inserted. J and Alt-J now make one edit over every range, as helix does, rather than J being replayed per range; that is what lets Alt-J hand back all the spaces as the new selection. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/edit.zig')
-rw-r--r--src/edit.zig103
1 files changed, 66 insertions, 37 deletions
diff --git a/src/edit.zig b/src/edit.zig
index b548c7c6..09c3ffe7 100644
--- a/src/edit.zig
+++ b/src/edit.zig
@@ -1214,53 +1214,82 @@ fn applyChanges(p: *Pardes, text: []const u8, chs: []const TextChange) ![]u8 {
return out;
}
-pub fn normalJoin(p: *Pardes, t: *Text) void {
+/// `J` / `Alt-J`: join every range's lines with the next, in ONE edit
+/// (helix join_selections): the newline and the next line's leading blanks
+/// become one space, or nothing when that line is blank, and a line two
+/// ranges share is joined once. `Alt-J` then selects the spaces it put in.
+pub fn normalJoin(p: *Pardes, t: *Text, select_space: bool) void {
+ const expl = (t.vsel.active and t.vsel.explicit) or t.msel.active;
// a join always eats the newline of its last line, so the buffer has
- // to reach one row PAST the selection
- const sr = selRows(t);
- const eb = editText(p, t, sr.lo_row, sr.hi_row + 1, -1) orelse return;
+ // to reach one row PAST every range
+ var b = selRows(t);
+ for (t.sels[0..t.nsel]) |s| {
+ b.lo_row = @min(b.lo_row, @min(s.row, s.arow));
+ b.hi_row = @max(b.hi_row, @max(s.row, s.arow));
+ }
+ const eb = editText(p, t, b.lo_row, b.hi_row + 1, -1) orelse return;
const text = eb.text;
- const range = t.primaryRange(text, eb.row0);
- const span = Text.rangeLineSpan(text, range);
+ var rs: [Text.max_selections]modal.Selection = undefined;
+ const got = t.ranges(text, eb.row0, &rs);
const nlines = modal.cursorLineCount(text);
- var end = span.end;
- if (span.start == end) end = @min(end + 1, nlines - 1);
- if (end <= span.start) return;
const arena = p.scratch.allocator();
- const chs = arena.alloc(TextChange, end - span.start) catch return;
+ const chs = arena.alloc(TextChange, nlines) catch return;
var chs_len: usize = 0;
- var l = span.start;
- while (l < end) : (l += 1) {
- const from = modal.lineEndOffset(text, l);
- var to = if (l + 1 >= nlines) text.len else modal.lineStartOffset(text, l + 1);
- while (to < text.len and (text[to] == ' ' or text[to] == '\t')) to += 1;
- const sep: []const u8 = if (to == modal.lineEndOffset(text, @min(l + 1, nlines - 1))) "" else " ";
- chs[chs_len] = .{ .from = from, .to = to, .ins = sep };
- chs_len += 1;
+ var next: usize = 0; // lines below this one are joined already
+ for (rs[0..got.n]) |r| {
+ const span = Text.rangeLineSpan(text, r);
+ var end = span.end;
+ if (span.start == end) end = @min(end + 1, nlines - 1);
+ var l = @max(span.start, next);
+ while (l < end) : (l += 1) {
+ const from = modal.lineEndOffset(text, l);
+ var to = if (l + 1 >= nlines) text.len else modal.lineStartOffset(text, l + 1);
+ while (to < text.len and (text[to] == ' ' or text[to] == '\t')) to += 1;
+ const sep: []const u8 = if (to == modal.lineEndOffset(text, @min(l + 1, nlines - 1))) "" else " ";
+ chs[chs_len] = .{ .from = from, .to = to, .ins = sep };
+ chs_len += 1;
+ }
+ next = @max(next, end);
}
if (chs_len == 0) return;
pushUndo(p, t);
- const cur_off = modal.offsetAt(text, .{ .row = @intCast(@max(0, t.cur_row - eb.row0)), .col = @intCast(@max(0, t.cur_col)) });
- const anc_off = if (t.vsel.active)
- modal.offsetAt(text, .{ .row = @intCast(@max(0, t.vsel.row - eb.row0)), .col = @intCast(@max(0, t.vsel.col)) })
- else
- cur_off;
const new = applyChanges(p, text, chs[0..chs_len]) catch return;
- const nc = mapThroughChanges(chs[0..chs_len], cur_off);
- const na = mapThroughChanges(chs[0..chs_len], anc_off);
- setEditText(p, t, new);
- const cc = modal.positionAt(new, nc);
- const ac = modal.positionAt(new, na);
- t.cur_row = @as(i32, @intCast(cc.row)) + eb.row0;
- t.cur_col = @intCast(cc.col);
- if (t.vsel.active) {
- t.vsel.row = @as(i32, @intCast(ac.row)) + eb.row0;
- t.vsel.col = @intCast(ac.col);
+ // every range's cells ride through the edit; Alt-J replaces them with
+ // the spaces, when it put any in
+ var cells: [Text.max_selections]Text.SelRange = undefined;
+ var n: usize = 0;
+ var pri = got.pri;
+ if (select_space) {
+ var delta: i64 = 0;
+ for (chs[0..chs_len]) |ch| {
+ const at: usize = @intCast(@as(i64, @intCast(ch.from)) + delta);
+ delta += @as(i64, @intCast(ch.ins.len)) - @as(i64, @intCast(ch.to - ch.from));
+ if (ch.ins.len == 0 or n == cells.len) continue;
+ const pos = modal.positionAt(new, at);
+ cells[n] = .{ .row = @as(i32, @intCast(pos.row)) + eb.row0, .col = @intCast(pos.col), .arow = @as(i32, @intCast(pos.row)) + eb.row0, .acol = @intCast(pos.col) };
+ n += 1;
+ }
+ pri = 0;
}
- t.msel.active = false;
- t.cur_pinned = true;
- t.sticky_col = -1;
- t.show();
+ if (n == 0) {
+ for (rs[0..got.n]) |r| {
+ const c = Text.rangeCells(text, r);
+ const cc = modal.positionAt(new, mapThroughChanges(chs[0..chs_len], c.cur));
+ const ac = modal.positionAt(new, mapThroughChanges(chs[0..chs_len], c.anc));
+ cells[n] = .{
+ .row = @as(i32, @intCast(cc.row)) + eb.row0,
+ .col = @intCast(cc.col),
+ .arow = @as(i32, @intCast(ac.row)) + eb.row0,
+ .acol = @intCast(ac.col),
+ };
+ n += 1;
+ }
+ pri = got.pri;
+ }
+ setEditText(p, t, new);
+ const t2 = flatSurface(p, t) catch return;
+ for (cells[0..n], 0..) |s, i| rs[i] = Text.cellRange(t2, s.arow, s.acol, s.row, s.col);
+ t.setRanges(t2, rs[0..n], &.{}, pri, expl);
}
pub fn normalIndent(p: *Pardes, t: *Text, cnt: usize, add: bool) void {