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

View file

@ -20,5 +20,9 @@
.url = "git+https://github.com/jinzhongjia/znvim#7927b8042872d5fa5f30862302bea1290d372d4f",
.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();
// 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
// choose to pass a null allocator to save some exit time.
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
// store the contents of the input
var text_input = TextInput.init(alloc);
var text_input = TextInput.init(alloc, &vx.unicode);
defer text_input.deinit();
// 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 Image = @import("Image.zig");
const Winsize = @import("Tty.zig").Winsize;
const Unicode = @import("Unicode.zig");
const Method = @import("gwidth.zig").Method;
const log = std.log.scoped(.screen);
@ -22,13 +24,14 @@ cursor_row: usize = 0,
cursor_col: usize = 0,
cursor_vis: bool = false,
/// true when we measure cells with unicode
unicode: bool = false,
unicode: *const Unicode = undefined,
width_method: Method = .wcwidth,
mouse_shape: Shape = .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 h = winsize.rows;
var self = Screen{
@ -37,6 +40,7 @@ pub fn init(alloc: std.mem.Allocator, winsize: Winsize) !Screen {
.height = h,
.width_pix = winsize.x_pixel,
.height_pix = winsize.y_pixel,
.unicode = unicode,
};
for (self.buf, 0..) |_, i| {
self.buf[i] = .{};

View file

@ -175,8 +175,8 @@ pub fn run(
},
.cap_unicode => {
log.info("unicode capability detected", .{});
loop.vaxis.caps.unicode = true;
loop.vaxis.screen.unicode = true;
loop.vaxis.caps.unicode = .unicode;
loop.vaxis.screen.width_method = .unicode;
},
.cap_da1 => {
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_graphics: bool = false,
rgb: bool = false,
unicode: bool = false,
unicode: gwidth.Method = .wcwidth,
};
pub const Options = struct {};
@ -63,18 +63,21 @@ query_futex: atomic.Value(u32) = atomic.Value(u32).init(0),
// images
next_img_id: u32 = 1,
unicode: Unicode,
// statistics
renders: usize = 0,
render_dur: i128 = 0,
render_timer: std.time.Timer,
/// Initialize Vaxis with runtime options
pub fn init(_: Options) !Vaxis {
pub fn init(alloc: std.mem.Allocator, _: Options) !Vaxis {
return .{
.tty = null,
.screen = .{},
.screen_last = .{},
.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("microseconds per render = {d}", .{tpr});
}
self.unicode.deinit();
}
/// 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 {
log.debug("resizing screen: width={d} height={d}", .{ winsize.cols, winsize.rows });
self.screen.deinit(alloc);
self.screen = try Screen.init(alloc, winsize);
self.screen.unicode = self.caps.unicode;
self.screen = try Screen.init(alloc, winsize, &self.unicode);
self.screen.width_method = self.caps.unicode;
// try self.screen.int(alloc, winsize.cols, winsize.rows);
// we only init our current screen. This has the effect of redrawing
// every cell
@ -201,7 +205,7 @@ pub fn queryTerminal(self: *Vaxis) !void {
if (self.caps.kitty_keyboard) {
try self.enableKittyKeyboard(.{});
}
if (self.caps.unicode) {
if (self.caps.unicode == .unicode) {
_ = try tty.write(ctlseqs.unicode_set);
}
}
@ -256,7 +260,7 @@ pub fn render(self: *Vaxis) !void {
const w = blk: {
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;
break :blk @max(1, width);
};

View file

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

View file

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

View file

@ -3,8 +3,8 @@ const assert = std.debug.assert;
const Key = @import("../Key.zig");
const Cell = @import("../Cell.zig");
const Window = @import("../Window.zig");
const GraphemeIterator = @import("ziglyph").GraphemeIterator;
const GapBuffer = @import("gap_buffer").GapBuffer;
const Unicode = @import("../Unicode.zig");
const log = std.log.scoped(.text_input);
@ -31,9 +31,12 @@ prev_cursor_idx: usize = 0,
/// approximate distance from an edge before we scroll
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{
.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
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();
while (iter.next()) |text| {
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
fn widthToCursor(self: *TextInput, win: Window) usize {
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;
while (first_iter.next()) |grapheme| {
defer i += 1;
@ -109,18 +112,18 @@ fn widthToCursor(self: *TextInput, win: Window) usize {
continue;
}
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);
}
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| {
defer i += 1;
if (i < self.draw_offset) {
continue;
}
if (i == self.cursor_idx) return width;
const g = grapheme.slice(second_half);
const g = grapheme.bytes(second_half);
width += win.gwidth(g);
}
return width;
@ -141,7 +144,7 @@ pub fn draw(self: *TextInput, win: Window) void {
// 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.
var first_iter = GraphemeIterator.init(self.buf.items);
var first_iter = self.unicode.graphemeIterator(self.buf.items);
var col: usize = 0;
var i: usize = 0;
while (first_iter.next()) |grapheme| {
@ -149,7 +152,7 @@ pub fn draw(self: *TextInput, win: Window) void {
i += 1;
continue;
}
const g = grapheme.slice(self.buf.items);
const g = grapheme.bytes(self.buf.items);
const w = win.gwidth(g);
if (col + w >= win.width) {
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;
}
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| {
if (i < self.draw_offset) {
i += 1;
continue;
}
const g = grapheme.slice(second_half);
const g = grapheme.bytes(second_half);
const w = win.gwidth(g);
if (col + w > win.width) {
win.writeCell(win.width - 1, 0, .{ .char = ellipsis });
@ -223,7 +226,7 @@ fn reset(self: *TextInput) void {
pub fn byteOffsetToCursor(self: TextInput) usize {
// 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.
var iter = GraphemeIterator.init(self.buf.items);
var iter = self.unicode.graphemeIterator(self.buf.items);
var offset: usize = 0;
var i: usize = 0;
while (iter.next()) |grapheme| {
@ -231,7 +234,7 @@ pub fn byteOffsetToCursor(self: TextInput) usize {
offset += grapheme.len;
i += 1;
} else {
var second_iter = GraphemeIterator.init(self.buf.secondHalf());
var second_iter = self.unicode.graphemeIterator(self.buf.secondHalf());
while (second_iter.next()) |grapheme| {
if (i == self.cursor_idx) break;
offset += grapheme.len;
@ -257,7 +260,7 @@ fn deleteToStart(self: *TextInput) !void {
fn deleteBeforeCursor(self: *TextInput) !void {
// 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.
var iter = GraphemeIterator.init(self.buf.items);
var iter = self.unicode.graphemeIterator(self.buf.items);
var offset: usize = 0;
var i: usize = 1;
while (iter.next()) |grapheme| {
@ -270,7 +273,7 @@ fn deleteBeforeCursor(self: *TextInput) !void {
offset += grapheme.len;
i += 1;
} else {
var second_iter = GraphemeIterator.init(self.buf.secondHalf());
var second_iter = self.unicode.graphemeIterator(self.buf.secondHalf());
while (second_iter.next()) |grapheme| {
if (i == self.cursor_idx) {
try self.buf.replaceRangeBefore(offset, grapheme.len, &.{});
@ -287,7 +290,7 @@ fn deleteBeforeCursor(self: *TextInput) !void {
fn deleteAtCursor(self: *TextInput) !void {
// 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.
var iter = GraphemeIterator.init(self.buf.items);
var iter = self.unicode.graphemeIterator(self.buf.items);
var offset: usize = 0;
var i: usize = 1;
while (iter.next()) |grapheme| {
@ -299,7 +302,7 @@ fn deleteAtCursor(self: *TextInput) !void {
offset += grapheme.len;
i += 1;
} else {
var second_iter = GraphemeIterator.init(self.buf.secondHalf());
var second_iter = self.unicode.graphemeIterator(self.buf.secondHalf());
while (second_iter.next()) |grapheme| {
if (i == self.cursor_idx + 1) {
try self.buf.replaceRangeAfter(offset, grapheme.len, &.{});