termio: wip but it builds

This commit is contained in:
Mitchell Hashimoto
2024-07-13 10:35:46 -07:00
parent 49c92fd0e6
commit c4484938c5
5 changed files with 6043 additions and 20 deletions

View File

@ -3,7 +3,7 @@
//! with the terminal. //! with the terminal.
pub usingnamespace @import("termio/message.zig"); pub usingnamespace @import("termio/message.zig");
pub const Exec = @import("termio/Exec.zig"); pub const reader = @import("termio/reader.zig");
pub const Options = @import("termio/Options.zig"); pub const Options = @import("termio/Options.zig");
pub const Termio = @import("termio/Termio.zig"); pub const Termio = @import("termio/Termio.zig");
pub const Thread = @import("termio/Thread.zig"); pub const Thread = @import("termio/Thread.zig");

2965
src/termio/Old.zig Normal file

File diff suppressed because it is too large Load Diff

2987
src/termio/Termio.zig Normal file

File diff suppressed because it is too large Load Diff

View File

@ -219,16 +219,21 @@ pub fn threadMain(self: *Thread) void {
fn threadMain_(self: *Thread) !void { fn threadMain_(self: *Thread) !void {
defer log.debug("IO thread exited", .{}); defer log.debug("IO thread exited", .{});
// Start the async handlers. We start these first so that they're // This is the data sent to xev callbacks. We want a pointer to both
// registered even if anything below fails so we can drain the mailbox. // ourselves and the thread data so we can thread that through (pun intended).
self.wakeup.wait(&self.loop, &self.wakeup_c, Thread, self, wakeupCallback); var cb: CallbackData = .{ .self = self };
self.stop.wait(&self.loop, &self.stop_c, Thread, self, stopCallback);
// Run our thread start/end callbacks. This allows the implementation // Run our thread start/end callbacks. This allows the implementation
// to hook into the event loop as needed. // to hook into the event loop as needed. The thread data is created
var data = try self.termio.threadEnter(self); // on the stack here so that it has a stable pointer throughout the
defer data.deinit(); // lifetime of the thread.
defer self.termio.threadExit(data); try self.termio.threadEnter(self, &cb.data);
defer cb.data.deinit();
defer self.termio.threadExit(&cb.data);
// Start the async handlers.
self.wakeup.wait(&self.loop, &self.wakeup_c, CallbackData, &cb, wakeupCallback);
self.stop.wait(&self.loop, &self.stop_c, CallbackData, &cb, stopCallback);
// Run // Run
log.debug("starting IO thread", .{}); log.debug("starting IO thread", .{});
@ -236,8 +241,14 @@ fn threadMain_(self: *Thread) !void {
try self.loop.run(.until_done); try self.loop.run(.until_done);
} }
/// This is the data passed to xev callbacks on the thread.
const CallbackData = struct {
self: *Thread,
data: termio.Termio.ThreadData = undefined,
};
/// Drain the mailbox, handling all the messages in our terminal implementation. /// Drain the mailbox, handling all the messages in our terminal implementation.
fn drainMailbox(self: *Thread) !void { fn drainMailbox(self: *Thread, data: *termio.Termio.ThreadData) !void {
// If we're draining, we just drain the mailbox and return. // If we're draining, we just drain the mailbox and return.
if (self.flags.drain) { if (self.flags.drain) {
while (self.mailbox.pop()) |_| {} while (self.mailbox.pop()) |_| {}
@ -256,21 +267,33 @@ fn drainMailbox(self: *Thread) !void {
switch (message) { switch (message) {
.change_config => |config| { .change_config => |config| {
defer config.alloc.destroy(config.ptr); defer config.alloc.destroy(config.ptr);
try self.termio.changeConfig(config.ptr); try self.termio.changeConfig(data, config.ptr);
}, },
.inspector => |v| self.flags.has_inspector = v, .inspector => |v| self.flags.has_inspector = v,
.resize => |v| self.handleResize(v), .resize => |v| self.handleResize(v),
.clear_screen => |v| try self.termio.clearScreen(v.history), .clear_screen => |v| try self.termio.clearScreen(data, v.history),
.scroll_viewport => |v| try self.termio.scrollViewport(v), .scroll_viewport => |v| try self.termio.scrollViewport(v),
.jump_to_prompt => |v| try self.termio.jumpToPrompt(v), .jump_to_prompt => |v| try self.termio.jumpToPrompt(v),
.start_synchronized_output => self.startSynchronizedOutput(), .start_synchronized_output => self.startSynchronizedOutput(),
.linefeed_mode => |v| self.flags.linefeed_mode = v, .linefeed_mode => |v| self.flags.linefeed_mode = v,
.child_exited_abnormally => |v| try self.termio.childExitedAbnormally(v.exit_code, v.runtime_ms), .child_exited_abnormally => |v| try self.termio.childExitedAbnormally(v.exit_code, v.runtime_ms),
.write_small => |v| try self.termio.queueWrite(v.data[0..v.len], self.flags.linefeed_mode), .write_small => |v| try self.termio.queueWrite(
.write_stable => |v| try self.termio.queueWrite(v, self.flags.linefeed_mode), data,
v.data[0..v.len],
self.flags.linefeed_mode,
),
.write_stable => |v| try self.termio.queueWrite(
data,
v,
self.flags.linefeed_mode,
),
.write_alloc => |v| { .write_alloc => |v| {
defer v.alloc.free(v.data); defer v.alloc.free(v.data);
try self.termio.queueWrite(v.data, self.flags.linefeed_mode); try self.termio.queueWrite(
data,
v.data,
self.flags.linefeed_mode,
);
}, },
} }
} }
@ -359,7 +382,7 @@ fn coalesceCallback(
} }
fn wakeupCallback( fn wakeupCallback(
self_: ?*Thread, cb_: ?*CallbackData,
_: *xev.Loop, _: *xev.Loop,
_: *xev.Completion, _: *xev.Completion,
r: xev.Async.WaitError!void, r: xev.Async.WaitError!void,
@ -369,23 +392,23 @@ fn wakeupCallback(
return .rearm; return .rearm;
}; };
const t = self_.?; const cb = cb_ orelse return .rearm;
// When we wake up, we check the mailbox. Mailbox producers should // When we wake up, we check the mailbox. Mailbox producers should
// wake up our thread after publishing. // wake up our thread after publishing.
t.drainMailbox() catch |err| cb.self.drainMailbox(&cb.data) catch |err|
log.err("error draining mailbox err={}", .{err}); log.err("error draining mailbox err={}", .{err});
return .rearm; return .rearm;
} }
fn stopCallback( fn stopCallback(
self_: ?*Thread, cb_: ?*CallbackData,
_: *xev.Loop, _: *xev.Loop,
_: *xev.Completion, _: *xev.Completion,
r: xev.Async.WaitError!void, r: xev.Async.WaitError!void,
) xev.CallbackAction { ) xev.CallbackAction {
_ = r catch unreachable; _ = r catch unreachable;
self_.?.loop.stop(); cb_.?.self.loop.stop();
return .disarm; return .disarm;
} }

48
src/termio/reader.zig Normal file
View File

@ -0,0 +1,48 @@
const std = @import("std");
const configpkg = @import("../config.zig");
const Command = @import("../Command.zig");
/// The kinds of readers.
pub const Kind = std.meta.Tag(Config);
/// Configuration for the various reader types.
pub const Config = union(enum) {
/// Manual means that the termio caller will handle reading input
/// and passing it to the termio implementation. Note that even if you
/// select a different reader, you can always still manually provide input;
/// this config just makes it so that it is ONLY manual input.
manual: void,
/// Exec uses posix exec to run a command with a pty.
exec: Exec,
pub const Exec = struct {
command: ?[]const u8 = null,
shell_integration: configpkg.Config.ShellIntegration = .detect,
shell_integration_features: configpkg.Config.ShellIntegrationFeatures = .{},
working_directory: ?[]const u8 = null,
linux_cgroup: Command.LinuxCgroup = Command.linux_cgroup_default,
};
};
/// Termio thread data. See termio.ThreadData for docs.
pub const ThreadData = union(Kind) {
manual: void,
exec: struct {
/// Process start time and boolean of whether its already exited.
start: std.time.Instant,
exited: bool = false,
/// The number of milliseconds below which we consider a process
/// exit to be abnormal. This is used to show an error message
/// when the process exits too quickly.
abnormal_runtime_threshold_ms: u32,
/// If true, do not immediately send a child exited message to the
/// surface to close the surface when the command exits. If this is
/// false we'll show a process exited message and wait for user input
/// to close the surface.
wait_after_command: bool,
},
};