|
|
@ -16,6 +16,162 @@ const c = @cImport({
|
|
16
|
16
|
});
|
|
17
|
17
|
const std = @import("std");
|
|
18
|
18
|
|
|
|
19
|
// TODO: incremental output (e.g. for ag)
|
|
|
20
|
// TODO: instant output (for some commands like `py ...`, `go ...`, qcalc)
|
|
|
21
|
|
|
|
22
|
// commands wishlist:
|
|
|
23
|
// - search (e.g. default current dir + /usr/include)
|
|
|
24
|
// - launch with logs (default launcher, use systemd-run --user --unit=name name?)
|
|
|
25
|
// - view (the above logs)
|
|
|
26
|
// - switch to window
|
|
|
27
|
// - open url
|
|
|
28
|
// - open shortcuts (logs -> ..., tickets)
|
|
|
29
|
// - history (could be another command + some special keybindings)
|
|
|
30
|
|
|
|
31
|
// output line-by-line -> saved by caller?
|
|
|
32
|
// output can be reset
|
|
|
33
|
// incremental output vs. final output/action
|
|
|
34
|
|
|
|
35
|
const ProcessWithOutput = struct {
|
|
|
36
|
process: std.ChildProcess,
|
|
|
37
|
stdout: *std.ArrayList(u8),
|
|
|
38
|
stderr: *std.ArrayList(u8),
|
|
|
39
|
|
|
|
40
|
dead_fds: usize,
|
|
|
41
|
max_output_bytes: usize,
|
|
|
42
|
|
|
|
43
|
fn spawn(allocator: std.Allocator, argv: [][]u8, max_output_bytes: usize) ProcessWithOutput {
|
|
|
44
|
const child = try std.ChildProcess.init(argv, allocator);
|
|
|
45
|
child.stdin_behavior = std.ChildProcess.Ignore;
|
|
|
46
|
child.stdout_behavior = std.ChildProcess.Pipe;
|
|
|
47
|
child.stderr_behavior = std.ChildProcess.Pipe;
|
|
|
48
|
try child.spawn();
|
|
|
49
|
|
|
|
50
|
return ProcessWithOutput{ .process = child, .stdout = std.ArrayList(u8).init(allocator), .stderr = std.ArrayList(u8).init(allocator), .dead_fds = 0, .max_output_bytes = max_output_bytes };
|
|
|
51
|
}
|
|
|
52
|
|
|
|
53
|
fn is_running(self: ProcessWithOutput) bool {
|
|
|
54
|
if (self.process.term) |_| {
|
|
|
55
|
return false;
|
|
|
56
|
} else {
|
|
|
57
|
return true;
|
|
|
58
|
}
|
|
|
59
|
}
|
|
|
60
|
|
|
|
61
|
fn stdout(self: ProcessWithOutput) []u8 {
|
|
|
62
|
return self.stdout.allocatedSlice();
|
|
|
63
|
}
|
|
|
64
|
|
|
|
65
|
fn stderr(self: ProcessWithOutput) []u8 {
|
|
|
66
|
return self.stdout.allocatedSlice();
|
|
|
67
|
}
|
|
|
68
|
|
|
|
69
|
// poll: https://github.com/ziglang/zig/blob/master/lib/std/child_process.zig#L206
|
|
|
70
|
// basically do one iteration with no blocking each time it runs and thus get the output incrementally?
|
|
|
71
|
fn poll(self: ProcessWithOutput) !void {
|
|
|
72
|
if (!self.is_running()) {
|
|
|
73
|
return;
|
|
|
74
|
}
|
|
|
75
|
|
|
|
76
|
var poll_fds = [_]std.os.pollfd{
|
|
|
77
|
.{ .fd = self.process.stdout.?.handle, .events = std.os.POLL.IN, .revents = undefined },
|
|
|
78
|
.{ .fd = self.process.stderr.?.handle, .events = std.os.POLL.IN, .revents = undefined },
|
|
|
79
|
};
|
|
|
80
|
|
|
|
81
|
// We ask for ensureTotalCapacity with this much extra space. This has more of an
|
|
|
82
|
// effect on small reads because once the reads start to get larger the amount
|
|
|
83
|
// of space an ArrayList will allocate grows exponentially.
|
|
|
84
|
const bump_amt = 512;
|
|
|
85
|
|
|
|
86
|
const err_mask = std.os.POLL.ERR | std.os.POLL.NVAL | std.os.POLL.HUP;
|
|
|
87
|
|
|
|
88
|
if (self.dead_fds >= poll_fds.len) {
|
|
|
89
|
return;
|
|
|
90
|
}
|
|
|
91
|
|
|
|
92
|
const events = try std.os.poll(&poll_fds, 0);
|
|
|
93
|
if (events == 0) {
|
|
|
94
|
return;
|
|
|
95
|
}
|
|
|
96
|
|
|
|
97
|
var remove_stdout = false;
|
|
|
98
|
var remove_stderr = false;
|
|
|
99
|
// Try reading whatever is available before checking the error
|
|
|
100
|
// conditions.
|
|
|
101
|
// It's still pstd.ossible to read after a POLL.HUP is received, always
|
|
|
102
|
// check if there's some data waiting to be read first.
|
|
|
103
|
if (poll_fds[0].revents & std.os.POLL.IN != 0) {
|
|
|
104
|
// stdout is ready.
|
|
|
105
|
const new_capacity = std.math.min(self.stdout.items.len + bump_amt, self.max_output_bytes);
|
|
|
106
|
try self.stdout.ensureTotalCapacity(new_capacity);
|
|
|
107
|
const buf = self.stdout.unusedCapacitySlice();
|
|
|
108
|
if (buf.len == 0) return error.StdoutStreamTooLong;
|
|
|
109
|
const nread = try std.os.read(poll_fds[0].fd, buf);
|
|
|
110
|
self.stdout.items.len += nread;
|
|
|
111
|
|
|
|
112
|
// Remove the fd when the EOF condition is met.
|
|
|
113
|
remove_stdout = nread == 0;
|
|
|
114
|
} else {
|
|
|
115
|
remove_stdout = poll_fds[0].revents & err_mask != 0;
|
|
|
116
|
}
|
|
|
117
|
|
|
|
118
|
if (poll_fds[1].revents & std.os.POLL.IN != 0) {
|
|
|
119
|
// stderr is ready.
|
|
|
120
|
const new_capacity = std.math.min(self.stderr.items.len + bump_amt, self.max_output_bytes);
|
|
|
121
|
try self.stderr.ensureTotalCapacity(new_capacity);
|
|
|
122
|
const buf = self.stderr.unusedCapacitySlice();
|
|
|
123
|
if (buf.len == 0) return error.StderrStreamTooLong;
|
|
|
124
|
const nread = try std.os.read(poll_fds[1].fd, buf);
|
|
|
125
|
self.stderr.items.len += nread;
|
|
|
126
|
|
|
|
127
|
// Remove the fd when the EOF condition is met.
|
|
|
128
|
remove_stderr = nread == 0;
|
|
|
129
|
} else {
|
|
|
130
|
remove_stderr = poll_fds[1].revents & err_mask != 0;
|
|
|
131
|
}
|
|
|
132
|
|
|
|
133
|
// Exclude the fds that signaled an error.
|
|
|
134
|
if (remove_stdout) {
|
|
|
135
|
poll_fds[0].fd = -1;
|
|
|
136
|
self.dead_fds += 1;
|
|
|
137
|
}
|
|
|
138
|
if (remove_stderr) {
|
|
|
139
|
poll_fds[1].fd = -1;
|
|
|
140
|
self.dead_fds += 1;
|
|
|
141
|
}
|
|
|
142
|
}
|
|
|
143
|
};
|
|
|
144
|
|
|
|
145
|
const RegexRunner = struct {
|
|
|
146
|
run_always: bool,
|
|
|
147
|
process: ProcessWithOutput,
|
|
|
148
|
|
|
|
149
|
command_to_argv: fn (cmd: []u8) [][]u8,
|
|
|
150
|
|
|
|
151
|
fn run(self: RegexRunner, cmd: []u8, is_confirmed: true) !void {
|
|
|
152
|
if (!self.run_always and !is_confirmed) {
|
|
|
153
|
return;
|
|
|
154
|
}
|
|
|
155
|
|
|
|
156
|
if (self.is_running()) {
|
|
|
157
|
self.process.kill();
|
|
|
158
|
}
|
|
|
159
|
const argv = self.command_to_argv(cmd);
|
|
|
160
|
self.process = ProcessWithOutput(argv);
|
|
|
161
|
}
|
|
|
162
|
|
|
|
163
|
fn output(self: RegexRunner) []u8 {
|
|
|
164
|
if (!self.is_running()) {
|
|
|
165
|
// TODO: return buffer
|
|
|
166
|
return "<done>";
|
|
|
167
|
}
|
|
|
168
|
|
|
|
169
|
// get more input?
|
|
|
170
|
|
|
|
171
|
// TODO: return buffer
|
|
|
172
|
}
|
|
|
173
|
};
|
|
|
174
|
|
|
19
|
175
|
pub fn main() !void {
|
|
20
|
176
|
var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
|
|
21
|
177
|
defer {
|