diff --git a/src/widgets/terminal/Screen.zig b/src/widgets/terminal/Screen.zig index 3f20e43..90df960 100644 --- a/src/widgets/terminal/Screen.zig +++ b/src/widgets/terminal/Screen.zig @@ -13,10 +13,22 @@ pub const Cell = struct { style: vaxis.Style = .{}, uri: std.ArrayList(u8) = undefined, uri_id: std.ArrayList(u8) = undefined, - width: u8 = 0, + width: u8 = 1, wrapped: bool = false, 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 { @@ -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 pub fn print( self: *Screen, @@ -288,3 +305,12 @@ pub fn cursorLeft(self: *Screen, n: usize) void { self.cursor.pending_wrap = false; 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); + } +} diff --git a/src/widgets/terminal/Terminal.zig b/src/widgets/terminal/Terminal.zig index b65aca0..5fdd012 100644 --- a/src/widgets/terminal/Terminal.zig +++ b/src/widgets/terminal/Terminal.zig @@ -11,6 +11,7 @@ const vaxis = @import("../../main.zig"); const Winsize = vaxis.Winsize; const Screen = @import("Screen.zig"); const DisplayWidth = @import("DisplayWidth"); +const Key = vaxis.Key; const grapheme = @import("grapheme"); @@ -26,6 +27,11 @@ pub const Options = struct { pub const Mode = struct { origin: bool = false, cursor: bool = true, + sync: bool = false, +}; + +pub const InputEvent = union(enum) { + key_press: vaxis.Key, }; 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 { // TODO: check sync - if (self.back_mutex.tryLock()) { + if (self.back_mutex.tryLock() and !self.mode.sync) { defer self.back_mutex.unlock(); 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); } +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 { const self: *const Terminal = @ptrCast(@alignCast(ptr)); return posix.read(self.pty.pty, buf); @@ -199,7 +223,26 @@ fn run(self: *Terminal) !void { 'B' => { // CUD var iter = seq.iterator(u16); 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 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.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' => { var iter = seq.iterator(u16); 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 { switch (mode) { - 25 => { - self.mode.cursor = val; + 25 => 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, } } + +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 => {}, + } +}