widgets(terminal): implement even more csi sequences
This commit is contained in:
parent
3ab3edf6bf
commit
9f2af4d5ee
2 changed files with 184 additions and 9 deletions
|
@ -65,6 +65,7 @@ pub fn main() !void {
|
||||||
while (vt.tryEvent()) |event| {
|
while (vt.tryEvent()) |event| {
|
||||||
switch (event) {
|
switch (event) {
|
||||||
.bell => {},
|
.bell => {},
|
||||||
|
.title_change => {},
|
||||||
.exited => return,
|
.exited => return,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,6 +83,7 @@ pub fn main() !void {
|
||||||
|
|
||||||
const win = vx.window();
|
const win = vx.window();
|
||||||
win.clear();
|
win.clear();
|
||||||
|
win.hideCursor();
|
||||||
const child = win.child(.{
|
const child = win.child(.{
|
||||||
.x_off = 4,
|
.x_off = 4,
|
||||||
.y_off = 2,
|
.y_off = 2,
|
||||||
|
|
|
@ -13,10 +13,12 @@ const Screen = @import("Screen.zig");
|
||||||
const DisplayWidth = @import("DisplayWidth");
|
const DisplayWidth = @import("DisplayWidth");
|
||||||
const Key = vaxis.Key;
|
const Key = vaxis.Key;
|
||||||
const Queue = vaxis.Queue(Event, 16);
|
const Queue = vaxis.Queue(Event, 16);
|
||||||
|
const code_point = @import("code_point");
|
||||||
|
|
||||||
pub const Event = union(enum) {
|
pub const Event = union(enum) {
|
||||||
exited,
|
exited,
|
||||||
bell,
|
bell,
|
||||||
|
title_change,
|
||||||
};
|
};
|
||||||
|
|
||||||
const grapheme = @import("grapheme");
|
const grapheme = @import("grapheme");
|
||||||
|
@ -68,6 +70,9 @@ should_quit: bool = false,
|
||||||
mode: Mode = .{},
|
mode: Mode = .{},
|
||||||
|
|
||||||
tab_stops: std.ArrayList(u16),
|
tab_stops: std.ArrayList(u16),
|
||||||
|
title: std.ArrayList(u8),
|
||||||
|
|
||||||
|
last_printed: []const u8 = "",
|
||||||
|
|
||||||
event_queue: Queue = .{},
|
event_queue: Queue = .{},
|
||||||
|
|
||||||
|
@ -102,6 +107,7 @@ pub fn init(
|
||||||
.back_screen_alt = try Screen.init(allocator, opts.winsize.cols, opts.winsize.rows),
|
.back_screen_alt = try Screen.init(allocator, opts.winsize.cols, opts.winsize.rows),
|
||||||
.unicode = unicode,
|
.unicode = unicode,
|
||||||
.tab_stops = tabs,
|
.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_pri.deinit(self.allocator);
|
||||||
self.back_screen_alt.deinit(self.allocator);
|
self.back_screen_alt.deinit(self.allocator);
|
||||||
self.tab_stops.deinit();
|
self.tab_stops.deinit();
|
||||||
|
self.title.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn spawn(self: *Terminal) !void {
|
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.setCursorShape(self.front_screen.cursor.shape);
|
||||||
win.showCursor(self.front_screen.cursor.col, self.front_screen.cursor.row);
|
win.showCursor(self.front_screen.cursor.col, self.front_screen.cursor.row);
|
||||||
}
|
} else win.hideCursor();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn tryEvent(self: *Terminal) ?Event {
|
pub fn tryEvent(self: *Terminal) ?Event {
|
||||||
|
@ -256,9 +263,10 @@ fn run(self: *Terminal) !void {
|
||||||
.print => |str| {
|
.print => |str| {
|
||||||
var iter = grapheme.Iterator.init(str, &self.unicode.grapheme_data);
|
var iter = grapheme.Iterator.init(str, &self.unicode.grapheme_data);
|
||||||
while (iter.next()) |g| {
|
while (iter.next()) |g| {
|
||||||
const bytes = g.bytes(str);
|
const gr = g.bytes(str);
|
||||||
const w = try vaxis.gwidth.gwidth(bytes, .unicode, &self.unicode.width_data);
|
// TODO: use actual instead of .unicode
|
||||||
self.back_screen.print(bytes, @truncate(w));
|
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),
|
.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;
|
self.back_screen.cursor.row = self.back_screen.scrolling_region.top;
|
||||||
try self.back_screen.insertLine(n);
|
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' => {
|
'X' => {
|
||||||
self.back_screen.cursor.pending_wrap = false;
|
self.back_screen.cursor.pending_wrap = false;
|
||||||
var iter = seq.iterator(u16);
|
var iter = seq.iterator(u16);
|
||||||
|
@ -410,14 +431,58 @@ fn run(self: *Terminal) !void {
|
||||||
const end = @max(
|
const end = @max(
|
||||||
self.back_screen.cursor.row * self.back_screen.width + self.back_screen.width,
|
self.back_screen.cursor.row * self.back_screen.width + self.back_screen.width,
|
||||||
n,
|
n,
|
||||||
|
1, // In case n == 0
|
||||||
);
|
);
|
||||||
var i: usize = start;
|
var i: usize = start;
|
||||||
while (i < end) : (i += 1) {
|
while (i < end) : (i += 1) {
|
||||||
self.back_screen.buf[i].erase(self.back_screen.cursor.style.bg);
|
self.back_screen.buf[i].erase(self.back_screen.cursor.style.bg);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// 'Z' => {}, // TODO: Back tab
|
'Z' => {
|
||||||
// Cursor Vertial Position Aboslute
|
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' => {
|
'd' => {
|
||||||
var iter = seq.iterator(u16);
|
var iter = seq.iterator(u16);
|
||||||
const n = iter.next() orelse 1;
|
const n = iter.next() orelse 1;
|
||||||
|
@ -427,6 +492,34 @@ fn run(self: *Terminal) !void {
|
||||||
n -| 1,
|
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' => {
|
'h', 'l' => {
|
||||||
var iter = seq.iterator(u16);
|
var iter = seq.iterator(u16);
|
||||||
const mode = iter.next() orelse continue;
|
const mode = iter.next() orelse continue;
|
||||||
|
@ -439,6 +532,40 @@ fn run(self: *Terminal) !void {
|
||||||
if (seq.intermediate == null and seq.private_marker == null) {
|
if (seq.intermediate == null and seq.private_marker == null) {
|
||||||
self.back_screen.sgr(seq);
|
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' => {
|
'q' => {
|
||||||
if (seq.intermediate) |int| {
|
if (seq.intermediate) |int| {
|
||||||
|
@ -451,6 +578,16 @@ fn run(self: *Terminal) !void {
|
||||||
else => {},
|
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' => {
|
'r' => {
|
||||||
if (seq.intermediate) |_| {
|
if (seq.intermediate) |_| {
|
||||||
|
@ -471,7 +608,24 @@ fn run(self: *Terminal) !void {
|
||||||
else => std.log.err("unhandled CSI: {}", .{seq}),
|
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}),
|
.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
|
// Move right the delta
|
||||||
self.back_screen.cursorRight(final - col);
|
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);
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue