diff --git a/src/Window.zig b/src/Window.zig index eedd3b6..46dfbc3 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -108,54 +108,131 @@ pub fn showCursor(self: Window, col: usize, row: usize) void { self.screen.cursor_col = col + self.x_off; } +/// Options to use when printing Segments to a window +pub const PrintOptions = struct { + /// vertical offset to start printing at + row_offset: usize = 0, + + /// wrap behavior for printing + wrap: enum { + /// wrap at grapheme boundaries + grapheme, + /// wrap at word boundaries + word, + /// stop printing after one line + none, + } = .grapheme, +}; + +/// prints segments to the window. returns true if the text overflowed with the +/// given wrap strategy and size. +pub fn print(self: Window, segments: []Segment, opts: PrintOptions) !bool { + var row = opts.row_offset; + switch (opts.wrap) { + .grapheme => { + var col: usize = 0; + for (segments) |segment| { + var iter = GraphemeIterator.init(segment.text); + while (iter.next()) |grapheme| { + if (col >= self.width) { + row += 1; + col = 0; + } + if (row >= self.height) return true; + const s = grapheme.slice(segment.text); + if (std.mem.eql(u8, s, "\n")) { + row += 1; + col = 0; + continue; + } + const w = self.gwidth(s); + self.writeCell(col, row, .{ + .char = .{ + .grapheme = s, + .width = w, + }, + .style = segment.style, + .link = segment.link, + }); + col += w; + } + } + }, + .word => { + 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 (word.bytes[0] == '\r' or word.bytes[0] == '\n') { + 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; + } + if (row >= self.height) return 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; + } + } + } + }, + .none => { + var col: usize = 0; + for (segments) |segment| { + var iter = GraphemeIterator.init(segment.text); + while (iter.next()) |grapheme| { + if (col >= self.width) return true; + const s = grapheme.slice(segment.text); + if (std.mem.eql(u8, s, "\n")) return true; + const w = self.gwidth(s); + self.writeCell(col, row, .{ + .char = .{ + .grapheme = s, + .width = w, + }, + .style = segment.style, + .link = segment.link, + }); + col += w; + } + } + }, + } + return false; +} + /// 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 (word.bytes[0] == '\r' or word.bytes[0] == '\n') { - 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; - } - } - } + return self.print(segments, .{ .wrap = .word }); } test "Window size set" {