IO thread stream handler is in, lots of commented TODOs

This commit is contained in:
Mitchell Hashimoto
2022-11-03 15:32:29 -07:00
parent 9b3d22e55e
commit d916d56bff
2 changed files with 344 additions and 3 deletions

View File

@ -440,7 +440,7 @@ pub fn Stream(comptime Handler: type) type {
}
fn configureCharset(
self: Self,
self: *Self,
intermediates: []const u8,
set: charsets.Charset,
) !void {

View File

@ -7,6 +7,7 @@ const Allocator = std.mem.Allocator;
const termio = @import("../termio.zig");
const Command = @import("../Command.zig");
const Pty = @import("../Pty.zig");
const SegmentedPool = @import("../segmented_pool.zig").SegmentedPool;
const terminal = @import("../terminal/main.zig");
const libuv = @import("libuv");
const renderer = @import("../renderer.zig");
@ -123,6 +124,7 @@ pub fn threadEnter(self: *Exec, loop: libuv.Loop) !ThreadData {
.terminal_stream = .{
.handler = .{
.terminal = &self.terminal,
.renderer_state = self.renderer_state,
},
},
};
@ -148,13 +150,17 @@ const ThreadData = struct {
ev: *EventData,
pub fn deinit(self: *ThreadData) void {
self.ev.deinit();
self.ev.deinit(self.alloc);
self.alloc.destroy(self.ev);
self.* = undefined;
}
};
const EventData = struct {
// The preallocation size for the write request pool. This should be big
// enough to satisfy most write requests. It must be a power of 2.
const WRITE_REQ_PREALLOC = std.math.pow(usize, 2, 5);
/// This is the arena allocator used for IO read buffers. Since we use
/// libuv under the covers, this lets us rarely heap allocate since we're
/// usually just reusing buffers from this.
@ -170,9 +176,22 @@ const EventData = struct {
/// The data stream is the main IO for the pty.
data_stream: libuv.Tty,
pub fn deinit(self: *EventData) void {
/// This is the pool of available (unused) write requests. If you grab
/// one from the pool, you must put it back when you're done!
write_req_pool: SegmentedPool(libuv.WriteReq.T, WRITE_REQ_PREALLOC) = .{},
/// The pool of available buffers for writing to the pty.
write_buf_pool: SegmentedPool([64]u8, WRITE_REQ_PREALLOC) = .{},
pub fn deinit(self: *EventData, alloc: Allocator) void {
self.read_arena.deinit();
// Clear our write pools. We know we aren't ever going to do
// any more IO since we stop our data stream below so we can just
// drop this.
self.write_req_pool.deinit(alloc);
self.write_buf_pool.deinit(alloc);
// Stop our data stream
self.data_stream.readStop();
self.data_stream.close((struct {
@ -268,6 +287,328 @@ fn ttyRead(t: *libuv.Tty, n: isize, buf: []const u8) void {
}
}
/// This is used as the handler for the terminal.Stream type. This is
/// stateful and is expected to live for the entire lifetime of the terminal.
/// It is NOT VALID to stop a stream handler, create a new one, and use that
/// unless all of the member fields are copied.
const StreamHandler = struct {
terminal: *terminal.Terminal,
renderer_state: *renderer.State,
/// Bracketed paste mode
bracketed_paste: bool = false,
// TODO
fn queueRender(self: *StreamHandler) !void {
_ = self;
}
// TODO
fn queueWrite(self: *StreamHandler, data: []const u8) !void {
_ = self;
_ = data;
}
pub fn print(self: *StreamHandler, c: u21) !void {
try self.terminal.print(c);
}
pub fn bell(self: StreamHandler) !void {
_ = self;
log.info("BELL", .{});
}
pub fn backspace(self: *StreamHandler) !void {
self.terminal.backspace();
}
pub fn horizontalTab(self: *StreamHandler) !void {
try self.terminal.horizontalTab();
}
pub fn linefeed(self: *StreamHandler) !void {
// Small optimization: call index instead of linefeed because they're
// identical and this avoids one layer of function call overhead.
try self.terminal.index();
}
pub fn carriageReturn(self: *StreamHandler) !void {
self.terminal.carriageReturn();
}
pub fn setCursorLeft(self: *StreamHandler, amount: u16) !void {
self.terminal.cursorLeft(amount);
}
pub fn setCursorRight(self: *StreamHandler, amount: u16) !void {
self.terminal.cursorRight(amount);
}
pub fn setCursorDown(self: *StreamHandler, amount: u16) !void {
self.terminal.cursorDown(amount);
}
pub fn setCursorUp(self: *StreamHandler, amount: u16) !void {
self.terminal.cursorUp(amount);
}
pub fn setCursorCol(self: *StreamHandler, col: u16) !void {
self.terminal.setCursorColAbsolute(col);
}
pub fn setCursorRow(self: *StreamHandler, row: u16) !void {
if (self.terminal.modes.origin) {
// TODO
log.err("setCursorRow: implement origin mode", .{});
unreachable;
}
self.terminal.setCursorPos(row, self.terminal.screen.cursor.x + 1);
}
pub fn setCursorPos(self: *StreamHandler, row: u16, col: u16) !void {
self.terminal.setCursorPos(row, col);
}
pub fn eraseDisplay(self: *StreamHandler, mode: terminal.EraseDisplay) !void {
if (mode == .complete) {
// Whenever we erase the full display, scroll to bottom.
try self.terminal.scrollViewport(.{ .bottom = {} });
try self.queueRender();
}
self.terminal.eraseDisplay(mode);
}
pub fn eraseLine(self: *StreamHandler, mode: terminal.EraseLine) !void {
self.terminal.eraseLine(mode);
}
pub fn deleteChars(self: *StreamHandler, count: usize) !void {
try self.terminal.deleteChars(count);
}
pub fn eraseChars(self: *StreamHandler, count: usize) !void {
self.terminal.eraseChars(count);
}
pub fn insertLines(self: *StreamHandler, count: usize) !void {
try self.terminal.insertLines(count);
}
pub fn insertBlanks(self: *StreamHandler, count: usize) !void {
self.terminal.insertBlanks(count);
}
pub fn deleteLines(self: *StreamHandler, count: usize) !void {
try self.terminal.deleteLines(count);
}
pub fn reverseIndex(self: *StreamHandler) !void {
try self.terminal.reverseIndex();
}
pub fn index(self: *StreamHandler) !void {
try self.terminal.index();
}
pub fn nextLine(self: *StreamHandler) !void {
self.terminal.carriageReturn();
try self.terminal.index();
}
pub fn setTopAndBottomMargin(self: *StreamHandler, top: u16, bot: u16) !void {
self.terminal.setScrollingRegion(top, bot);
}
// pub fn setMode(self: *StreamHandler, mode: terminal.Mode, enabled: bool) !void {
// switch (mode) {
// .reverse_colors => {
// self.terminal.modes.reverse_colors = enabled;
//
// // Schedule a render since we changed colors
// try self.queueRender();
// },
//
// .origin => {
// self.terminal.modes.origin = enabled;
// self.terminal.setCursorPos(1, 1);
// },
//
// .autowrap => {
// self.terminal.modes.autowrap = enabled;
// },
//
// .cursor_visible => {
// self.renderer_state.cursor.visible = enabled;
// },
//
// .alt_screen_save_cursor_clear_enter => {
// const opts: terminal.Terminal.AlternateScreenOptions = .{
// .cursor_save = true,
// .clear_on_enter = true,
// };
//
// if (enabled)
// self.terminal.alternateScreen(opts)
// else
// self.terminal.primaryScreen(opts);
//
// // Schedule a render since we changed screens
// try self.queueRender();
// },
//
// .bracketed_paste => self.bracketed_paste = true,
//
// .enable_mode_3 => {
// // Disable deccolm
// self.terminal.setDeccolmSupported(enabled);
//
// // Force resize back to the window size
// self.terminal.resize(self.alloc, self.grid_size.columns, self.grid_size.rows) catch |err|
// log.err("error updating terminal size: {}", .{err});
// },
//
// .@"132_column" => try self.terminal.deccolm(
// self.alloc,
// if (enabled) .@"132_cols" else .@"80_cols",
// ),
//
// .mouse_event_x10 => self.terminal.modes.mouse_event = if (enabled) .x10 else .none,
// .mouse_event_normal => self.terminal.modes.mouse_event = if (enabled) .normal else .none,
// .mouse_event_button => self.terminal.modes.mouse_event = if (enabled) .button else .none,
// .mouse_event_any => self.terminal.modes.mouse_event = if (enabled) .any else .none,
//
// .mouse_format_utf8 => self.terminal.modes.mouse_format = if (enabled) .utf8 else .x10,
// .mouse_format_sgr => self.terminal.modes.mouse_format = if (enabled) .sgr else .x10,
// .mouse_format_urxvt => self.terminal.modes.mouse_format = if (enabled) .urxvt else .x10,
// .mouse_format_sgr_pixels => self.terminal.modes.mouse_format = if (enabled) .sgr_pixels else .x10,
//
// else => if (enabled) log.warn("unimplemented mode: {}", .{mode}),
// }
// }
pub fn setAttribute(self: *StreamHandler, attr: terminal.Attribute) !void {
switch (attr) {
.unknown => |unk| log.warn("unimplemented or unknown attribute: {any}", .{unk}),
else => self.terminal.setAttribute(attr) catch |err|
log.warn("error setting attribute {}: {}", .{ attr, err }),
}
}
pub fn deviceAttributes(
self: *StreamHandler,
req: terminal.DeviceAttributeReq,
params: []const u16,
) !void {
_ = params;
switch (req) {
// VT220
.primary => self.queueWrite("\x1B[?62;c") catch |err|
log.warn("error queueing device attr response: {}", .{err}),
else => log.warn("unimplemented device attributes req: {}", .{req}),
}
}
pub fn deviceStatusReport(
self: *StreamHandler,
req: terminal.DeviceStatusReq,
) !void {
switch (req) {
.operating_status => self.queueWrite("\x1B[0n") catch |err|
log.warn("error queueing device attr response: {}", .{err}),
.cursor_position => {
const pos: struct {
x: usize,
y: usize,
} = if (self.terminal.modes.origin) .{
// TODO: what do we do if cursor is outside scrolling region?
.x = self.terminal.screen.cursor.x,
.y = self.terminal.screen.cursor.y -| self.terminal.scrolling_region.top,
} else .{
.x = self.terminal.screen.cursor.x,
.y = self.terminal.screen.cursor.y,
};
// Response always is at least 4 chars, so this leaves the
// remainder for the row/column as base-10 numbers. This
// will support a very large terminal.
var buf: [32]u8 = undefined;
const resp = try std.fmt.bufPrint(&buf, "\x1B[{};{}R", .{
pos.y + 1,
pos.x + 1,
});
try self.queueWrite(resp);
},
else => log.warn("unimplemented device status req: {}", .{req}),
}
}
pub fn setCursorStyle(
self: *StreamHandler,
style: terminal.CursorStyle,
) !void {
self.renderer_state.cursor.style = style;
}
pub fn decaln(self: *StreamHandler) !void {
try self.terminal.decaln();
}
pub fn tabClear(self: *StreamHandler, cmd: terminal.TabClear) !void {
self.terminal.tabClear(cmd);
}
pub fn tabSet(self: *StreamHandler) !void {
self.terminal.tabSet();
}
pub fn saveCursor(self: *StreamHandler) !void {
self.terminal.saveCursor();
}
pub fn restoreCursor(self: *StreamHandler) !void {
self.terminal.restoreCursor();
}
pub fn enquiry(self: *StreamHandler) !void {
try self.queueWrite("");
}
pub fn scrollDown(self: *StreamHandler, count: usize) !void {
try self.terminal.scrollDown(count);
}
pub fn scrollUp(self: *StreamHandler, count: usize) !void {
try self.terminal.scrollUp(count);
}
pub fn setActiveStatusDisplay(
self: *StreamHandler,
req: terminal.StatusDisplay,
) !void {
self.terminal.status_display = req;
}
pub fn configureCharset(
self: *StreamHandler,
slot: terminal.CharsetSlot,
set: terminal.Charset,
) !void {
self.terminal.configureCharset(slot, set);
}
pub fn invokeCharset(
self: *StreamHandler,
active: terminal.CharsetActiveSlot,
slot: terminal.CharsetSlot,
single: bool,
) !void {
self.terminal.invokeCharset(active, slot, single);
}
};