vaxis: use vt caps to measure grapheme widths

Implement our own grapheme measuring function which switches on whether
the terminal supports mode 2027

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
This commit is contained in:
Tim Culverhouse 2024-01-24 06:12:39 -06:00
parent 04f6117cfe
commit 9f89ed06f3
6 changed files with 30 additions and 9 deletions

View file

@ -10,6 +10,8 @@ const InternalScreen = @This();
pub const InternalCell = struct { pub const InternalCell = struct {
char: std.ArrayList(u8) = undefined, char: std.ArrayList(u8) = undefined,
style: Style = .{}, style: Style = .{},
// if we got skipped because of a wide character
skipped: bool = false,
pub fn eql(self: InternalCell, cell: Cell) bool { pub fn eql(self: InternalCell, cell: Cell) bool {
return std.mem.eql(u8, self.char.items, cell.char.grapheme) and std.meta.eql(self.style, cell.style); return std.mem.eql(u8, self.char.items, cell.char.grapheme) and std.meta.eql(self.style, cell.style);

View file

@ -16,6 +16,8 @@ cursor_row: usize = 0,
cursor_col: usize = 0, cursor_col: usize = 0,
cursor_vis: bool = false, cursor_vis: bool = false,
unicode: bool = false,
pub fn init(alloc: std.mem.Allocator, w: usize, h: usize) !Screen { pub fn init(alloc: std.mem.Allocator, w: usize, h: usize) !Screen {
var self = Screen{ var self = Screen{
.buf = try alloc.alloc(Cell, w * h), .buf = try alloc.alloc(Cell, w * h),

View file

@ -178,13 +178,17 @@ pub fn run(
} }
}, },
.cap_kitty_keyboard => { .cap_kitty_keyboard => {
log.info("kitty capability detected", .{});
vx.caps.kitty_keyboard = true; vx.caps.kitty_keyboard = true;
}, },
.cap_rgb => { .cap_rgb => {
log.info("rgb capability detected", .{});
vx.caps.rgb = true; vx.caps.rgb = true;
}, },
.cap_unicode => { .cap_unicode => {
log.info("unicode capability detected", .{});
vx.caps.unicode = true; vx.caps.unicode = true;
vx.screen.unicode = true;
}, },
.cap_da1 => { .cap_da1 => {
std.Thread.Futex.wake(&vx.query_futex, 10); std.Thread.Futex.wake(&vx.query_futex, 10);

View file

@ -2,6 +2,7 @@ const std = @import("std");
const Screen = @import("Screen.zig"); const Screen = @import("Screen.zig");
const Cell = @import("cell.zig").Cell; const Cell = @import("cell.zig").Cell;
const gw = @import("gwidth.zig");
const log = std.log.scoped(.window); const log = std.log.scoped(.window);
@ -72,6 +73,13 @@ pub fn clear(self: Window) void {
self.fill(.{}); self.fill(.{});
} }
/// returns the width of the grapheme. This depends on the terminal capabilities
pub fn gwidth(self: Window, str: []const u8) usize {
const m: gw.Method = if (self.screen.unicode) .unicode else .wcwidth;
log.info("using method {any}", .{m});
return gw.gwidth(str, m) catch 1;
}
/// fills the window with the provided cell /// fills the window with the provided cell
pub fn fill(self: Window, cell: Cell) void { pub fn fill(self: Window, cell: Cell) void {
var row: usize = self.y_off; var row: usize = self.y_off;

View file

@ -12,6 +12,7 @@ const Window = @import("Window.zig");
const Options = @import("Options.zig"); const Options = @import("Options.zig");
const Style = @import("cell.zig").Style; const Style = @import("cell.zig").Style;
const strWidth = @import("ziglyph").display_width.strWidth; const strWidth = @import("ziglyph").display_width.strWidth;
const gwidth = @import("gwidth.zig");
/// Vaxis is the entrypoint for a Vaxis application. The provided type T should /// Vaxis is the entrypoint for a Vaxis application. The provided type T should
/// be a tagged union which contains all of the events the application will /// be a tagged union which contains all of the events the application will
@ -141,6 +142,7 @@ pub fn Vaxis(comptime T: type) type {
log.debug("resizing screen: width={d} height={d}", .{ winsize.cols, winsize.rows }); log.debug("resizing screen: width={d} height={d}", .{ winsize.cols, winsize.rows });
self.screen.deinit(alloc); self.screen.deinit(alloc);
self.screen = try Screen.init(alloc, winsize.cols, winsize.rows); self.screen = try Screen.init(alloc, winsize.cols, winsize.rows);
self.screen.unicode = self.caps.unicode;
// try self.screen.int(alloc, winsize.cols, winsize.rows); // try self.screen.int(alloc, winsize.cols, winsize.rows);
// we only init our current screen. This has the effect of redrawing // we only init our current screen. This has the effect of redrawing
// every cell // every cell
@ -270,12 +272,14 @@ pub fn Vaxis(comptime T: type) type {
const cell = self.screen.buf[i]; const cell = self.screen.buf[i];
defer { defer {
// advance by the width of this char mod 1 // advance by the width of this char mod 1
const width = blk: { const method: gwidth.Method = if (self.caps.unicode) .unicode else .wcwidth;
if (cell.char.width > 0) break :blk cell.char.width; const w = gwidth.gwidth(cell.char.grapheme, method) catch 1;
break :blk strWidth(cell.char.grapheme, .half) catch 1; var j = i + 1;
}; while (j < i + w) : (j += 1) {
col += width; self.screen_last.buf[j].skipped = true;
i += width; }
col += w;
i += w;
} }
if (col >= self.screen.width) { if (col >= self.screen.width) {
row += 1; row += 1;
@ -283,7 +287,8 @@ pub fn Vaxis(comptime T: type) type {
} }
// If cell is the same as our last frame, we don't need to do // If cell is the same as our last frame, we don't need to do
// anything // anything
if (!self.refresh and self.screen_last.buf[i].eql(cell)) { const last = self.screen_last.buf[i];
if (!self.refresh and last.eql(cell) and !last.skipped) {
reposition = true; reposition = true;
// Close any osc8 sequence we might be in before // Close any osc8 sequence we might be in before
// repositioning // repositioning
@ -292,6 +297,7 @@ pub fn Vaxis(comptime T: type) type {
} }
continue; continue;
} }
self.screen_last.buf[i].skipped = false;
defer cursor = cell.style; defer cursor = cell.style;
// Set this cell in the last frame // Set this cell in the last frame
self.screen_last.writeCell(col, row, cell.char.grapheme, cell.style); self.screen_last.writeCell(col, row, cell.char.grapheme, cell.style);

View file

@ -3,7 +3,6 @@ const Cell = @import("../cell.zig").Cell;
const Key = @import("../Key.zig"); const Key = @import("../Key.zig");
const Window = @import("../Window.zig"); const Window = @import("../Window.zig");
const GraphemeIterator = @import("ziglyph").GraphemeIterator; const GraphemeIterator = @import("ziglyph").GraphemeIterator;
const strWidth = @import("ziglyph").display_width.strWidth;
const log = std.log.scoped(.text_input); const log = std.log.scoped(.text_input);
@ -67,7 +66,7 @@ pub fn draw(self: *TextInput, win: Window) void {
var cursor_idx: usize = 0; var cursor_idx: usize = 0;
while (iter.next()) |grapheme| { while (iter.next()) |grapheme| {
const g = grapheme.slice(self.buf.items); const g = grapheme.slice(self.buf.items);
const w = strWidth(g, .half) catch 1; const w = win.gwidth(g);
win.writeCell(col, 0, .{ win.writeCell(col, 0, .{
.char = .{ .char = .{
.grapheme = g, .grapheme = g,