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
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
//! Test helpers shared by the control-tree modules: drive one request
//! through the core and collect the reply and the effects it raised.
const std = @import("std");
const pardes = @import("../pardes.zig");
const tree = @import("tree.zig");
const Pardes = pardes.Pardes;
pub const Req = tree.Req;
pub const Reply = tree.Reply;
pub const E = tree.E;
pub const Answer = struct {
reply: Reply = .{ .tag = 0, .status = .err, .errno = E.IO },
bytes: []const u8 = "",
saved: bool = false,
watch: ?bool = null,
pty_buf: [256]u8 = undefined,
pty_len: usize = 0,
winsize: ?struct { cols: u16, rows: u16 } = null,
signal: ?pardes.PtySignal = null,
spawned: bool = false,
pub fn pty(a: *const Answer) []const u8 {
return a.pty_buf[0..a.pty_len];
}
pub fn errno(a: Answer) u16 {
return if (a.reply.status == .err) a.reply.errno else 0;
}
};
pub fn call(p: *Pardes, req: Req) Answer {
p.update(.{ .fs_req = req });
var ans: Answer = .{};
while (p.nextEffect()) |e| switch (e) {
.fs_reply => |r| {
ans.reply = r;
ans.bytes = p.fsPayload(r);
},
.save_file, .save_text => ans.saved = true,
.watch => |w| ans.watch = w.on,
.write => |w| {
const b = w.bytes.slice();
const n = @min(b.len, ans.pty_buf.len - ans.pty_len);
@memcpy(ans.pty_buf[ans.pty_len..][0..n], b[0..n]);
ans.pty_len += n;
},
.resize_pty => |r| ans.winsize = .{ .cols = r.cols, .rows = r.rows },
.signal_pty => |s| ans.signal = s.sig,
.spawn => ans.spawned = true,
else => {},
};
return ans;
}
pub fn rd(p: *Pardes, node: u64, off: u64, size: u32) Answer {
return call(p, .{ .tag = 1, .op = .read, .node = node, .off = off, .size = size });
}
pub fn wr(p: *Pardes, node: u64, data: []const u8) Answer {
return call(p, .{ .tag = 2, .op = .write, .node = node, .data = data });
}
pub fn rdir(p: *Pardes, node: u64, skip: u64) Answer {
return call(p, .{ .tag = 4, .op = .readdir, .node = node, .off = skip, .size = 4096 });
}
pub fn look_up(p: *Pardes, dir: u64, name: []const u8) Answer {
return call(p, .{ .tag = 3, .op = .lookup, .node = dir, .data = name });
}
/// Opens /new once, as a client would, and returns the pane it made.
pub fn newPane(p: *Pardes) !u32 {
const new = @intFromEnum(tree.TopFile.new);
const opened = call(p, .{ .tag = 9, .op = .open, .node = new });
if (opened.reply.status != .ok or opened.reply.handle == 0) return error.NoPane;
_ = call(p, .{ .tag = 9, .op = .release, .node = new, .handle = opened.reply.handle });
return opened.reply.handle;
}
pub fn withFile(gpa: std.mem.Allocator, text: []const u8) !*Pardes {
const p = try Pardes.init(gpa, .{ .tty_only = true, .cols = 80, .rows = 24 });
errdefer p.deinit();
while (p.nextEffect()) |_| {}
_ = try p.setTestFile(text);
while (p.nextEffect()) |_| {}
return p;
}
pub fn withTerm(gpa: std.mem.Allocator) !*Pardes {
const p = try Pardes.init(gpa, .{ .tty_only = true, .cols = 80, .rows = 24 });
errdefer p.deinit();
while (p.nextEffect()) |_| {}
std.debug.assert(p.panes[0].?.isTerminal());
return p;
}
pub fn serialOf(p: *Pardes) u32 {
return p.panes[0].?.serial;
}
pub const Dirent = struct { node: u64, dir: bool, name: []const u8 };
pub fn dirents(bytes: []const u8, out: []Dirent) []Dirent {
var n: usize = 0;
var i: usize = 0;
while (i + 10 <= bytes.len and n < out.len) {
const node = std.mem.readInt(u64, bytes[i..][0..8], .little);
const kind = bytes[i + 8];
const len = bytes[i + 9];
i += 10;
if (i + len > bytes.len) break;
out[n] = .{ .node = node, .dir = kind == 1, .name = bytes[i .. i + len] };
i += len;
n += 1;
}
return out[0..n];
}
pub fn nameAt(list: []const Dirent, want: []const u8) ?Dirent {
for (list) |d| if (std.mem.eql(u8, d.name, want)) return d;
return null;
}
pub const FakeTty = struct {
taken: bool,
pub const vtable: pardes.Host.VTable = .{ .tty_taken = answer };
fn answer(ctx: ?*anyopaque, pane: u8) bool {
_ = pane;
const f: *FakeTty = @ptrCast(@alignCast(ctx.?));
return f.taken;
}
};
|