window: change print return value to struct, add commit option

Return more information from `print`. Add `commit` option which does a
"dry run" of printing and returns the size of the wrapped text.
This commit is contained in:
Tim Culverhouse 2024-04-19 16:12:52 -08:00
parent cda3e1de79
commit b93a5d2c72

View file

@ -250,23 +250,30 @@ pub const PrintOptions = struct {
/// stop printing after one line /// stop printing after one line
none, none,
} = .grapheme, } = .grapheme,
/// when true, print will write to the screen for rendering. When false,
/// nothing is written. The return value describes the size of the wrapped
/// text
commit: bool = true,
};
pub const PrintResult = struct {
col: usize,
row: usize,
overflow: bool,
}; };
/// prints segments to the window. returns true if the text overflowed with the /// prints segments to the window. returns true if the text overflowed with the
/// given wrap strategy and size. /// given wrap strategy and size.
pub fn print(self: Window, segments: []Segment, opts: PrintOptions) !bool { pub fn print(self: Window, segments: []Segment, opts: PrintOptions) !PrintResult {
var row = opts.row_offset; var row = opts.row_offset;
switch (opts.wrap) { switch (opts.wrap) {
.grapheme => { .grapheme => {
var col: usize = 0; var col: usize = 0;
for (segments) |segment| { const overflow: bool = blk: for (segments) |segment| {
var iter = GraphemeIterator.init(segment.text); var iter = GraphemeIterator.init(segment.text);
while (iter.next()) |grapheme| { while (iter.next()) |grapheme| {
if (col >= self.width) { if (row >= self.height) break :blk true;
row += 1;
col = 0;
}
if (row >= self.height) return true;
const s = grapheme.slice(segment.text); const s = grapheme.slice(segment.text);
if (std.mem.eql(u8, s, "\n")) { if (std.mem.eql(u8, s, "\n")) {
row += 1; row += 1;
@ -275,7 +282,7 @@ pub fn print(self: Window, segments: []Segment, opts: PrintOptions) !bool {
} }
const w = self.gwidth(s); const w = self.gwidth(s);
if (w == 0) continue; if (w == 0) continue;
self.writeCell(col, row, .{ if (opts.commit) self.writeCell(col, row, .{
.char = .{ .char = .{
.grapheme = s, .grapheme = s,
.width = w, .width = w,
@ -284,68 +291,88 @@ pub fn print(self: Window, segments: []Segment, opts: PrintOptions) !bool {
.link = segment.link, .link = segment.link,
}); });
col += w; col += w;
if (col >= self.width) {
row += 1;
col = 0;
}
} }
} } else false;
return .{
.row = row,
.col = col,
.overflow = overflow,
};
}, },
.word => { .word => {
var col: usize = 0; var col: usize = 0;
var wrapped: bool = false; const overflow: bool = blk: for (segments) |segment| {
for (segments) |segment| { var line_iter = std.mem.tokenizeAny(u8, segment.text, "\r\n");
var word_iter = try WordIterator.init(segment.text); while (line_iter.next()) |line| {
while (word_iter.next()) |word| { col = 0;
// break lines when we need defer row += 1;
if (word.bytes[0] == '\r' or word.bytes[0] == '\n') { var word_iter = std.mem.tokenizeScalar(u8, line, ' ');
row += 1; while (word_iter.next()) |word| {
col = 0; const width = self.gwidth(word);
wrapped = false; if (width == 0) continue;
continue; // only wrap when the word can fit by itself on a
} // line
// break lines when we can't fit this word, and the word isn't longer if (width + col + 1 > self.width and width < self.width) {
// than our width
const word_width = self.gwidth(word.bytes);
if (word_width == 0) continue;
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; row += 1;
col = 0; col = 0;
wrapped = true;
} }
const s = grapheme.slice(word.bytes); if (row >= self.height)
const w = self.gwidth(s); break :blk true;
self.writeCell(col, row, .{ if (col > 0) {
.char = .{ if (opts.commit) self.writeCell(col, row, .{
.grapheme = s, .char = .{
.width = w, .grapheme = " ",
}, .width = 1,
.style = segment.style, },
.link = segment.link, .style = segment.style,
}); .link = segment.link,
col += w; });
col += 1;
}
var iter = GraphemeIterator.init(word);
while (iter.next()) |grapheme| {
const s = grapheme.slice(word);
const w = self.gwidth(s);
if (opts.commit) self.writeCell(col, row, .{
.char = .{
.grapheme = s,
.width = w,
},
.style = segment.style,
.link = segment.link,
});
col += w;
if (col >= self.width) {
row += 1;
col = 0;
}
}
} }
} }
} } else false;
return .{
// remove last row counter
.row = row - 1,
.col = col,
.overflow = overflow,
};
}, },
.none => { .none => {
var col: usize = 0; var col: usize = 0;
for (segments) |segment| { const overflow: bool = blk: for (segments) |segment| {
var iter = GraphemeIterator.init(segment.text); var iter = GraphemeIterator.init(segment.text);
while (iter.next()) |grapheme| { while (iter.next()) |grapheme| {
if (col >= self.width) return true; if (col >= self.width) break :blk true;
const s = grapheme.slice(segment.text); const s = grapheme.slice(segment.text);
if (std.mem.eql(u8, s, "\n")) return true; if (std.mem.eql(u8, s, "\n")) break :blk true;
const w = self.gwidth(s); const w = self.gwidth(s);
if (w == 0) continue; if (w == 0) continue;
self.writeCell(col, row, .{ if (opts.commit) self.writeCell(col, row, .{
.char = .{ .char = .{
.grapheme = s, .grapheme = s,
.width = w, .width = w,
@ -355,7 +382,12 @@ pub fn print(self: Window, segments: []Segment, opts: PrintOptions) !bool {
}); });
col += w; col += w;
} }
} } else false;
return .{
.row = row,
.col = col,
.overflow = overflow,
};
}, },
} }
return false; return false;
@ -450,3 +482,147 @@ test "Window size nested offsets" {
try std.testing.expectEqual(11, ch.x_off); try std.testing.expectEqual(11, ch.x_off);
try std.testing.expectEqual(11, ch.y_off); try std.testing.expectEqual(11, ch.y_off);
} }
test "print: grapheme" {
var screen: Screen = .{
.unicode = true,
};
const win: Window = .{
.x_off = 0,
.y_off = 0,
.width = 4,
.height = 2,
.screen = &screen,
};
const opts: PrintOptions = .{
.commit = false,
.wrap = .grapheme,
};
{
var segments = [_]Segment{
.{ .text = "a" },
};
const result = try win.print(&segments, opts);
try std.testing.expectEqual(1, result.col);
try std.testing.expectEqual(0, result.row);
try std.testing.expectEqual(false, result.overflow);
}
{
var segments = [_]Segment{
.{ .text = "abcd" },
};
const result = try win.print(&segments, opts);
try std.testing.expectEqual(0, result.col);
try std.testing.expectEqual(1, result.row);
try std.testing.expectEqual(false, result.overflow);
}
{
var segments = [_]Segment{
.{ .text = "abcde" },
};
const result = try win.print(&segments, opts);
try std.testing.expectEqual(1, result.col);
try std.testing.expectEqual(1, result.row);
try std.testing.expectEqual(false, result.overflow);
}
{
var segments = [_]Segment{
.{ .text = "abcdefgh" },
};
const result = try win.print(&segments, opts);
try std.testing.expectEqual(0, result.col);
try std.testing.expectEqual(2, result.row);
try std.testing.expectEqual(false, result.overflow);
}
{
var segments = [_]Segment{
.{ .text = "abcdefghi" },
};
const result = try win.print(&segments, opts);
try std.testing.expectEqual(0, result.col);
try std.testing.expectEqual(2, result.row);
try std.testing.expectEqual(true, result.overflow);
}
}
test "print: word" {
var screen: Screen = .{
.unicode = true,
};
const win: Window = .{
.x_off = 0,
.y_off = 0,
.width = 4,
.height = 2,
.screen = &screen,
};
const opts: PrintOptions = .{
.commit = false,
.wrap = .word,
};
{
var segments = [_]Segment{
.{ .text = "a" },
};
const result = try win.print(&segments, opts);
try std.testing.expectEqual(1, result.col);
try std.testing.expectEqual(0, result.row);
try std.testing.expectEqual(false, result.overflow);
}
{
var segments = [_]Segment{
.{ .text = "a b" },
};
const result = try win.print(&segments, opts);
try std.testing.expectEqual(3, result.col);
try std.testing.expectEqual(0, result.row);
try std.testing.expectEqual(false, result.overflow);
}
{
var segments = [_]Segment{
.{ .text = "a b c" },
};
const result = try win.print(&segments, opts);
try std.testing.expectEqual(1, result.col);
try std.testing.expectEqual(1, result.row);
try std.testing.expectEqual(false, result.overflow);
}
{
var segments = [_]Segment{
.{ .text = "hello" },
};
const result = try win.print(&segments, opts);
try std.testing.expectEqual(1, result.col);
try std.testing.expectEqual(1, result.row);
try std.testing.expectEqual(false, result.overflow);
}
{
var segments = [_]Segment{
.{ .text = "hi tim" },
};
const result = try win.print(&segments, opts);
try std.testing.expectEqual(3, result.col);
try std.testing.expectEqual(1, result.row);
try std.testing.expectEqual(false, result.overflow);
}
{
var segments = [_]Segment{
.{ .text = "hello tim" },
};
const result = try win.print(&segments, opts);
try std.testing.expectEqual(0, result.col);
try std.testing.expectEqual(2, result.row);
try std.testing.expectEqual(true, result.overflow);
}
{
var segments = [_]Segment{
.{ .text = "hello ti" },
};
const result = try win.print(&segments, opts);
try std.testing.expectEqual(0, result.col);
try std.testing.expectEqual(2, result.row);
try std.testing.expectEqual(false, result.overflow);
}
}