From 3798a8ede3860a547f5917f1d52a9c3901e10ede Mon Sep 17 00:00:00 2001 From: Tim Culverhouse Date: Wed, 31 Jan 2024 19:13:56 -0600 Subject: [PATCH] window: implement wrap and introduce Segment type Signed-off-by: Tim Culverhouse --- src/Screen.zig | 1 + src/Window.zig | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/cell.zig | 7 ++++++ src/main.zig | 1 + 4 files changed, 75 insertions(+) diff --git a/src/Screen.zig b/src/Screen.zig index 1a10eeb..e5f35ff 100644 --- a/src/Screen.zig +++ b/src/Screen.zig @@ -22,6 +22,7 @@ cursor_row: usize = 0, cursor_col: usize = 0, cursor_vis: bool = false, +/// true when we measure cells with unicode unicode: bool = false, mouse_shape: Shape = .default, diff --git a/src/Window.zig b/src/Window.zig index cd5bc12..c54a056 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -1,7 +1,11 @@ const std = @import("std"); +const ziglyph = @import("ziglyph"); +const WordIterator = ziglyph.WordIterator; +const GraphemeIterator = ziglyph.GraphemeIterator; const Screen = @import("Screen.zig"); const Cell = @import("cell.zig").Cell; +const Segment = @import("cell.zig").Segment; const gw = @import("gwidth.zig"); 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; } +/// 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" { var parent = Window{ .x_off = 0, diff --git a/src/cell.zig b/src/cell.zig index dd662b6..fba30bc 100644 --- a/src/cell.zig +++ b/src/cell.zig @@ -7,6 +7,13 @@ pub const Cell = struct { 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 { grapheme: []const u8 = " ", /// width should only be provided when the application is sure the terminal diff --git a/src/main.zig b/src/main.zig index 5903ad3..0263433 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4,6 +4,7 @@ pub const Options = @import("Options.zig"); const cell = @import("cell.zig"); pub const Cell = cell.Cell; pub const Style = cell.Style; +pub const Segment = cell.Segment; pub const Key = @import("Key.zig"); pub const Mouse = @import("Mouse.zig");