feat(tasks): stream task output to buffer in the background

This commit is contained in:
CJ van den Berg 2025-01-26 20:17:58 +01:00
parent 649d369a56
commit a28f1db4c7
Signed by: neurocyte
GPG key ID: 8EB1E1BB660E3FB9
2 changed files with 59 additions and 21 deletions

View file

@ -13,10 +13,11 @@ pub const Writer = std.io.Writer(*Self, Error, write);
pub const BufferedWriter = std.io.BufferedWriter(max_chunk_size, Writer); pub const BufferedWriter = std.io.BufferedWriter(max_chunk_size, Writer);
pub const Error = error{ InvalidShellArg0, OutOfMemory, Exit, ThespianSpawnFailed, Closed }; pub const Error = error{ InvalidShellArg0, OutOfMemory, Exit, ThespianSpawnFailed, Closed };
pub const OutputHandler = fn (parent: tp.pid_ref, arg0: []const u8, output: []const u8) void; pub const OutputHandler = fn (context: usize, parent: tp.pid_ref, arg0: []const u8, output: []const u8) void;
pub const ExitHandler = fn (parent: tp.pid_ref, arg0: []const u8, err_msg: []const u8, exit_code: i64) void; pub const ExitHandler = fn (context: usize, parent: tp.pid_ref, arg0: []const u8, err_msg: []const u8, exit_code: i64) void;
pub const Handlers = struct { pub const Handlers = struct {
context: usize = 0,
out: *const OutputHandler, out: *const OutputHandler,
err: ?*const OutputHandler = null, err: ?*const OutputHandler = null,
exit: *const ExitHandler = log_exit_handler, exit: *const ExitHandler = log_exit_handler,
@ -74,7 +75,8 @@ pub fn bufferedWriter(self: *Self) BufferedWriter {
return .{ .unbuffered_writer = self.writer() }; return .{ .unbuffered_writer = self.writer() };
} }
pub fn log_handler(parent: tp.pid_ref, arg0: []const u8, output: []const u8) void { pub fn log_handler(context: usize, parent: tp.pid_ref, arg0: []const u8, output: []const u8) void {
_ = context;
_ = parent; _ = parent;
_ = arg0; _ = arg0;
const logger = log.logger(@typeName(Self)); const logger = log.logger(@typeName(Self));
@ -82,14 +84,16 @@ pub fn log_handler(parent: tp.pid_ref, arg0: []const u8, output: []const u8) voi
while (it.next()) |line| if (line.len > 0) logger.print("{s}", .{line}); while (it.next()) |line| if (line.len > 0) logger.print("{s}", .{line});
} }
pub fn log_err_handler(parent: tp.pid_ref, arg0: []const u8, output: []const u8) void { pub fn log_err_handler(context: usize, parent: tp.pid_ref, arg0: []const u8, output: []const u8) void {
_ = context;
_ = parent; _ = parent;
const logger = log.logger(@typeName(Self)); const logger = log.logger(@typeName(Self));
var it = std.mem.splitScalar(u8, output, '\n'); var it = std.mem.splitScalar(u8, output, '\n');
while (it.next()) |line| logger.print_err(arg0, "{s}", .{line}); while (it.next()) |line| logger.print_err(arg0, "{s}", .{line});
} }
pub fn log_exit_handler(parent: tp.pid_ref, arg0: []const u8, err_msg: []const u8, exit_code: i64) void { pub fn log_exit_handler(context: usize, parent: tp.pid_ref, arg0: []const u8, err_msg: []const u8, exit_code: i64) void {
_ = context;
_ = parent; _ = parent;
const logger = log.logger(@typeName(Self)); const logger = log.logger(@typeName(Self));
if (exit_code > 0) { if (exit_code > 0) {
@ -99,7 +103,8 @@ pub fn log_exit_handler(parent: tp.pid_ref, arg0: []const u8, err_msg: []const u
} }
} }
pub fn log_exit_err_handler(parent: tp.pid_ref, arg0: []const u8, err_msg: []const u8, exit_code: i64) void { pub fn log_exit_err_handler(context: usize, parent: tp.pid_ref, arg0: []const u8, err_msg: []const u8, exit_code: i64) void {
_ = context;
_ = parent; _ = parent;
const logger = log.logger(@typeName(Self)); const logger = log.logger(@typeName(Self));
if (exit_code > 0) { if (exit_code > 0) {
@ -178,9 +183,9 @@ const Process = struct {
} else if (try m.match(.{"close"})) { } else if (try m.match(.{"close"})) {
try self.close(); try self.close();
} else if (try m.match(.{ module_name, "stdout", tp.extract(&bytes) })) { } else if (try m.match(.{ module_name, "stdout", tp.extract(&bytes) })) {
self.handlers.out(self.parent.ref(), self.arg0, bytes); self.handlers.out(self.handlers.context, self.parent.ref(), self.arg0, bytes);
} else if (try m.match(.{ module_name, "stderr", tp.extract(&bytes) })) { } else if (try m.match(.{ module_name, "stderr", tp.extract(&bytes) })) {
(self.handlers.err orelse self.handlers.out)(self.parent.ref(), self.arg0, bytes); (self.handlers.err orelse self.handlers.out)(self.handlers.context, self.parent.ref(), self.arg0, bytes);
} else if (try m.match(.{ module_name, "term", tp.more })) { } else if (try m.match(.{ module_name, "term", tp.more })) {
self.handle_terminated(m) catch |e| return tp.exit_error(e, @errorReturnTrace()); self.handle_terminated(m) catch |e| return tp.exit_error(e, @errorReturnTrace());
} else if (try m.match(.{ "exit", "normal" })) { } else if (try m.match(.{ "exit", "normal" })) {
@ -195,11 +200,11 @@ const Process = struct {
var err_msg: []const u8 = undefined; var err_msg: []const u8 = undefined;
var exit_code: i64 = undefined; var exit_code: i64 = undefined;
if (try m.match(.{ tp.any, tp.any, "exited", 0 })) { if (try m.match(.{ tp.any, tp.any, "exited", 0 })) {
self.handlers.exit(self.parent.ref(), self.arg0, "exited", 0); self.handlers.exit(self.handlers.context, self.parent.ref(), self.arg0, "exited", 0);
} else if (try m.match(.{ tp.any, tp.any, "error.FileNotFound", 1 })) { } else if (try m.match(.{ tp.any, tp.any, "error.FileNotFound", 1 })) {
self.logger.print_err(self.arg0, "'{s}' executable not found", .{self.arg0}); self.logger.print_err(self.arg0, "'{s}' executable not found", .{self.arg0});
} else if (try m.match(.{ tp.any, tp.any, tp.extract(&err_msg), tp.extract(&exit_code) })) { } else if (try m.match(.{ tp.any, tp.any, tp.extract(&err_msg), tp.extract(&exit_code) })) {
self.handlers.exit(self.parent.ref(), self.arg0, err_msg, exit_code); self.handlers.exit(self.handlers.context, self.parent.ref(), self.arg0, err_msg, exit_code);
} }
} }
}; };

View file

@ -14,7 +14,7 @@ const build_options = @import("build_options");
const Plane = @import("renderer").Plane; const Plane = @import("renderer").Plane;
const input = @import("input"); const input = @import("input");
const command = @import("command"); const command = @import("command");
const BufferManager = @import("Buffer").Manager; const Buffer = @import("Buffer");
const tui = @import("tui.zig"); const tui = @import("tui.zig");
const Box = @import("Box.zig"); const Box = @import("Box.zig");
@ -48,7 +48,7 @@ active_view: ?usize = 0,
panels: ?*WidgetList = null, panels: ?*WidgetList = null,
last_match_text: ?[]const u8 = null, last_match_text: ?[]const u8 = null,
location_history: location_history, location_history: location_history,
buffer_manager: BufferManager, buffer_manager: Buffer.Manager,
find_in_files_state: enum { init, adding, done } = .done, find_in_files_state: enum { init, adding, done } = .done,
file_list_type: FileListType = .find_in_files, file_list_type: FileListType = .find_in_files,
panel_height: ?usize = null, panel_height: ?usize = null,
@ -70,7 +70,7 @@ pub fn create(allocator: std.mem.Allocator) !Widget {
.location_history = try location_history.create(), .location_history = try location_history.create(),
.views = undefined, .views = undefined,
.views_widget = undefined, .views_widget = undefined,
.buffer_manager = BufferManager.init(allocator), .buffer_manager = Buffer.Manager.init(allocator),
}; };
try self.commands.init(self); try self.commands.init(self);
const w = Widget.to(self); const w = Widget.to(self);
@ -287,7 +287,7 @@ const cmds = struct {
if (self.file_list_type == .diagnostics and self.is_panel_view_showing(filelist_view)) if (self.file_list_type == .diagnostics and self.is_panel_view_showing(filelist_view))
try self.toggle_panel_view(filelist_view, false); try self.toggle_panel_view(filelist_view, false);
self.buffer_manager.deinit(); self.buffer_manager.deinit();
self.buffer_manager = BufferManager.init(self.allocator); self.buffer_manager = Buffer.Manager.init(self.allocator);
try project_manager.open(project_dir); try project_manager.open(project_dir);
const project = tp.env.get().str("project"); const project = tp.env.get().str("project");
tui.rdr().set_terminal_working_directory(project); tui.rdr().set_terminal_working_directory(project);
@ -685,7 +685,7 @@ const cmds = struct {
return error.InvalidShellArgument; return error.InvalidShellArgument;
const cmd = ctx.args; const cmd = ctx.args;
const handlers = struct { const handlers = struct {
fn out(parent: tp.pid_ref, _: []const u8, output: []const u8) void { fn out(_: usize, parent: tp.pid_ref, _: []const u8, output: []const u8) void {
var pos: usize = 0; var pos: usize = 0;
var nl_count: usize = 0; var nl_count: usize = 0;
while (std.mem.indexOfScalarPos(u8, output, pos, '\n')) |next| { while (std.mem.indexOfScalarPos(u8, output, pos, '\n')) |next| {
@ -711,10 +711,10 @@ const cmds = struct {
return error.InvalidShellArgument; return error.InvalidShellArgument;
const cmd = ctx.args; const cmd = ctx.args;
const handlers = struct { const handlers = struct {
fn out(parent: tp.pid_ref, _: []const u8, output: []const u8) void { fn out(buffer_ref: usize, parent: tp.pid_ref, _: []const u8, output: []const u8) void {
parent.send(.{ "cmd", "insert_chars", .{output} }) catch {}; parent.send(.{ "cmd", "shell_execute_stream_output", .{ buffer_ref, output } }) catch {};
} }
fn exit(parent: tp.pid_ref, arg0: []const u8, err_msg: []const u8, exit_code: i64) void { fn exit(buffer_ref: usize, parent: tp.pid_ref, arg0: []const u8, err_msg: []const u8, exit_code: i64) void {
var buf: [256]u8 = undefined; var buf: [256]u8 = undefined;
var stream = std.io.fixedBufferStream(&buf); var stream = std.io.fixedBufferStream(&buf);
const writer = stream.writer(); const writer = stream.writer();
@ -723,14 +723,47 @@ const cmds = struct {
} else { } else {
writer.print("\n'{s}' exited\n", .{arg0}) catch {}; writer.print("\n'{s}' exited\n", .{arg0}) catch {};
} }
parent.send(.{ "cmd", "move_buffer_end", .{} }) catch {}; parent.send(.{ "cmd", "shell_execute_stream_output", .{ buffer_ref, stream.getWritten() } }) catch {};
parent.send(.{ "cmd", "insert_chars", .{stream.getWritten()} }) catch {}; parent.send(.{ "cmd", "shell_execute_stream_output_complete", .{buffer_ref} }) catch {};
} }
}; };
try shell.execute(self.allocator, cmd, .{ .out = handlers.out, .err = handlers.out, .exit = handlers.exit }); const editor = self.get_active_editor() orelse return error.Stop;
const buffer = editor.buffer orelse return error.Stop;
const buffer_ref = self.buffer_manager.buffer_to_ref(buffer);
try shell.execute(self.allocator, cmd, .{ .context = buffer_ref, .out = handlers.out, .err = handlers.out, .exit = handlers.exit });
} }
pub const shell_execute_stream_meta = .{ .arguments = &.{.string} }; pub const shell_execute_stream_meta = .{ .arguments = &.{.string} };
pub fn shell_execute_stream_output(self: *Self, ctx: Ctx) Result {
var buffer_ref: usize = 0;
var output: []const u8 = undefined;
if (!try ctx.args.match(.{ tp.extract(&buffer_ref), tp.extract(&output) }))
return error.InvalidShellOutputArgument;
const buffer = self.buffer_manager.buffer_from_ref(buffer_ref) orelse return;
if (self.get_active_editor()) |editor| if (editor.buffer) |eb| if (eb == buffer) {
editor.move_buffer_end(.{}) catch {};
editor.insert_chars(command.fmt(.{output})) catch {};
return;
};
var cursor: Buffer.Cursor = .{};
const metrics = self.plane.metrics(1);
cursor.move_buffer_end(buffer.root, metrics);
var root_ = buffer.root;
_, _, root_ = try root_.insert_chars(cursor.row, cursor.col, output, self.allocator, metrics);
buffer.store_undo(&[_]u8{}) catch {};
buffer.update(root_);
}
pub const shell_execute_stream_output_meta = .{ .arguments = &.{ .integer, .string } };
pub fn shell_execute_stream_output_complete(self: *Self, ctx: Ctx) Result {
var buffer_ref: usize = 0;
if (!try ctx.args.match(.{tp.extract(&buffer_ref)}))
return error.InvalidShellOutputCompleteArgument;
// TODO
_ = self;
}
pub const shell_execute_stream_output_complete_meta = .{ .arguments = &.{ .integer, .string } };
pub fn adjust_fontsize(_: *Self, ctx: Ctx) Result { pub fn adjust_fontsize(_: *Self, ctx: Ctx) Result {
var amount: f32 = undefined; var amount: f32 = undefined;
if (!try ctx.args.match(.{tp.extract(&amount)})) if (!try ctx.args.match(.{tp.extract(&amount)}))