summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Terminal.zig5
-rw-r--r--src/Text.zig42
-rw-r--r--src/edit.zig10
-rw-r--r--src/layout.zig6
-rw-r--r--src/normal.zig28
-rw-r--r--src/pardes.zig10
6 files changed, 83 insertions, 18 deletions
diff --git a/src/Terminal.zig b/src/Terminal.zig
index 4c29e151..ba156ada 100644
--- a/src/Terminal.zig
+++ b/src/Terminal.zig
@@ -545,10 +545,7 @@ test "replay ownership is terminal-only and construction rolls back on allocatio
try std.testing.expectEqual(terminal and enabled, pane.terminal != null);
}
};
- // Most of it is the three texts' selections, limits.selections ranges
- // each held inline (about 20 KiB a text on the desktop), and the regex
- // prompt's snapshot of them.
- try std.testing.expect(@sizeOf(Pane) < 160 * 1024);
+ try std.testing.expect(@sizeOf(Pane) < 128 * 1024);
try std.testing.checkAllAllocationFailures(std.testing.allocator, Case.run, .{false});
try std.testing.checkAllAllocationFailures(std.testing.allocator, Case.run, .{true});
}
diff --git a/src/Text.zig b/src/Text.zig
index 70ec5369..a44241ca 100644
--- a/src/Text.zig
+++ b/src/Text.zig
@@ -60,8 +60,14 @@ cur_row: i32 = 0,
cur_col: i32 = 0,
msel: LineSel = .{},
vsel: CharSel = .{},
-sels: [max_selections - 1]SelRange = undefined,
+/// The ranges beside the primary, `nsel` of them: room for max_selections - 1
+/// made when a second range appears and given back when the selection is one
+/// range again, so a single cursor holds nothing here.
+sels: []SelRange = &.{},
nsel: u16 = 0,
+/// What a column's or the workspace's tag allocates its ranges from; a text
+/// with a pane uses the pane's.
+gpa: ?std.mem.Allocator = null,
select: bool = false,
/// sticky goal column for j/k runs (helix old_visual_position): any
/// non-vertical range write resets it to -1.
@@ -101,10 +107,39 @@ pub fn pane(t: *Text) ?*Pane {
/// Free what a text owns: its characters and the buffers its undo keeps.
pub fn deinit(t: *Text, gpa: std.mem.Allocator) void {
+ if (t.sels.len > 0) gpa.free(t.sels);
if (t.own) |own| gpa.free(own);
for (t.ed_undo[0..t.ed_undo_len]) |item| if (item.ovl) |overlay| gpa.free(overlay.text);
for (t.ed_redo[0..t.ed_redo_len]) |item| if (item.ovl) |overlay| gpa.free(overlay.text);
- t.* = .{ .what = t.what };
+ t.* = .{ .what = t.what, .gpa = t.gpa };
+}
+
+fn allocator(t: *Text) ?std.mem.Allocator {
+ return if (t.pane()) |w| w.gpa else t.gpa;
+}
+
+/// Room for the ranges beside the primary; false when there is none to be had.
+pub fn reserveSels(t: *Text) bool {
+ if (t.sels.len > 0) return true;
+ const gpa = t.allocator() orelse return false;
+ t.sels = gpa.alloc(SelRange, max_selections - 1) catch return false;
+ return true;
+}
+
+/// The ranges beside the primary as they were (a refused edit's undoing).
+pub fn restoreSels(t: *Text, kept: []const SelRange) void {
+ if (kept.len == 0) return t.dropSels();
+ if (!t.reserveSels()) return;
+ @memcpy(t.sels[0..kept.len], kept);
+ t.nsel = @intCast(kept.len);
+}
+
+/// Back to one range: the room for the others goes.
+pub fn dropSels(t: *Text) void {
+ t.nsel = 0;
+ if (t.sels.len == 0) return;
+ if (t.allocator()) |gpa| gpa.free(t.sels);
+ t.sels = &.{};
}
/// Bring the cursor into view (acme's textshow). Only a body scrolls to it;
@@ -185,7 +220,7 @@ pub fn setRange(t: *Text, text: []const u8, row0: i32, r0: modal.Selection, expl
.explicit = explicit or t.select,
};
t.msel.active = false;
- t.nsel = 0; // writing ONE range means the selection IS that range
+ t.dropSels(); // writing ONE range means the selection IS that range
t.cur_pinned = true;
t.sticky_col = -1;
t.normal.clear();
@@ -262,6 +297,7 @@ pub fn setRanges(t: *Text, text: []const u8, in: []const modal.Selection, sticky
}
n = k + 1;
setRange(t, text, 0, r[pri], explicit);
+ if (n > 1 and !t.reserveSels()) return; // no room: the primary alone
t.sticky_col = st[pri];
var w: usize = 0;
for (r[0..n], 0..) |x, idx| {
diff --git a/src/edit.zig b/src/edit.zig
index 01b34237..5437a67d 100644
--- a/src/edit.zig
+++ b/src/edit.zig
@@ -167,10 +167,16 @@ fn pasteInto(p: *Pardes, t: *Text, bytes: []const u8) void {
if (bytes.len == 0) return;
const typed: pardes.Key = .{ .cp = bytes[0], .text = bytes };
if (t.what == .tag and t.mode == .insert) if (t.pane()) |pane| if (tagline.typedIntoName(p, pane, typed)) return;
- const kept = .{ t.cur_row, t.cur_col, t.vsel, t.msel, t.sels, t.nsel };
+ const kept = .{ t.cur_row, t.cur_col, t.vsel, t.msel };
+ var kept_sels: [Text.max_selections - 1]Text.SelRange = undefined;
+ const kept_n = t.nsel;
+ @memcpy(kept_sels[0..kept_n], t.sels[0..kept_n]);
t.refused = false;
if (t.mode == .insert) insertKey(p, t, typed) else pasteText(p, t, bytes, false, 1);
- if (t.refused) t.cur_row, t.cur_col, t.vsel, t.msel, t.sels, t.nsel = kept;
+ if (t.refused) {
+ t.cur_row, t.cur_col, t.vsel, t.msel = kept;
+ t.restoreSels(kept_sels[0..kept_n]);
+ }
}
pub fn clipYank(p: *Pardes, pane: *Pane, main_only: bool) void {
diff --git a/src/layout.zig b/src/layout.zig
index 8e44f1d9..be8bad67 100644
--- a/src/layout.zig
+++ b/src/layout.zig
@@ -947,7 +947,7 @@ pub fn removePane(p: *Pardes, id: usize) void {
p.col_serial[j] = p.col_serial[j + 1];
}
p.ncol -= 1;
- p.col_tags[p.ncol] = .{ .what = .column };
+ p.col_tags[p.ncol] = .{ .what = .column, .gpa = p.gpa };
p.col_serial[p.ncol] = 0;
}
}
@@ -971,7 +971,7 @@ pub fn joinCol(p: *Pardes) void {
p.col_serial[j] = p.col_serial[j + 1];
}
p.ncol -= 1;
- p.col_tags[p.ncol] = .{ .what = .column };
+ p.col_tags[p.ncol] = .{ .what = .column, .gpa = p.gpa };
p.col_serial[p.ncol] = 0;
}
@@ -1021,7 +1021,7 @@ pub fn splitColumn(p: *Pardes, source_id: usize, id: usize, before: bool) bool {
p.col_serial[j] = p.col_serial[j - 1];
}
p.col_weight[c] = new_weight;
- p.col_tags[c] = .{ .what = .column };
+ p.col_tags[c] = .{ .what = .column, .gpa = p.gpa };
p.col_serial[c] = 0;
p.col_panes[c][0] = id;
p.col_n[c] = 1;
diff --git a/src/normal.zig b/src/normal.zig
index 4459ab53..2aea2e54 100644
--- a/src/normal.zig
+++ b/src/normal.zig
@@ -686,10 +686,7 @@ fn executeNormalAction(p: *Pardes, t: *Text, semantic: modal.Normal.Action) void
.all => return t.setRange(text, 0, .{ .anchor = 0, .head = text.len }, false),
},
.multi => |multi| {
- if (multi.kind == .keep_primary) {
- t.nsel = 0;
- return;
- }
+ if (multi.kind == .keep_primary) return t.dropSels();
return t.multiSelAction(text, multi.kind, multi.count);
},
// s, S and | answer for the text they are pressed in; `/` searches
@@ -922,6 +919,7 @@ test "flat text movement and selection replay need no scratch rows" {
pane.body.cur_row = 1024;
pane.body.cur_col = 0;
pane.body.nsel = 1;
+ _ = pane.body.reserveSels();
pane.body.sels[0] = .{ .row = 3072, .col = 0, .arow = 3072, .acol = 0 };
handleNormal(p, &pane.body, .{ .cp = 'l' });
handleNormal(p, &pane.body, .{ .cp = 'j' });
@@ -1021,3 +1019,25 @@ test "tree-sitter selection keys do in a JSON file what hx 25.07.1 does" {
};
}
}
+
+test "one cursor holds no room for other ranges; a second range makes it and one again gives it back" {
+ const gpa = std.testing.allocator;
+ const p = try Pardes.init(gpa, .{ .tty_only = true, .cols = 60, .rows = 12 });
+ defer p.deinit();
+ const pane = try p.setTestFile("alpha beta\nalpha beta\n");
+ for ("wxe%;gg") |cp| handleNormal(p, &pane.body, .{ .cp = cp });
+ try std.testing.expectEqual(@as(usize, 0), pane.body.sels.len);
+ handleNormal(p, &pane.body, .{ .cp = 'C' });
+ try std.testing.expectEqual(@as(u16, 1), pane.body.nsel);
+ try std.testing.expect(pane.body.sels.len > 0);
+ handleNormal(p, &pane.body, .{ .cp = 'l' }); // replayed at both, still two
+ try std.testing.expectEqual(@as(u16, 1), pane.body.nsel);
+ handleNormal(p, &pane.body, .{ .cp = ',' });
+ try std.testing.expectEqual(@as(usize, 0), pane.body.sels.len);
+ handleNormal(p, &pane.body, .{ .cp = 'C' });
+ handleNormal(p, &pane.body, .{ .cp = ';' });
+ handleNormal(p, &pane.body, .{ .cp = 'x' }); // x at both: still two lines
+ handleNormal(p, &pane.body, .{ .cp = '_', .alt = true }); // merged to one
+ try std.testing.expectEqual(@as(u16, 0), pane.body.nsel);
+ try std.testing.expectEqual(@as(usize, 0), pane.body.sels.len);
+}
diff --git a/src/pardes.zig b/src/pardes.zig
index 6b397017..187f3ed3 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -4146,6 +4146,8 @@ pub const Pardes = struct {
.pdf_gpa = pdf_gpa,
.tree_sitter_gpa = tree_sitter_gpa,
.opts = opts,
+ .col_tags = @splat(.{ .what = .column, .gpa = gpa }),
+ .global_tag = .{ .what = .workspace, .gpa = gpa },
.fs = .{},
.screen_w = opts.cols,
.screen_h = opts.rows,
@@ -5335,10 +5337,14 @@ pub const Pardes = struct {
if (t.what == .tag and t.mode == .insert and tagline.typedIntoName(p, pane, key)) return;
// A tag's prefix is read-only: a key whose edit reached into it
// changes nothing, not even where the cursor is.
- const kept = .{ t.cur_row, t.cur_col, t.vsel, t.msel, t.sels, t.nsel };
+ const kept = .{ t.cur_row, t.cur_col, t.vsel, t.msel };
+ var kept_sels: [Text.max_selections - 1]Text.SelRange = undefined;
+ const kept_n = t.nsel;
+ @memcpy(kept_sels[0..kept_n], t.sels[0..kept_n]);
t.refused = false;
defer if (t.refused) {
- t.cur_row, t.cur_col, t.vsel, t.msel, t.sels, t.nsel = kept;
+ t.cur_row, t.cur_col, t.vsel, t.msel = kept;
+ t.restoreSels(kept_sels[0..kept_n]);
};
const was = t.mode;
switch (t.mode) {