summaryrefslogtreecommitdiff
path: root/src/Registers.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/Registers.zig')
-rw-r--r--src/Registers.zig106
1 files changed, 106 insertions, 0 deletions
diff --git a/src/Registers.zig b/src/Registers.zig
new file mode 100644
index 00000000..d2a42434
--- /dev/null
+++ b/src/Registers.zig
@@ -0,0 +1,106 @@
+//! helix's registers: named stores of text, each holding one VALUE PER RANGE
+//! of the selection that wrote it. `"` is the default, `+` the one the
+//! system clipboard is copied from, `/` the last s/S pattern, `@` the last
+//! macro; any other character names a register of its own. The registers
+//! computed from the editor rather than stored (`_`, `#`, `.`, `%`) and the
+//! clipboard's paste side are read in edit.zig, which has the pane at hand.
+const std = @import("std");
+
+const Registers = @This();
+
+list: std.ArrayList(Reg) = .empty,
+
+pub const Reg = struct {
+ name: u21,
+ /// gpa-owned, one per range, in document order
+ values: [][]u8,
+ /// the values joined by newlines, for the readers that take one text
+ /// (the acme chords, a paste into a terminal, the clipboard); gpa-owned,
+ /// made on first read
+ joined: ?[]u8 = null,
+};
+
+pub const default = '"';
+
+pub fn deinit(r: *Registers, gpa: std.mem.Allocator) void {
+ for (r.list.items) |*reg| freeReg(gpa, reg);
+ r.list.deinit(gpa);
+ r.* = .{};
+}
+
+fn freeReg(gpa: std.mem.Allocator, reg: *Reg) void {
+ for (reg.values) |v| gpa.free(v);
+ gpa.free(reg.values);
+ if (reg.joined) |j| gpa.free(j);
+}
+
+pub fn get(r: *Registers, name: u21) ?*Reg {
+ for (r.list.items) |*reg| if (reg.name == name) return reg;
+ return null;
+}
+
+/// Value `i` of register `name`: a register holding fewer values than there
+/// are ranges repeats its last (helix paste_impl).
+pub fn value(r: *Registers, name: u21, i: usize) ?[]const u8 {
+ const reg = r.get(name) orelse return null;
+ if (reg.values.len == 0) return null;
+ return reg.values[@min(i, reg.values.len - 1)];
+}
+
+/// Store value `i` of `n`. `fresh` starts the register over with `n` empty
+/// values; the replay across ranges passes it on its first pass only, so
+/// every range fills its own slot.
+pub fn put(r: *Registers, gpa: std.mem.Allocator, name: u21, bytes: []const u8, i: usize, n: usize, fresh: bool) void {
+ const copy = gpa.dupe(u8, bytes) catch return;
+ const reg = r.get(name) orelse blk: {
+ r.list.append(gpa, .{ .name = name, .values = &.{} }) catch {
+ gpa.free(copy);
+ return;
+ };
+ break :blk &r.list.items[r.list.items.len - 1];
+ };
+ if (fresh or reg.values.len != n) {
+ const values = gpa.alloc([]u8, n) catch {
+ gpa.free(copy);
+ return;
+ };
+ for (values) |*v| v.* = &.{};
+ freeReg(gpa, reg);
+ reg.* = .{ .name = name, .values = values };
+ }
+ gpa.free(reg.values[i]);
+ reg.values[i] = copy;
+ if (reg.joined) |j| gpa.free(j);
+ reg.joined = null;
+}
+
+/// The whole register as one text: its values joined by newlines, no
+/// newline added after a value that already ends in one.
+pub fn text(r: *Registers, gpa: std.mem.Allocator, name: u21) ?[]const u8 {
+ const reg = r.get(name) orelse return null;
+ if (reg.joined) |j| return j;
+ var out: std.ArrayList(u8) = .empty;
+ for (reg.values, 0..) |v, i| {
+ if (i > 0 and !std.mem.endsWith(u8, reg.values[i - 1], "\n")) out.append(gpa, '\n') catch return null;
+ out.appendSlice(gpa, v) catch return null;
+ }
+ reg.joined = out.toOwnedSlice(gpa) catch return null;
+ return reg.joined;
+}
+
+test "a register holds a value per range and joins them for one-text readers" {
+ const gpa = std.testing.allocator;
+ var r: Registers = .{};
+ defer r.deinit(gpa);
+ try std.testing.expect(r.value(default, 0) == null);
+ // a replay fills the last range first
+ r.put(gpa, default, "two", 1, 2, true);
+ r.put(gpa, default, "one\n", 0, 2, false);
+ try std.testing.expectEqualStrings("one\n", r.value(default, 0).?);
+ try std.testing.expectEqualStrings("two", r.value(default, 1).?);
+ try std.testing.expectEqualStrings("two", r.value(default, 5).?);
+ try std.testing.expectEqualStrings("one\ntwo", r.text(gpa, default).?);
+ r.put(gpa, 'a', "x", 0, 1, true);
+ try std.testing.expectEqualStrings("x", r.text(gpa, 'a').?);
+ try std.testing.expectEqualStrings("one\ntwo", r.text(gpa, default).?);
+}