widgets(terminal): implement even more csi sequences

This commit is contained in:
Tim Culverhouse 2024-06-10 14:18:55 -05:00
parent 3ab3edf6bf
commit 9f2af4d5ee
2 changed files with 184 additions and 9 deletions

View file

@ -65,6 +65,7 @@ pub fn main() !void {
while (vt.tryEvent()) |event| {
switch (event) {
.bell => {},
.title_change => {},
.exited => return,
}
}
@ -82,6 +83,7 @@ pub fn main() !void {
const win = vx.window();
win.clear();
win.hideCursor();
const child = win.child(.{
.x_off = 4,
.y_off = 2,

View file

@ -13,10 +13,12 @@ const Screen = @import("Screen.zig");
const DisplayWidth = @import("DisplayWidth");
const Key = vaxis.Key;
const Queue = vaxis.Queue(Event, 16);
const code_point = @import("code_point");
pub const Event = union(enum) {
exited,
bell,
title_change,
};
const grapheme = @import("grapheme");
@ -68,6 +70,9 @@ should_quit: bool = false,
mode: Mode = .{},
tab_stops: std.ArrayList(u16),
title: std.ArrayList(u8),
last_printed: []const u8 = "",
event_queue: Queue = .{},
@ -102,6 +107,7 @@ pub fn init(
.back_screen_alt = try Screen.init(allocator, opts.winsize.cols, opts.winsize.rows),
.unicode = unicode,
.tab_stops = tabs,
.title = std.ArrayList(u8).init(allocator),
};
}
@ -133,6 +139,7 @@ pub fn deinit(self: *Terminal) void {
self.back_screen_pri.deinit(self.allocator);
self.back_screen_alt.deinit(self.allocator);
self.tab_stops.deinit();
self.title.deinit();
}
pub fn spawn(self: *Terminal) !void {
@ -196,10 +203,10 @@ pub fn draw(self: *Terminal, win: vaxis.Window) !void {
}
}
if (self.front_screen.cursor.visible) {
if (self.mode.cursor) {
win.setCursorShape(self.front_screen.cursor.shape);
win.showCursor(self.front_screen.cursor.col, self.front_screen.cursor.row);
}
} else win.hideCursor();
}
pub fn tryEvent(self: *Terminal) ?Event {
@ -256,9 +263,10 @@ fn run(self: *Terminal) !void {
.print => |str| {
var iter = grapheme.Iterator.init(str, &self.unicode.grapheme_data);
while (iter.next()) |g| {
const bytes = g.bytes(str);
const w = try vaxis.gwidth.gwidth(bytes, .unicode, &self.unicode.width_data);
self.back_screen.print(bytes, @truncate(w));
const gr = g.bytes(str);
// TODO: use actual instead of .unicode
const w = try vaxis.gwidth.gwidth(gr, .unicode, &self.unicode.width_data);
self.back_screen.print(gr, @truncate(w));
}
},
.c0 => |b| try self.handleC0(b),
@ -401,7 +409,20 @@ fn run(self: *Terminal) !void {
self.back_screen.cursor.row = self.back_screen.scrolling_region.top;
try self.back_screen.insertLine(n);
},
// 'W' => {}, // TODO: Tab control
// Tab Control
'W' => {
if (seq.private_marker) |pm| {
if (pm != '?') continue;
var iter = seq.iterator(u16);
const n = iter.next() orelse continue;
if (n != 5) continue;
self.tab_stops.clearRetainingCapacity();
var col: u16 = 0;
while (col < self.back_screen.width) : (col += 8) {
try self.tab_stops.append(col);
}
}
},
'X' => {
self.back_screen.cursor.pending_wrap = false;
var iter = seq.iterator(u16);
@ -410,14 +431,58 @@ fn run(self: *Terminal) !void {
const end = @max(
self.back_screen.cursor.row * self.back_screen.width + self.back_screen.width,
n,
1, // In case n == 0
);
var i: usize = start;
while (i < end) : (i += 1) {
self.back_screen.buf[i].erase(self.back_screen.cursor.style.bg);
}
},
// 'Z' => {}, // TODO: Back tab
// Cursor Vertial Position Aboslute
'Z' => {
var iter = seq.iterator(u16);
const n = iter.next() orelse 1;
self.horizontalBackTab(n);
},
// Cursor Horizontal Position Relative
'a' => {
var iter = seq.iterator(u16);
const n = iter.next() orelse 1;
self.back_screen.cursor.pending_wrap = false;
const max_end = if (self.mode.origin)
self.back_screen.scrolling_region.right
else
self.back_screen.width - 1;
self.back_screen.cursor.col = @min(
self.back_screen.cursor.col + max_end,
self.back_screen.cursor.col + n,
);
},
// Repeat Previous Character
'b' => {
var iter = seq.iterator(u16);
const n = iter.next() orelse 1;
// TODO: maybe not .unicode
const w = try vaxis.gwidth.gwidth(self.last_printed, .unicode, &self.unicode.width_data);
var i: usize = 0;
while (i < n) : (i += 1) {
self.back_screen.print(self.last_printed, @truncate(w));
}
},
// Device Attributes
'c' => {
if (seq.private_marker) |pm| {
switch (pm) {
// Secondary
'>' => try self.anyWriter().writeAll("\x1B[>1;69;0c"),
'=' => try self.anyWriter().writeAll("\x1B[=0000c"),
else => std.log.err("unhandled CSI: {}", .{seq}),
}
} else {
// Primary
try self.anyWriter().writeAll("\x1B[?62;22c");
}
},
// Cursor Vertical Position Absolute
'd' => {
var iter = seq.iterator(u16);
const n = iter.next() orelse 1;
@ -427,6 +492,34 @@ fn run(self: *Terminal) !void {
n -| 1,
);
},
// Cursor Horizontal Position Absolute
'e' => {
var iter = seq.iterator(u16);
const n = iter.next() orelse 1;
self.back_screen.cursor.pending_wrap = false;
self.back_screen.cursor.col = @min(
self.back_screen.width -| 1,
n -| 1,
);
},
// Tab Clear
'g' => {
var iter = seq.iterator(u16);
const n = iter.next() orelse 0;
switch (n) {
0 => {
const current = try self.tab_stops.toOwnedSlice();
defer self.tab_stops.allocator.free(current);
self.tab_stops.clearRetainingCapacity();
for (current) |stop| {
if (stop == self.back_screen.cursor.col) continue;
try self.tab_stops.append(stop);
}
},
3 => self.tab_stops.clearAndFree(),
else => std.log.err("unhandled CSI: {}", .{seq}),
}
},
'h', 'l' => {
var iter = seq.iterator(u16);
const mode = iter.next() orelse continue;
@ -439,6 +532,40 @@ fn run(self: *Terminal) !void {
if (seq.intermediate == null and seq.private_marker == null) {
self.back_screen.sgr(seq);
}
// TODO: private marker and intermediates
},
'n' => {
var iter = seq.iterator(u16);
const ps = iter.next() orelse 0;
if (seq.intermediate == null and seq.private_marker == null) {
switch (ps) {
5 => try self.anyWriter().writeAll("\x1b[0n"),
6 => try self.anyWriter().print("\x1b[{d};{d}R", .{
self.back_screen.cursor.row + 1,
self.back_screen.cursor.col + 1,
}),
else => std.log.err("unhandled CSI: {}", .{seq}),
}
}
},
'p' => {
var iter = seq.iterator(u16);
const ps = iter.next() orelse 0;
if (seq.intermediate) |int| {
switch (int) {
// report mode
'$' => {
switch (ps) {
2026 => try self.anyWriter().writeAll("\x1b[?2026;2$p"),
else => {
std.log.warn("unhandled mode: {}", .{ps});
try self.anyWriter().print("\x1b[?{d};0$p", .{ps});
},
}
},
else => std.log.err("unhandled CSI: {}", .{seq}),
}
}
},
'q' => {
if (seq.intermediate) |int| {
@ -451,6 +578,16 @@ fn run(self: *Terminal) !void {
else => {},
}
}
if (seq.private_marker) |pm| {
switch (pm) {
// XTVERSION
'>' => try self.anyWriter().print(
"\x1bP>|libvaxis {s}\x1B\\",
.{"dev"},
),
else => std.log.err("unhandled CSI: {}", .{seq}),
}
}
},
'r' => {
if (seq.intermediate) |_| {
@ -471,7 +608,24 @@ fn run(self: *Terminal) !void {
else => std.log.err("unhandled CSI: {}", .{seq}),
}
},
.osc => |osc| std.log.err("unhandled osc: {s}", .{osc}),
.osc => |osc| {
const semicolon = std.mem.indexOfScalar(u8, osc, ';') orelse {
std.log.err("unhandled osc: {s}", .{osc});
continue;
};
const ps = std.fmt.parseUnsigned(u8, osc[0..semicolon], 10) catch {
std.log.err("unhandled osc: {s}", .{osc});
continue;
};
switch (ps) {
0 => {
self.title.clearRetainingCapacity();
try self.title.appendSlice(osc[semicolon + 1 ..]);
self.event_queue.push(.title_change);
},
else => std.log.err("unhandled osc: {s}", .{osc}),
}
},
.apc => |apc| std.log.err("unhandled apc: {s}", .{apc}),
}
}
@ -552,3 +706,22 @@ pub fn horizontalTab(self: *Terminal, n: usize) void {
// Move right the delta
self.back_screen.cursorRight(final - col);
}
pub fn horizontalBackTab(self: *Terminal, n: usize) void {
// Get the current cursor position
const col = self.back_screen.cursor.col;
// Find the index of the next backtab
const idx = for (self.tab_stops.items, 0..) |ts, i| {
if (ts <= col) continue;
break i;
} else self.tab_stops.items.len - 1;
const final = if (self.mode.origin)
@max(self.tab_stops.items[idx -| (n -| 1)], self.back_screen.scrolling_region.left)
else
self.tab_stops.items[idx -| (n -| 1)];
// Move left the delta
self.back_screen.cursorLeft(final - col);
}