Skip to content

Commit bb27a91

Browse files
committed
Add basic --preview support
The preview is fetched in the same thread as the main process so it is not yet optimal (if the command blocks this could be bad). But it is a good start, and vaxis makes this so easy!
1 parent acd70a2 commit bb27a91

File tree

3 files changed

+59
-65
lines changed

3 files changed

+59
-65
lines changed

src/Previewer.zig

Lines changed: 5 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
const mem = std.mem;
44
const os = std.os;
5-
const posix = std.posix;
65
const process = std.process;
76
const std = @import("std");
87

@@ -23,7 +22,7 @@ child: ?Child = null,
2322
stdout: ArrayList(u8),
2423
stderr: ArrayList(u8),
2524

26-
pub fn init(allocator: Allocator, cmd: []const u8, arg: []const u8) !Previewer {
25+
pub fn init(allocator: Allocator, cmd: []const u8) Previewer {
2726
const shell = process.getEnvVarOwned(allocator, "SHELL") catch "/bin/sh";
2827

2928
var iter = std.mem.tokenizeSequence(u8, cmd, "{}");
@@ -32,24 +31,20 @@ pub fn init(allocator: Allocator, cmd: []const u8, arg: []const u8) !Previewer {
3231
iter.next() orelse "",
3332
};
3433

35-
var previewer = Previewer{
34+
return .{
3635
.allocator = allocator,
3736
.shell = shell,
3837
.cmd_parts = cmd_parts,
3938
.stdout = ArrayList(u8).init(allocator),
4039
.stderr = ArrayList(u8).init(allocator),
4140
};
42-
try previewer.spawn(arg);
43-
44-
return previewer;
4541
}
4642

4743
pub fn reset(previewer: *Previewer) !void {
4844
previewer.stdout.clearRetainingCapacity();
4945
previewer.stderr.clearRetainingCapacity();
5046
if (previewer.child) |*child| {
5147
_ = try child.kill();
52-
previewer.loop.clearChild();
5348
}
5449
}
5550

@@ -61,25 +56,14 @@ pub fn spawn(previewer: *Previewer, arg: []const u8) !void {
6156

6257
try previewer.reset();
6358

64-
const command = try std.fmt.allocPrint(previewer.allocator, "{s}{s}{s} | expand -t4", .{ previewer.cmd_parts[0], arg, previewer.cmd_parts[1] });
59+
const command = try std.fmt.allocPrint(previewer.allocator, "{s}{s}{s}", .{ previewer.cmd_parts[0], arg, previewer.cmd_parts[1] });
6560

6661
var child = Child.init(&.{ previewer.shell, "-c", command }, previewer.allocator);
6762
child.stdin_behavior = .Close;
6863
child.stdout_behavior = .Pipe;
6964
child.stderr_behavior = .Pipe;
7065
try child.spawn();
7166

72-
_ = try posix.fcntl(child.stdout.?.handle, posix.F.SETFL, @as(
73-
u32,
74-
@bitCast(posix.O{ .NONBLOCK = true }),
75-
));
76-
_ = try posix.fcntl(
77-
child.stderr.?.handle,
78-
posix.F.SETFL,
79-
@as(u32, @bitCast(posix.O{ .NONBLOCK = true })),
80-
);
81-
82-
previewer.loop.setChild(child.stdout.?.handle, child.stderr.?.handle);
8367
previewer.child = child;
8468
previewer.current_arg = try previewer.allocator.dupe(u8, arg);
8569
}
@@ -98,7 +82,6 @@ pub fn read(previewer: *Previewer, stream: enum { stdout, stderr }) !bool {
9882
if (len == 0) {
9983
_ = try child.kill();
10084
previewer.child = null;
101-
previewer.loop.clearChild();
10285
return false;
10386
}
10487

@@ -116,13 +99,14 @@ pub fn read(previewer: *Previewer, stream: enum { stdout, stderr }) !bool {
11699
if (buf_len > 4096 * 10) {
117100
_ = try child.kill();
118101
previewer.child = null;
119-
previewer.loop.clearChild();
120102
}
121103
}
122104
return true;
123105
}
124106

125107
pub fn lines(previewer: *Previewer) std.mem.SplitIterator(u8, .scalar) {
108+
_ = previewer.read(.stdout) catch {};
109+
126110
if (previewer.stderr.items.len > 0) {
127111
return std.mem.splitScalar(u8, previewer.stderr.items, '\n');
128112
}

src/opts.zig

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -158,23 +158,23 @@ pub fn parse(allocator: Allocator, args: []const []const u8, stderr: File.Writer
158158
}
159159

160160
// preview
161-
// else if (mem.eql(u8, opt, "preview")) {
162-
// const command = iter.getArg() orelse missingArg(stderr, iter, opt);
163-
// config.preview = allocator.dupe(u8, command) catch unreachable;
164-
// }
161+
else if (mem.eql(u8, opt, "preview")) {
162+
const command = iter.getArg() orelse missingArg(stderr, iter, opt);
163+
config.preview = allocator.dupe(u8, command) catch unreachable;
164+
}
165165

166166
// preview-width
167-
// else if (mem.eql(u8, opt, "preview-width")) {
168-
// const width_str = blk: {
169-
// const arg = iter.getArg() orelse missingArg(stderr, iter, opt);
170-
// if (mem.endsWith(u8, arg, "%")) break :blk arg[0 .. arg.len - 1];
171-
// break :blk arg;
172-
// };
173-
// const preview_width = fmt.parseUnsigned(usize, width_str, 10) catch argError(stderr, "preview-width must be an integer");
174-
// if (preview_width < 20 or preview_width > 80) argError(stderr, "preview-width must be between 20% and 80%");
175-
176-
// config.preview_width = @as(f64, @floatFromInt(preview_width)) / 100.0;
177-
// }
167+
else if (mem.eql(u8, opt, "preview-width")) {
168+
const width_str = blk: {
169+
const arg = iter.getArg() orelse missingArg(stderr, iter, opt);
170+
if (mem.endsWith(u8, arg, "%")) break :blk arg[0 .. arg.len - 1];
171+
break :blk arg;
172+
};
173+
const preview_width = fmt.parseUnsigned(usize, width_str, 10) catch argError(stderr, "preview-width must be an integer");
174+
if (preview_width < 20 or preview_width > 80) argError(stderr, "preview-width must be between 20% and 80%");
175+
176+
config.preview_width = @as(f64, @floatFromInt(preview_width)) / 100.0;
177+
}
178178

179179
// invalid option
180180
else {

src/ui.zig

Lines changed: 39 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,10 @@ pub const State = struct {
128128
pub fn init(allocator: Allocator, config: Config) !State {
129129
const vx = try vaxis.init(allocator, .{});
130130

131+
const preview = if (config.preview) |cmd| blk: {
132+
break :blk Previewer.init(allocator, cmd);
133+
} else null;
134+
131135
return .{
132136
.allocator = allocator,
133137
.config = config,
@@ -139,6 +143,8 @@ pub const State = struct {
139143
.selected_rows = ArrayToggleSet(usize).init(allocator),
140144
.offset = 0,
141145
.query = EditBuffer.init(allocator),
146+
147+
.preview = preview,
142148
};
143149
}
144150

@@ -198,12 +204,12 @@ pub const State = struct {
198204
}
199205

200206
// The selection changed and the child process should be respawned
201-
// if (state.selection_changed) if (state.preview) |*preview| {
202-
// state.selection_changed = false;
203-
// if (filtered.len > 0) {
204-
// try preview.spawn(filtered[state.selected + state.offset].str);
205-
// } else try preview.reset();
206-
// };
207+
if (state.selection_changed or true) if (state.preview) |*preview| {
208+
state.selection_changed = false;
209+
if (filtered.len > 0) {
210+
try preview.spawn(filtered[state.selected + state.offset].str);
211+
} else try preview.reset();
212+
};
207213

208214
try state.draw(tokens, filtered, candidates.len);
209215
try state.vx.render(state.tty.anyWriter());
@@ -300,23 +306,22 @@ pub const State = struct {
300306
const win = state.vx.window();
301307
win.clear();
302308

303-
const child = win.child(.{ .height = .{ .limit = state.config.height } });
304-
305309
const width = state.vx.screen.width;
306310
const preview_width: usize = if (state.preview) |_|
307311
@intFromFloat(@as(f64, @floatFromInt(width)) * state.preview_width)
308312
else
309313
0;
310-
const items_width = width - preview_width - @as(usize, if (state.preview) |_| 2 else 0);
311-
_ = items_width;
314+
315+
const items_width = width - preview_width;
316+
const items = win.child(.{ .height = .{ .limit = state.config.height }, .width = .{ .limit = items_width } });
312317

313318
const height = @min(state.vx.screen.height, state.config.height);
314319

315320
// draw the candidates
316321
var line: usize = 0;
317322
while (line < height - 1) : (line += 1) {
318323
if (line < candidates.len) state.drawCandidate(
319-
child,
324+
items,
320325
line + 1,
321326
candidates[line + state.offset],
322327
tokens,
@@ -332,40 +337,45 @@ pub const State = struct {
332337
if (num_selected > 0) {
333338
const stats = try std.fmt.bufPrint(&buf, "{}/{} [{}]", .{ candidates.len, total_candidates, num_selected });
334339
const stats_width = numDigits(candidates.len) + numDigits(total_candidates) + numDigits(num_selected) + 4;
335-
_ = try child.printSegment(.{ .text = stats }, .{ .col_offset = width - stats_width, .row_offset = 0 });
340+
_ = try items.printSegment(.{ .text = stats }, .{ .col_offset = items_width - stats_width, .row_offset = 0 });
336341
} else {
337342
const stats = try std.fmt.bufPrint(&buf, "{}/{}", .{ candidates.len, total_candidates });
338343
const stats_width = numDigits(candidates.len) + numDigits(total_candidates) + 1;
339-
_ = try child.printSegment(.{ .text = stats }, .{ .col_offset = width - stats_width, .row_offset = 0 });
344+
_ = try items.printSegment(.{ .text = stats }, .{ .col_offset = items_width - stats_width, .row_offset = 0 });
340345
}
341346
}
342347

343348
// draw the prompt
344349
// TODO: handle display of queries longer than the screen width
345350
// const query_width = state.query.slice().len;
346-
_ = try child.print(&.{
351+
_ = try items.print(&.{
347352
.{ .text = state.config.prompt },
348353
.{ .text = state.query.slice() },
349354
}, .{ .col_offset = 0, .row_offset = 0 });
350355

351-
// // draw a preview window if requested
352-
// if (state.preview) |*preview| {
353-
// var lines = preview.lines();
354-
355-
// for (0..height) |_| {
356-
// terminal.cursorCol(items_width + 2);
357-
// terminal.write("│ ");
356+
// draw a preview window if requested
357+
if (state.preview) |*preview| {
358+
const preview_win = win.child(.{
359+
.x_off = items_width,
360+
.y_off = 0,
361+
.height = .{ .limit = state.config.height },
362+
.width = .{ .limit = preview_width },
363+
.border = .{ .where = .left },
364+
});
358365

359-
// if (lines.next()) |preview_line| {
360-
// terminal.write(preview_line[0..@min(preview_line.len, preview_width - 1)]);
361-
// }
366+
var lines = preview.lines();
362367

363-
// terminal.cursorDown(1);
364-
// }
365-
// terminal.sgr(.reset);
366-
// } else terminal.cursorDown(height);
368+
for (0..height) |l| {
369+
if (lines.next()) |preview_line| {
370+
_ = try preview_win.printSegment(
371+
.{ .text = preview_line },
372+
.{ .row_offset = l, .wrap = .none },
373+
);
374+
}
375+
}
376+
}
367377

368-
child.showCursor(state.config.prompt.len + state.query.cursor, 0);
378+
items.showCursor(state.config.prompt.len + state.query.cursor, 0);
369379
}
370380

371381
fn drawCandidate(

0 commit comments

Comments
 (0)