core: replace ziglyph.GraphemeIterator with zg version

ziglyph is being replaced by zg. Replace all calls to ziglyph grapheme
iterator with the zg version
This commit is contained in:
Tim Culverhouse 2024-04-29 13:01:04 -05:00
parent e7915b5dd7
commit 9fec6f122b
10 changed files with 83 additions and 39 deletions

View file

@ -10,6 +10,10 @@ pub fn build(b: *std.Build) void {
.optimize = optimize, .optimize = optimize,
.target = target, .target = target,
}); });
const zg_dep = b.dependency("zg", .{
.optimize = optimize,
.target = target,
});
const zigimg_dep = b.dependency("zigimg", .{ const zigimg_dep = b.dependency("zigimg", .{
.optimize = optimize, .optimize = optimize,
.target = target, .target = target,
@ -30,6 +34,7 @@ pub fn build(b: *std.Build) void {
.optimize = optimize, .optimize = optimize,
}); });
vaxis_mod.addImport("ziglyph", ziglyph_dep.module("ziglyph")); vaxis_mod.addImport("ziglyph", ziglyph_dep.module("ziglyph"));
vaxis_mod.addImport("grapheme", zg_dep.module("grapheme"));
vaxis_mod.addImport("zigimg", zigimg_dep.module("zigimg")); vaxis_mod.addImport("zigimg", zigimg_dep.module("zigimg"));
vaxis_mod.addImport("gap_buffer", gap_buffer_dep.module("gap_buffer")); vaxis_mod.addImport("gap_buffer", gap_buffer_dep.module("gap_buffer"));
vaxis_mod.addImport("znvim", znvim_dep.module("znvim")); vaxis_mod.addImport("znvim", znvim_dep.module("znvim"));
@ -67,6 +72,7 @@ pub fn build(b: *std.Build) void {
.optimize = optimize, .optimize = optimize,
}); });
tests.root_module.addImport("ziglyph", ziglyph_dep.module("ziglyph")); tests.root_module.addImport("ziglyph", ziglyph_dep.module("ziglyph"));
tests.root_module.addImport("grapheme", zg_dep.module("grapheme"));
tests.root_module.addImport("zigimg", zigimg_dep.module("zigimg")); tests.root_module.addImport("zigimg", zigimg_dep.module("zigimg"));
tests.root_module.addImport("gap_buffer", gap_buffer_dep.module("gap_buffer")); tests.root_module.addImport("gap_buffer", gap_buffer_dep.module("gap_buffer"));
tests.root_module.addImport("znvim", znvim_dep.module("znvim")); tests.root_module.addImport("znvim", znvim_dep.module("znvim"));

View file

@ -20,5 +20,9 @@
.url = "git+https://github.com/jinzhongjia/znvim#7927b8042872d5fa5f30862302bea1290d372d4f", .url = "git+https://github.com/jinzhongjia/znvim#7927b8042872d5fa5f30862302bea1290d372d4f",
.hash = "12202372c2043a9ac557144d327c09638ccd8d615bba459ba17d1a7a4197a213d939", .hash = "12202372c2043a9ac557144d327c09638ccd8d615bba459ba17d1a7a4197a213d939",
}, },
.zg = .{
.url = "git+https://codeberg.org/dude_the_builder/zg#16735685fcc3410de361ba3411788ad1fb4fe188",
.hash = "1220fe9ac5cdb41833d327a78745614e67d472469f8666567bd8cf9f5847a52b1c51",
},
}, },
} }

View file

@ -30,7 +30,7 @@ pub fn main() !void {
const alloc = gpa.allocator(); const alloc = gpa.allocator();
// Initialize Vaxis // Initialize Vaxis
var vx = try vaxis.init(.{}); var vx = try vaxis.init(alloc, .{});
// deinit takes an optional allocator. If your program is exiting, you can // deinit takes an optional allocator. If your program is exiting, you can
// choose to pass a null allocator to save some exit time. // choose to pass a null allocator to save some exit time.
defer vx.deinit(alloc); defer vx.deinit(alloc);
@ -53,7 +53,7 @@ pub fn main() !void {
// init our text input widget. The text input widget needs an allocator to // init our text input widget. The text input widget needs an allocator to
// store the contents of the input // store the contents of the input
var text_input = TextInput.init(alloc); var text_input = TextInput.init(alloc, &vx.unicode);
defer text_input.deinit(); defer text_input.deinit();
// Sends queries to terminal to detect certain features. This should // Sends queries to terminal to detect certain features. This should

View file

@ -5,6 +5,8 @@ const Cell = @import("Cell.zig");
const Shape = @import("Mouse.zig").Shape; const Shape = @import("Mouse.zig").Shape;
const Image = @import("Image.zig"); const Image = @import("Image.zig");
const Winsize = @import("Tty.zig").Winsize; const Winsize = @import("Tty.zig").Winsize;
const Unicode = @import("Unicode.zig");
const Method = @import("gwidth.zig").Method;
const log = std.log.scoped(.screen); const log = std.log.scoped(.screen);
@ -22,13 +24,14 @@ cursor_row: usize = 0,
cursor_col: usize = 0, cursor_col: usize = 0,
cursor_vis: bool = false, cursor_vis: bool = false,
/// true when we measure cells with unicode unicode: *const Unicode = undefined,
unicode: bool = false,
width_method: Method = .wcwidth,
mouse_shape: Shape = .default, mouse_shape: Shape = .default,
cursor_shape: Cell.CursorShape = .default, cursor_shape: Cell.CursorShape = .default,
pub fn init(alloc: std.mem.Allocator, winsize: Winsize) !Screen { pub fn init(alloc: std.mem.Allocator, winsize: Winsize, unicode: *const Unicode) !Screen {
const w = winsize.cols; const w = winsize.cols;
const h = winsize.rows; const h = winsize.rows;
var self = Screen{ var self = Screen{
@ -37,6 +40,7 @@ pub fn init(alloc: std.mem.Allocator, winsize: Winsize) !Screen {
.height = h, .height = h,
.width_pix = winsize.x_pixel, .width_pix = winsize.x_pixel,
.height_pix = winsize.y_pixel, .height_pix = winsize.y_pixel,
.unicode = unicode,
}; };
for (self.buf, 0..) |_, i| { for (self.buf, 0..) |_, i| {
self.buf[i] = .{}; self.buf[i] = .{};

View file

@ -175,8 +175,8 @@ pub fn run(
}, },
.cap_unicode => { .cap_unicode => {
log.info("unicode capability detected", .{}); log.info("unicode capability detected", .{});
loop.vaxis.caps.unicode = true; loop.vaxis.caps.unicode = .unicode;
loop.vaxis.screen.unicode = true; loop.vaxis.screen.width_method = .unicode;
}, },
.cap_da1 => { .cap_da1 => {
std.Thread.Futex.wake(&loop.vaxis.query_futex, 10); std.Thread.Futex.wake(&loop.vaxis.query_futex, 10);

26
src/Unicode.zig Normal file
View file

@ -0,0 +1,26 @@
const std = @import("std");
const grapheme = @import("grapheme");
/// A thin wrapper around zg data
const Unicode = @This();
grapheme_data: grapheme.GraphemeData,
/// initialize all unicode data vaxis may possibly need
pub fn init(alloc: std.mem.Allocator) !Unicode {
const grapheme_data = try grapheme.GraphemeData.init(alloc);
return .{
.grapheme_data = grapheme_data,
};
}
/// free all data
pub fn deinit(self: *Unicode) void {
self.grapheme_data.deinit();
}
/// creates a grapheme iterator based on str
pub fn graphemeIterator(self: *const Unicode, str: []const u8) grapheme.Iterator {
return grapheme.Iterator.init(str, &self.grapheme_data);
}

View file

@ -29,7 +29,7 @@ pub const Capabilities = struct {
kitty_keyboard: bool = false, kitty_keyboard: bool = false,
kitty_graphics: bool = false, kitty_graphics: bool = false,
rgb: bool = false, rgb: bool = false,
unicode: bool = false, unicode: gwidth.Method = .wcwidth,
}; };
pub const Options = struct {}; pub const Options = struct {};
@ -63,18 +63,21 @@ query_futex: atomic.Value(u32) = atomic.Value(u32).init(0),
// images // images
next_img_id: u32 = 1, next_img_id: u32 = 1,
unicode: Unicode,
// statistics // statistics
renders: usize = 0, renders: usize = 0,
render_dur: i128 = 0, render_dur: i128 = 0,
render_timer: std.time.Timer, render_timer: std.time.Timer,
/// Initialize Vaxis with runtime options /// Initialize Vaxis with runtime options
pub fn init(_: Options) !Vaxis { pub fn init(alloc: std.mem.Allocator, _: Options) !Vaxis {
return .{ return .{
.tty = null, .tty = null,
.screen = .{}, .screen = .{},
.screen_last = .{}, .screen_last = .{},
.render_timer = try std.time.Timer.start(), .render_timer = try std.time.Timer.start(),
.unicode = try Unicode.init(alloc),
}; };
} }
@ -111,6 +114,7 @@ pub fn deinit(self: *Vaxis, alloc: ?std.mem.Allocator) void {
log.debug("total renders = {d}", .{self.renders}); log.debug("total renders = {d}", .{self.renders});
log.debug("microseconds per render = {d}", .{tpr}); log.debug("microseconds per render = {d}", .{tpr});
} }
self.unicode.deinit();
} }
/// resize allocates a slice of cells equal to the number of cells /// resize allocates a slice of cells equal to the number of cells
@ -119,8 +123,8 @@ pub fn deinit(self: *Vaxis, alloc: ?std.mem.Allocator) void {
pub fn resize(self: *Vaxis, alloc: std.mem.Allocator, winsize: Winsize) !void { pub fn resize(self: *Vaxis, alloc: std.mem.Allocator, winsize: Winsize) !void {
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); self.screen = try Screen.init(alloc, winsize, &self.unicode);
self.screen.unicode = self.caps.unicode; self.screen.width_method = 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
@ -201,7 +205,7 @@ pub fn queryTerminal(self: *Vaxis) !void {
if (self.caps.kitty_keyboard) { if (self.caps.kitty_keyboard) {
try self.enableKittyKeyboard(.{}); try self.enableKittyKeyboard(.{});
} }
if (self.caps.unicode) { if (self.caps.unicode == .unicode) {
_ = try tty.write(ctlseqs.unicode_set); _ = try tty.write(ctlseqs.unicode_set);
} }
} }
@ -256,7 +260,7 @@ pub fn render(self: *Vaxis) !void {
const w = blk: { const w = blk: {
if (cell.char.width != 0) break :blk cell.char.width; if (cell.char.width != 0) break :blk cell.char.width;
const method: gwidth.Method = if (self.caps.unicode) .unicode else .wcwidth; const method: gwidth.Method = self.caps.unicode;
const width = gwidth.gwidth(cell.char.grapheme, method) catch 1; const width = gwidth.gwidth(cell.char.grapheme, method) catch 1;
break :blk @max(1, width); break :blk @max(1, width);
}; };

View file

@ -1,6 +1,4 @@
const std = @import("std"); const std = @import("std");
const ziglyph = @import("ziglyph");
const GraphemeIterator = ziglyph.GraphemeIterator;
const Screen = @import("Screen.zig"); const Screen = @import("Screen.zig");
const Cell = @import("Cell.zig"); const Cell = @import("Cell.zig");
@ -202,8 +200,7 @@ pub fn clear(self: Window) void {
/// returns the width of the grapheme. This depends on the terminal capabilities /// returns the width of the grapheme. This depends on the terminal capabilities
pub fn gwidth(self: Window, str: []const u8) usize { pub fn gwidth(self: Window, str: []const u8) usize {
const m: gw.Method = if (self.screen.unicode) .unicode else .wcwidth; return gw.gwidth(str, self.screen.width_method) catch 1;
return gw.gwidth(str, m) catch 1;
} }
/// fills the window with the provided cell /// fills the window with the provided cell
@ -270,7 +267,7 @@ pub fn print(self: Window, segments: []Segment, opts: PrintOptions) !PrintResult
.grapheme => { .grapheme => {
var col: usize = 0; var col: usize = 0;
const overflow: bool = blk: for (segments) |segment| { const overflow: bool = blk: for (segments) |segment| {
var iter = GraphemeIterator.init(segment.text); var iter = self.screen.unicode.graphemeIterator(segment.text);
while (iter.next()) |grapheme| { while (iter.next()) |grapheme| {
if (row >= self.height) break :blk true; if (row >= self.height) break :blk true;
const s = grapheme.slice(segment.text); const s = grapheme.slice(segment.text);
@ -353,7 +350,7 @@ pub fn print(self: Window, segments: []Segment, opts: PrintOptions) !PrintResult
else else
word; word;
defer soft_wrapped = false; defer soft_wrapped = false;
var iter = GraphemeIterator.init(printed_word); var iter = self.screen.unicode.graphemeIterator(segment.text);
while (iter.next()) |grapheme| { while (iter.next()) |grapheme| {
const s = grapheme.slice(printed_word); const s = grapheme.slice(printed_word);
const w = self.gwidth(s); const w = self.gwidth(s);
@ -405,7 +402,7 @@ pub fn print(self: Window, segments: []Segment, opts: PrintOptions) !PrintResult
.none => { .none => {
var col: usize = 0; var col: usize = 0;
const overflow: bool = blk: for (segments) |segment| { const overflow: bool = blk: for (segments) |segment| {
var iter = GraphemeIterator.init(segment.text); var iter = self.screen.unicode.graphemeIterator(segment.text);
while (iter.next()) |grapheme| { while (iter.next()) |grapheme| {
if (col >= self.width) break :blk true; if (col >= self.width) break :blk true;
const s = grapheme.slice(segment.text); const s = grapheme.slice(segment.text);

View file

@ -22,8 +22,8 @@ pub const widgets = @import("widgets.zig");
pub const gwidth = @import("gwidth.zig"); pub const gwidth = @import("gwidth.zig");
/// Initialize a Vaxis application. /// Initialize a Vaxis application.
pub fn init(opts: Vaxis.Options) !Vaxis { pub fn init(alloc: std.mem.Allocator, opts: Vaxis.Options) !Vaxis {
return Vaxis.init(opts); return Vaxis.init(alloc, opts);
} }
test { test {

View file

@ -3,8 +3,8 @@ const assert = std.debug.assert;
const Key = @import("../Key.zig"); const Key = @import("../Key.zig");
const Cell = @import("../Cell.zig"); const Cell = @import("../Cell.zig");
const Window = @import("../Window.zig"); const Window = @import("../Window.zig");
const GraphemeIterator = @import("ziglyph").GraphemeIterator;
const GapBuffer = @import("gap_buffer").GapBuffer; const GapBuffer = @import("gap_buffer").GapBuffer;
const Unicode = @import("../Unicode.zig");
const log = std.log.scoped(.text_input); const log = std.log.scoped(.text_input);
@ -31,9 +31,12 @@ prev_cursor_idx: usize = 0,
/// approximate distance from an edge before we scroll /// approximate distance from an edge before we scroll
scroll_offset: usize = 4, scroll_offset: usize = 4,
pub fn init(alloc: std.mem.Allocator) TextInput { unicode: *const Unicode,
pub fn init(alloc: std.mem.Allocator, unicode: *const Unicode) TextInput {
return TextInput{ return TextInput{
.buf = GapBuffer(u8).init(alloc), .buf = GapBuffer(u8).init(alloc),
.unicode = unicode,
}; };
} }
@ -73,7 +76,7 @@ pub fn update(self: *TextInput, event: Event) !void {
/// insert text at the cursor position /// insert text at the cursor position
pub fn insertSliceAtCursor(self: *TextInput, data: []const u8) !void { pub fn insertSliceAtCursor(self: *TextInput, data: []const u8) !void {
var iter = GraphemeIterator.init(data); var iter = self.unicode.graphemeIterator(data);
var byte_offset_to_cursor = self.byteOffsetToCursor(); var byte_offset_to_cursor = self.byteOffsetToCursor();
while (iter.next()) |text| { while (iter.next()) |text| {
try self.buf.insertSliceBefore(byte_offset_to_cursor, text.slice(data)); try self.buf.insertSliceBefore(byte_offset_to_cursor, text.slice(data));
@ -101,7 +104,7 @@ pub fn sliceToCursor(self: *TextInput, buf: []u8) []const u8 {
/// calculates the display width from the draw_offset to the cursor /// calculates the display width from the draw_offset to the cursor
fn widthToCursor(self: *TextInput, win: Window) usize { fn widthToCursor(self: *TextInput, win: Window) usize {
var width: usize = 0; var width: usize = 0;
var first_iter = GraphemeIterator.init(self.buf.items); var first_iter = self.unicode.graphemeIterator(self.buf.items);
var i: usize = 0; var i: usize = 0;
while (first_iter.next()) |grapheme| { while (first_iter.next()) |grapheme| {
defer i += 1; defer i += 1;
@ -109,18 +112,18 @@ fn widthToCursor(self: *TextInput, win: Window) usize {
continue; continue;
} }
if (i == self.cursor_idx) return width; if (i == self.cursor_idx) return width;
const g = grapheme.slice(self.buf.items); const g = grapheme.bytes(self.buf.items);
width += win.gwidth(g); width += win.gwidth(g);
} }
const second_half = self.buf.secondHalf(); const second_half = self.buf.secondHalf();
var second_iter = GraphemeIterator.init(second_half); var second_iter = self.unicode.graphemeIterator(second_half);
while (second_iter.next()) |grapheme| { while (second_iter.next()) |grapheme| {
defer i += 1; defer i += 1;
if (i < self.draw_offset) { if (i < self.draw_offset) {
continue; continue;
} }
if (i == self.cursor_idx) return width; if (i == self.cursor_idx) return width;
const g = grapheme.slice(second_half); const g = grapheme.bytes(second_half);
width += win.gwidth(g); width += win.gwidth(g);
} }
return width; return width;
@ -141,7 +144,7 @@ pub fn draw(self: *TextInput, win: Window) void {
// assumption!! the gap is never within a grapheme // assumption!! the gap is never within a grapheme
// one way to _ensure_ this is to move the gap... but that's a cost we probably don't want to pay. // one way to _ensure_ this is to move the gap... but that's a cost we probably don't want to pay.
var first_iter = GraphemeIterator.init(self.buf.items); var first_iter = self.unicode.graphemeIterator(self.buf.items);
var col: usize = 0; var col: usize = 0;
var i: usize = 0; var i: usize = 0;
while (first_iter.next()) |grapheme| { while (first_iter.next()) |grapheme| {
@ -149,7 +152,7 @@ pub fn draw(self: *TextInput, win: Window) void {
i += 1; i += 1;
continue; continue;
} }
const g = grapheme.slice(self.buf.items); const g = grapheme.bytes(self.buf.items);
const w = win.gwidth(g); const w = win.gwidth(g);
if (col + w >= win.width) { if (col + w >= win.width) {
win.writeCell(win.width - 1, 0, .{ .char = ellipsis }); win.writeCell(win.width - 1, 0, .{ .char = ellipsis });
@ -166,13 +169,13 @@ pub fn draw(self: *TextInput, win: Window) void {
if (i == self.cursor_idx) self.prev_cursor_col = col; if (i == self.cursor_idx) self.prev_cursor_col = col;
} }
const second_half = self.buf.secondHalf(); const second_half = self.buf.secondHalf();
var second_iter = GraphemeIterator.init(second_half); var second_iter = self.unicode.graphemeIterator(second_half);
while (second_iter.next()) |grapheme| { while (second_iter.next()) |grapheme| {
if (i < self.draw_offset) { if (i < self.draw_offset) {
i += 1; i += 1;
continue; continue;
} }
const g = grapheme.slice(second_half); const g = grapheme.bytes(second_half);
const w = win.gwidth(g); const w = win.gwidth(g);
if (col + w > win.width) { if (col + w > win.width) {
win.writeCell(win.width - 1, 0, .{ .char = ellipsis }); win.writeCell(win.width - 1, 0, .{ .char = ellipsis });
@ -223,7 +226,7 @@ fn reset(self: *TextInput) void {
pub fn byteOffsetToCursor(self: TextInput) usize { pub fn byteOffsetToCursor(self: TextInput) usize {
// assumption! the gap is never in the middle of a grapheme // assumption! the gap is never in the middle of a grapheme
// one way to _ensure_ this is to move the gap... but that's a cost we probably don't want to pay. // one way to _ensure_ this is to move the gap... but that's a cost we probably don't want to pay.
var iter = GraphemeIterator.init(self.buf.items); var iter = self.unicode.graphemeIterator(self.buf.items);
var offset: usize = 0; var offset: usize = 0;
var i: usize = 0; var i: usize = 0;
while (iter.next()) |grapheme| { while (iter.next()) |grapheme| {
@ -231,7 +234,7 @@ pub fn byteOffsetToCursor(self: TextInput) usize {
offset += grapheme.len; offset += grapheme.len;
i += 1; i += 1;
} else { } else {
var second_iter = GraphemeIterator.init(self.buf.secondHalf()); var second_iter = self.unicode.graphemeIterator(self.buf.secondHalf());
while (second_iter.next()) |grapheme| { while (second_iter.next()) |grapheme| {
if (i == self.cursor_idx) break; if (i == self.cursor_idx) break;
offset += grapheme.len; offset += grapheme.len;
@ -257,7 +260,7 @@ fn deleteToStart(self: *TextInput) !void {
fn deleteBeforeCursor(self: *TextInput) !void { fn deleteBeforeCursor(self: *TextInput) !void {
// assumption! the gap is never in the middle of a grapheme // assumption! the gap is never in the middle of a grapheme
// one way to _ensure_ this is to move the gap... but that's a cost we probably don't want to pay. // one way to _ensure_ this is to move the gap... but that's a cost we probably don't want to pay.
var iter = GraphemeIterator.init(self.buf.items); var iter = self.unicode.graphemeIterator(self.buf.items);
var offset: usize = 0; var offset: usize = 0;
var i: usize = 1; var i: usize = 1;
while (iter.next()) |grapheme| { while (iter.next()) |grapheme| {
@ -270,7 +273,7 @@ fn deleteBeforeCursor(self: *TextInput) !void {
offset += grapheme.len; offset += grapheme.len;
i += 1; i += 1;
} else { } else {
var second_iter = GraphemeIterator.init(self.buf.secondHalf()); var second_iter = self.unicode.graphemeIterator(self.buf.secondHalf());
while (second_iter.next()) |grapheme| { while (second_iter.next()) |grapheme| {
if (i == self.cursor_idx) { if (i == self.cursor_idx) {
try self.buf.replaceRangeBefore(offset, grapheme.len, &.{}); try self.buf.replaceRangeBefore(offset, grapheme.len, &.{});
@ -287,7 +290,7 @@ fn deleteBeforeCursor(self: *TextInput) !void {
fn deleteAtCursor(self: *TextInput) !void { fn deleteAtCursor(self: *TextInput) !void {
// assumption! the gap is never in the middle of a grapheme // assumption! the gap is never in the middle of a grapheme
// one way to _ensure_ this is to move the gap... but that's a cost we probably don't want to pay. // one way to _ensure_ this is to move the gap... but that's a cost we probably don't want to pay.
var iter = GraphemeIterator.init(self.buf.items); var iter = self.unicode.graphemeIterator(self.buf.items);
var offset: usize = 0; var offset: usize = 0;
var i: usize = 1; var i: usize = 1;
while (iter.next()) |grapheme| { while (iter.next()) |grapheme| {
@ -299,7 +302,7 @@ fn deleteAtCursor(self: *TextInput) !void {
offset += grapheme.len; offset += grapheme.len;
i += 1; i += 1;
} else { } else {
var second_iter = GraphemeIterator.init(self.buf.secondHalf()); var second_iter = self.unicode.graphemeIterator(self.buf.secondHalf());
while (second_iter.next()) |grapheme| { while (second_iter.next()) |grapheme| {
if (i == self.cursor_idx + 1) { if (i == self.cursor_idx + 1) {
try self.buf.replaceRangeAfter(offset, grapheme.len, &.{}); try self.buf.replaceRangeAfter(offset, grapheme.len, &.{});