window: implement wrap and introduce Segment type

Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
This commit is contained in:
Tim Culverhouse 2024-01-31 19:13:56 -06:00
parent f3cf7bcfcd
commit 3798a8ede3
4 changed files with 75 additions and 0 deletions

View file

@ -22,6 +22,7 @@ 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: bool = false, unicode: bool = false,
mouse_shape: Shape = .default, mouse_shape: Shape = .default,

View file

@ -1,7 +1,11 @@
const std = @import("std"); const std = @import("std");
const ziglyph = @import("ziglyph");
const WordIterator = ziglyph.WordIterator;
const GraphemeIterator = ziglyph.GraphemeIterator;
const Screen = @import("Screen.zig"); const Screen = @import("Screen.zig");
const Cell = @import("cell.zig").Cell; const Cell = @import("cell.zig").Cell;
const Segment = @import("cell.zig").Segment;
const gw = @import("gwidth.zig"); const gw = @import("gwidth.zig");
const log = std.log.scoped(.window); const log = std.log.scoped(.window);
@ -104,6 +108,68 @@ pub fn showCursor(self: Window, col: usize, row: usize) void {
self.screen.cursor_col = col + self.x_off; self.screen.cursor_col = col + self.x_off;
} }
/// prints text in the window with simple word wrapping.
pub fn wrap(self: Window, segments: []Segment) !void {
// pub fn wrap(self: Window, str: []const u8) !void {
var row: usize = 0;
var col: usize = 0;
var wrapped: bool = false;
for (segments) |segment| {
var word_iter = try WordIterator.init(segment.text);
while (word_iter.next()) |word| {
// break lines when we need
if (isLineBreak(word.bytes)) {
row += 1;
col = 0;
wrapped = false;
continue;
}
// break lines when we can't fit this word, and the word isn't longer
// than our width
const word_width = self.gwidth(word.bytes);
if (word_width + col >= self.width and word_width < self.width) {
row += 1;
col = 0;
wrapped = true;
}
// don't print whitespace in the first column, unless we had a hard
// break
if (col == 0 and std.mem.eql(u8, word.bytes, " ") and wrapped) continue;
var iter = GraphemeIterator.init(word.bytes);
while (iter.next()) |grapheme| {
if (col >= self.width) {
row += 1;
col = 0;
wrapped = true;
}
const s = grapheme.slice(word.bytes);
const w = self.gwidth(s);
self.writeCell(col, row, .{
.char = .{
.grapheme = s,
.width = w,
},
.style = segment.style,
.link = segment.link,
});
col += w;
}
}
}
}
fn isLineBreak(str: []const u8) bool {
if (std.mem.eql(u8, str, "\r\n")) {
return true;
} else if (std.mem.eql(u8, str, "\r")) {
return true;
} else if (std.mem.eql(u8, str, "\n")) {
return true;
} else {
return false;
}
}
test "Window size set" { test "Window size set" {
var parent = Window{ var parent = Window{
.x_off = 0, .x_off = 0,

View file

@ -7,6 +7,13 @@ pub const Cell = struct {
image: ?Image.Placement = null, image: ?Image.Placement = null,
}; };
/// Segment is a contiguous run of text that has a constant style
pub const Segment = struct {
text: []const u8,
style: Style = .{},
link: Hyperlink = .{},
};
pub const Character = struct { pub const Character = struct {
grapheme: []const u8 = " ", grapheme: []const u8 = " ",
/// width should only be provided when the application is sure the terminal /// width should only be provided when the application is sure the terminal

View file

@ -4,6 +4,7 @@ pub const Options = @import("Options.zig");
const cell = @import("cell.zig"); const cell = @import("cell.zig");
pub const Cell = cell.Cell; pub const Cell = cell.Cell;
pub const Style = cell.Style; pub const Style = cell.Style;
pub const Segment = cell.Segment;
pub const Key = @import("Key.zig"); pub const Key = @import("Key.zig");
pub const Mouse = @import("Mouse.zig"); pub const Mouse = @import("Mouse.zig");