widgets(terminal): simple key encoding, alt screen

This commit is contained in:
Tim Culverhouse 2024-06-07 18:51:36 -05:00
parent 78508b76bd
commit 7a7f4892d7
2 changed files with 106 additions and 5 deletions

View file

@ -13,10 +13,22 @@ pub const Cell = struct {
style: vaxis.Style = .{}, style: vaxis.Style = .{},
uri: std.ArrayList(u8) = undefined, uri: std.ArrayList(u8) = undefined,
uri_id: std.ArrayList(u8) = undefined, uri_id: std.ArrayList(u8) = undefined,
width: u8 = 0, width: u8 = 1,
wrapped: bool = false, wrapped: bool = false,
dirty: bool = true, dirty: bool = true,
pub fn erase(self: *Cell, bg: vaxis.Color) void {
self.char.clearRetainingCapacity();
self.char.append(' ') catch unreachable; // we never completely free this list
self.style = .{};
self.style.bg = bg;
self.uri.clearRetainingCapacity();
self.uri_id.clearRetainingCapacity();
self.width = 1;
self.wrapped = false;
self.dirty = true;
}
}; };
pub const Cursor = struct { pub const Cursor = struct {
@ -130,6 +142,11 @@ pub fn readCell(self: *Screen, col: usize, row: usize) ?vaxis.Cell {
}; };
} }
/// returns true if the current cursor position is within the scrolling region
pub fn withinScrollingRegion(self: Screen) bool {
return self.scrolling_region.contains(self.cursor.col, self.cursor.row);
}
/// writes a cell to a location. 0 indexed /// writes a cell to a location. 0 indexed
pub fn print( pub fn print(
self: *Screen, self: *Screen,
@ -288,3 +305,12 @@ pub fn cursorLeft(self: *Screen, n: usize) void {
self.cursor.pending_wrap = false; self.cursor.pending_wrap = false;
self.cursor.col -= cnt; self.cursor.col -= cnt;
} }
pub fn eraseRight(self: *Screen) void {
self.cursor.pending_wrap = false;
const end = (self.cursor.row * self.width) + (self.width);
var i = (self.cursor.row * self.width) + self.cursor.col;
while (i < end) : (i += 1) {
self.buf[i].erase(self.cursor.style.bg);
}
}

View file

@ -11,6 +11,7 @@ const vaxis = @import("../../main.zig");
const Winsize = vaxis.Winsize; const Winsize = vaxis.Winsize;
const Screen = @import("Screen.zig"); const Screen = @import("Screen.zig");
const DisplayWidth = @import("DisplayWidth"); const DisplayWidth = @import("DisplayWidth");
const Key = vaxis.Key;
const grapheme = @import("grapheme"); const grapheme = @import("grapheme");
@ -26,6 +27,11 @@ pub const Options = struct {
pub const Mode = struct { pub const Mode = struct {
origin: bool = false, origin: bool = false,
cursor: bool = true, cursor: bool = true,
sync: bool = false,
};
pub const InputEvent = union(enum) {
key_press: vaxis.Key,
}; };
allocator: std.mem.Allocator, allocator: std.mem.Allocator,
@ -134,7 +140,7 @@ pub fn resize(self: *Terminal, ws: Winsize) !void {
pub fn draw(self: *Terminal, win: vaxis.Window) !void { pub fn draw(self: *Terminal, win: vaxis.Window) !void {
// TODO: check sync // TODO: check sync
if (self.back_mutex.tryLock()) { if (self.back_mutex.tryLock() and !self.mode.sync) {
defer self.back_mutex.unlock(); defer self.back_mutex.unlock();
try self.back_screen.copyTo(&self.front_screen); try self.back_screen.copyTo(&self.front_screen);
} }
@ -153,6 +159,24 @@ pub fn draw(self: *Terminal, win: vaxis.Window) !void {
win.showCursor(self.front_screen.cursor.col, self.front_screen.cursor.row); win.showCursor(self.front_screen.cursor.col, self.front_screen.cursor.row);
} }
pub fn update(self: *Terminal, event: InputEvent) !void {
switch (event) {
.key_press => |key| try self.encodeKey(key, true),
}
}
fn opaqueWrite(ptr: *const anyopaque, buf: []const u8) !usize {
const self: *const Terminal = @ptrCast(@alignCast(ptr));
return posix.write(self.pty.pty, buf);
}
pub fn anyWriter(self: *const Terminal) std.io.AnyWriter {
return .{
.context = self,
.writeFn = Terminal.opaqueWrite,
};
}
fn opaqueRead(ptr: *const anyopaque, buf: []u8) !usize { fn opaqueRead(ptr: *const anyopaque, buf: []u8) !usize {
const self: *const Terminal = @ptrCast(@alignCast(ptr)); const self: *const Terminal = @ptrCast(@alignCast(ptr));
return posix.read(self.pty.pty, buf); return posix.read(self.pty.pty, buf);
@ -199,7 +223,26 @@ fn run(self: *Terminal) !void {
'B' => { // CUD 'B' => { // CUD
var iter = seq.iterator(u16); var iter = seq.iterator(u16);
const delta = iter.next() orelse 1; const delta = iter.next() orelse 1;
self.back_screen.cursor.row = @min(self.back_screen.height - 1, self.back_screen.cursor.row + delta); self.back_screen.cursor.row = @min(
self.back_screen.height - 1,
self.back_screen.cursor.row + delta,
);
},
'C' => {
self.back_screen.cursor.pending_wrap = false;
var iter = seq.iterator(u16);
const delta = iter.next() orelse 1;
const within = self.back_screen.withinScrollingRegion();
if (within)
self.back_screen.cursor.col = @min(
self.back_screen.cursor.col + delta,
self.back_screen.scrolling_region.right,
)
else
self.back_screen.cursor.col = @min(
self.back_screen.cursor.col + delta,
self.back_screen.width,
);
}, },
'H' => { // CUP 'H' => { // CUP
var iter = seq.iterator(u16); var iter = seq.iterator(u16);
@ -208,6 +251,17 @@ fn run(self: *Terminal) !void {
self.back_screen.cursor.col = col - 1; self.back_screen.cursor.col = col - 1;
self.back_screen.cursor.row = row - 1; self.back_screen.cursor.row = row - 1;
}, },
'K' => {
// TODO selective erase (private_marker == '?')
var iter = seq.iterator(u8);
const ps = iter.next() orelse 0;
switch (ps) {
0 => self.back_screen.eraseRight(),
1 => {},
2 => {},
else => continue,
}
},
'h', 'l' => { 'h', 'l' => {
var iter = seq.iterator(u16); var iter = seq.iterator(u16);
const mode = iter.next() orelse continue; const mode = iter.next() orelse continue;
@ -256,9 +310,30 @@ inline fn handleC0(self: *Terminal, b: ansi.C0) !void {
pub fn setMode(self: *Terminal, mode: u16, val: bool) void { pub fn setMode(self: *Terminal, mode: u16, val: bool) void {
switch (mode) { switch (mode) {
25 => { 25 => self.mode.cursor = val,
self.mode.cursor = val; 1049 => {
if (val)
self.back_screen = &self.back_screen_alt
else
self.back_screen = &self.back_screen_pri;
}, },
2026 => self.mode.sync = val,
else => return, else => return,
} }
} }
pub fn encodeKey(self: *Terminal, key: vaxis.Key, press: bool) !void {
switch (press) {
true => {
if (key.text) |text| {
try self.anyWriter().writeAll(text);
return;
}
switch (key.codepoint) {
0x00...0x7F => try self.anyWriter().writeByte(@intCast(key.codepoint)),
else => {},
}
},
false => {},
}
}