widgets(terminal): handle exiting events

This commit is contained in:
Tim Culverhouse 2024-06-09 06:43:14 -05:00
parent ef62de5541
commit d991755fe2
3 changed files with 160 additions and 9 deletions

View file

@ -3,6 +3,7 @@ const Command = @This();
const std = @import("std");
const builtin = @import("builtin");
const Pty = @import("Pty.zig");
const Terminal = @import("Terminal.zig");
const posix = std.posix;
@ -40,19 +41,45 @@ pub fn spawn(self: *Command, allocator: std.mem.Allocator) !void {
try posix.dup2(self.pty.tty, std.posix.STDOUT_FILENO);
try posix.dup2(self.pty.tty, std.posix.STDERR_FILENO);
// posix.close(self.pty.tty);
// if (self.pty.pty > 2) posix.close(self.pty.pty);
posix.close(self.pty.tty);
if (self.pty.pty > 2) posix.close(self.pty.pty);
// exec
const err = std.posix.execvpeZ(argv_buf.ptr[0].?, argv_buf.ptr, envp);
_ = err catch {};
@panic("a");
// const EOT = "\x04";
// _ = std.posix.write(self.pty.tty, EOT) catch {};
}
var act = posix.Sigaction{
.handler = .{ .handler = handleSigChild },
.mask = switch (builtin.os.tag) {
.macos => 0,
.linux => posix.empty_sigset,
else => @compileError("os not supported"),
},
.flags = 0,
};
try posix.sigaction(posix.SIG.CHLD, &act, null);
// we are the parent
self.pid = @intCast(pid);
return;
}
fn handleSigChild(_: c_int) callconv(.C) void {
std.log.err("sigchild", .{});
const result = std.posix.waitpid(-1, 0);
Terminal.global_vt_mutex.lock();
defer Terminal.global_vt_mutex.unlock();
if (Terminal.global_vts) |vts| {
var vt = vts.get(result.pid) orelse return;
vt.event_queue.push(.exited);
}
}
pub fn kill(self: *Command) void {
if (self.pid) |pid| {
std.posix.kill(pid, std.posix.SIG.TERM) catch {};

View file

@ -29,6 +29,20 @@ pub const Cell = struct {
self.wrapped = false;
self.dirty = true;
}
pub fn copyFrom(self: *Cell, src: Cell) !void {
self.char.clearRetainingCapacity();
try self.char.appendSlice(src.char.items);
self.style = src.style;
self.uri.clearRetainingCapacity();
try self.uri.appendSlice(src.uri.items);
self.uri_id.clearRetainingCapacity();
try self.uri_id.appendSlice(src.uri_id.items);
self.width = src.width;
self.wrapped = src.wrapped;
self.dirty = true;
}
};
pub const Cursor = struct {
@ -198,7 +212,8 @@ pub fn index(self: *Screen) !void {
// Inside scrolling region *and* at bottom of screen, we scroll contents up and insert a
// blank line
// TODO: scrollback if scrolling region is entire visible screen
@panic("TODO");
try self.deleteLine(1);
return;
}
self.cursor.row += 1;
}
@ -311,3 +326,55 @@ pub fn eraseRight(self: *Screen) void {
self.buf[i].erase(self.cursor.style.bg);
}
}
/// delete n lines from te bottom of te scrolling region
pub fn deleteLine(self: *Screen, n: usize) !void {
if (n == 0) return;
// Don't delete if outside scroll region
if (!self.withinScrollingRegion()) return;
self.cursor.pending_wrap = false;
// Number of rows from here to bottom of scroll region or n
const cnt = @min(self.scrolling_region.bottom - self.cursor.row + 1, n);
const stride = (self.width) * cnt;
var row: usize = self.scrolling_region.top;
while (row <= self.scrolling_region.bottom) : (row += 1) {
var col: usize = self.scrolling_region.left;
while (col <= self.scrolling_region.right) : (col += 1) {
const i = (row * self.width) + col;
if (row + cnt > self.scrolling_region.bottom)
self.buf[i].erase(self.cursor.style.bg)
else
try self.buf[i].copyFrom(self.buf[i + stride]);
}
}
}
/// insert n lines at the top of the scrolling region
pub fn insertLine(self: *Screen, n: usize) !void {
if (n == 0) return;
// Don't insert if outside scroll region
if (!self.withinScrollingRegion()) return;
self.cursor.pending_wrap = false;
// Number of rows from here to top of scroll region or n
const cnt = @min(self.cursor.row - self.scrolling_region.top + 1, n);
const stride = (self.width) * cnt;
var row: usize = self.scrolling_region.bottom;
while (row > self.scrolling_region.top) : (row -= 1) {
var col: usize = self.scrolling_region.left;
while (col <= self.scrolling_region.right) : (col += 1) {
const i = (row * self.width) + col;
if (row - cnt < self.scrolling_region.top)
self.buf[i].erase(self.cursor.style.bg)
else
try self.buf[i].copyFrom(self.buf[i - stride]);
}
}
}

View file

@ -12,6 +12,12 @@ const Winsize = vaxis.Winsize;
const Screen = @import("Screen.zig");
const DisplayWidth = @import("DisplayWidth");
const Key = vaxis.Key;
const Queue = vaxis.Queue(Event, 16);
pub const Event = union(enum) {
exited,
bell,
};
const grapheme = @import("grapheme");
@ -34,6 +40,9 @@ pub const InputEvent = union(enum) {
key_press: vaxis.Key,
};
pub var global_vt_mutex: std.Thread.Mutex = .{};
pub var global_vts: ?std.AutoHashMap(i32, *Terminal) = null;
allocator: std.mem.Allocator,
scrollback_size: usize,
@ -58,9 +67,7 @@ should_quit: bool = false,
mode: Mode = .{},
pending_events: struct {
bell: std.atomic.Value(bool) = std.atomic.Value(bool).init(false),
} = .{},
event_queue: Queue = .{},
/// initialize a Terminal. This sets the size of the underlying pty and allocates the sizes of the
/// screen
@ -93,6 +100,15 @@ pub fn init(
/// release all resources of the Terminal
pub fn deinit(self: *Terminal) void {
self.should_quit = true;
pid: {
global_vt_mutex.lock();
defer global_vt_mutex.unlock();
var vts = global_vts orelse break :pid;
if (self.cmd.pid) |pid|
_ = vts.remove(pid);
if (vts.count() == 0) vts.deinit();
}
self.cmd.kill();
if (self.thread) |thread| {
// write an EOT into the tty to trigger a read on our thread
@ -112,6 +128,17 @@ pub fn spawn(self: *Terminal) !void {
self.back_screen = &self.back_screen_pri;
try self.cmd.spawn(self.allocator);
{
// add to our global list
global_vt_mutex.lock();
defer global_vt_mutex.unlock();
if (global_vts == null)
global_vts = std.AutoHashMap(i32, *Terminal).init(self.allocator);
if (self.cmd.pid) |pid|
try global_vts.?.put(pid, self);
}
self.thread = try std.Thread.spawn(.{}, Terminal.run, .{self});
}
@ -160,6 +187,10 @@ pub fn draw(self: *Terminal, win: vaxis.Window) !void {
}
}
pub fn tryEvent(self: *Terminal) ?Event {
return self.event_queue.tryPop();
}
pub fn update(self: *Terminal, event: InputEvent) !void {
switch (event) {
.key_press => |key| try self.encodeKey(key, true),
@ -216,7 +247,7 @@ fn run(self: *Terminal) !void {
}
},
.c0 => |b| try self.handleC0(b),
.escape => |str| std.log.err("unhandled escape: {s}", .{str}),
.escape => |_| {}, // std.log.err("unhandled escape: {s}", .{str}),
.ss2 => |ss2| std.log.err("unhandled ss2: {c}", .{ss2}),
.ss3 => |ss3| std.log.err("unhandled ss3: {c}", .{ss3}),
.csi => |seq| {
@ -250,7 +281,7 @@ fn run(self: *Terminal) !void {
self.back_screen.width,
);
},
'H' => { // CUP
'H', 'f' => { // CUP
var iter = seq.iterator(u16);
const row = iter.next() orelse 1;
const col = iter.next() orelse 1;
@ -268,6 +299,16 @@ fn run(self: *Terminal) !void {
else => continue,
}
},
'L' => {
var iter = seq.iterator(u16);
const n = iter.next() orelse 1;
try self.back_screen.insertLine(n);
},
'M' => {
var iter = seq.iterator(u16);
const n = iter.next() orelse 1;
try self.back_screen.deleteLine(n);
},
'h', 'l' => {
var iter = seq.iterator(u16);
const mode = iter.next() orelse continue;
@ -293,6 +334,22 @@ fn run(self: *Terminal) !void {
}
}
},
'r' => {
if (seq.intermediate) |_| {
// TODO: XTRESTORE
continue;
}
if (seq.private_marker) |_| {
// TODO: DECCARA
continue;
}
// DECSTBM
var iter = seq.iterator(u16);
const top = iter.next() orelse 1;
const bottom = iter.next() orelse self.back_screen.height;
self.back_screen.scrolling_region.top = top - 1;
self.back_screen.scrolling_region.bottom = bottom - 1;
},
else => std.log.err("unhandled CSI: {}", .{seq}),
}
},
@ -307,7 +364,7 @@ inline fn handleC0(self: *Terminal, b: ansi.C0) !void {
.NUL, .SOH, .STX => {},
.EOT => {}, // we send EOT to quit the read thread
.ENQ => {},
.BEL => self.pending_events.bell.store(true, .unordered),
.BEL => self.event_queue.push(.bell),
.BS => self.back_screen.cursorLeft(1),
.HT => {}, // TODO: HT
.LF, .VT, .FF => try self.back_screen.index(),