1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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).?);
}
|