summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--build.zig52
-rw-r--r--src/tty/tty.zig108
2 files changed, 70 insertions, 90 deletions
diff --git a/build.zig b/build.zig
index 8646be05..2419dd59 100644
--- a/build.zig
+++ b/build.zig
@@ -864,11 +864,59 @@ pub fn build(b: *std.Build) void {
\\}
\\
;
+ // vaxis's render, patched the same way: a blank whose ink alone
+ // changed is not sent, since a blank's ink shows nowhere. A fade
+ // (InactiveDim on a focus change) sent every blank of every pane it
+ // touched again: 7 KB a click for three shell panes. The terminal
+ // keeps the old ink there, and so does vaxis's last frame.
+ const render_path = vaxis_dep.path("src/Vaxis.zig").getPath(b);
+ var render_src = std.Io.Dir.cwd().readFileAlloc(io, render_path, b.allocator, .limited(1 << 20)) catch @panic("read vaxis Vaxis.zig");
+ const render_anchor =
+ \\ if ((!self.refresh and
+ \\ last.eql(cell) and
+ ;
+ if (std.mem.count(u8, render_src, render_anchor) != 1) std.debug.panic("vaxis's Vaxis.zig changed at `{s}`: redo its patch in build.zig", .{render_anchor});
+ render_src = std.mem.replaceOwned(u8, b.allocator, render_src, render_anchor,
+ \\ if ((!self.refresh and
+ \\ (last.eql(cell) or blankInkOnly(&last, &cell)) and // pardes's patch (its build.zig)
+ \\
+ ) catch @panic("OOM");
+ const blank_ink =
+ \\
+ \\/// pardes's patch (its build.zig): a blank cell (a space, no underline,
+ \\/// strike or reverse) that differs from the last frame's blank in its
+ \\/// ink alone, which shows nowhere.
+ \\inline fn blankInkOnly(last: *const InternalScreen.InternalCell, cell: *const Cell) bool {
+ \\ if (!(cell.char.grapheme.len == 1 and cell.char.grapheme[0] == ' ')) return false;
+ \\ if (last.default or cell.default) return false;
+ \\ if (!(last.char.items.len == 1 and last.char.items[0] == ' ')) return false;
+ \\ const s = cell.style;
+ \\ if (s.reverse or s.strikethrough or s.ul_style != .off) return false;
+ \\ if (!std.mem.eql(u8, last.uri.items, cell.link.uri)) return false;
+ \\ // Field by field: Style.eql is slow, and this runs for every blank
+ \\ // a scrolling shell moves.
+ \\ const l = last.style;
+ \\ return sameColor(l.bg, s.bg) and sameColor(l.ul, s.ul) and l.ul_style == s.ul_style and
+ \\ l.bold == s.bold and l.dim == s.dim and l.italic == s.italic and l.blink == s.blink and
+ \\ !l.reverse and !l.strikethrough and l.invisible == s.invisible;
+ \\}
+ \\
+ \\inline fn sameColor(a: Cell.Color, b: Cell.Color) bool {
+ \\ return switch (a) {
+ \\ .default => b == .default,
+ \\ .index => |i| b == .index and b.index == i,
+ \\ .rgb => |rgb| b == .rgb and rgb[0] == b.rgb[0] and rgb[1] == b.rgb[1] and rgb[2] == b.rgb[2],
+ \\ };
+ \\}
+ \\
+ ;
const files = b.addWriteFiles();
_ = files.add("Parser.zig", b.fmt("{s}{s}", .{ src, cursor_report }));
+ _ = files.add("Vaxis.zig", b.fmt("{s}{s}", .{ render_src, blank_ink }));
_ = files.addCopyFile(vaxis_dep.path("src/widgets/terminal/Parser.zig"), "widgets/terminal/Parser.zig");
- // Suffixes: this leaves out both Parser.zig files, written above.
- _ = files.addCopyDirectory(vaxis_dep.path("src"), "", .{ .exclude_extensions = &.{"Parser.zig"} });
+ // Suffixes: this leaves out both Parser.zig files and Vaxis.zig,
+ // written above.
+ _ = files.addCopyDirectory(vaxis_dep.path("src"), "", .{ .exclude_extensions = &.{ "Parser.zig", "Vaxis.zig" } });
vaxis_mod.root_source_file = files.getDirectory().path(b, "main.zig");
}
diff --git a/src/tty/tty.zig b/src/tty/tty.zig
index efccd98e..df1e5d02 100644
--- a/src/tty/tty.zig
+++ b/src/tty/tty.zig
@@ -1453,7 +1453,7 @@ const Shell = struct {
const shadowed = chipShadows(s.frame.allocator(), surface, s.core, truecolor) catch surface;
const flashed = s.flash.apply(s.frame.allocator(), shadowed, s.core, truecolor) catch shadowed;
const trailed = s.trail.apply(s.frame.allocator(), flashed, s.core, truecolor) catch flashed;
- paintCells(vx, win, trailed.cells, trailed.cols, trailed.rows);
+ paintCells(win, trailed.cells, trailed.cols, trailed.rows);
tz_cells.end();
for (surface.images[0..surface.nimages]) |maybe| {
const place = maybe orelse continue;
@@ -2170,94 +2170,26 @@ fn mouseEvent(m: vaxis.Mouse) ?pardes.Event {
} };
}
-fn paintCells(vx: *const vaxis.Vaxis, win: vaxis.Window, cells: []const pardes.Cell, cols: u16, rows: u16) void {
+/// A blank whose ink alone changed is not sent: vaxis's render skips it
+/// (pardes's patch to Vaxis.zig in build.zig), since a blank's ink shows
+/// nowhere. Deciding that here cost every cell of every frame a look at the
+/// frame before; vaxis already makes that comparison.
+fn paintCells(win: vaxis.Window, cells: []const pardes.Cell, cols: u16, rows: u16) void {
win.clear();
var y: u16 = 0;
while (y < rows) : (y += 1) {
- const row = cells[@as(usize, y) * cols ..][0..cols];
var x: u16 = 0;
- // The cell to the left as written, for a blank that needs it.
- var left: ?Written = null;
while (x < cols) : (x += 1) {
- const cell = &row[x];
- if (cell.default) {
- left = null;
- continue;
- }
- const grapheme = cell.grapheme();
- var style = vaxisStyle(cell.style);
- if (inkless(grapheme, style)) blankInk(vx, row, x, y, &style, left);
- left = .{ .grapheme = grapheme, .style = style };
- win.writeCell(x, y, .{ .char = .{ .grapheme = grapheme }, .style = style });
+ const cell = &cells[@as(usize, y) * cols + x];
+ if (cell.default) continue;
+ win.writeCell(x, y, .{
+ .char = .{ .grapheme = cell.grapheme() },
+ .style = vaxisStyle(cell.style),
+ });
}
}
}
-const Written = struct { grapheme: []const u8, style: vaxis.Style };
-
-/// A blank's ink shows nowhere (no underline, strike or reverse to show it).
-fn inkless(grapheme: []const u8, style: vaxis.Style) bool {
- return std.mem.eql(u8, grapheme, " ") and !style.reverse and !style.strikethrough and style.ul_style == .off;
-}
-
-/// A blank whose ink alone changed (a fade: InactiveDim on a focus change)
-/// keeps the ink the terminal has there and so is not sent at all:
-/// otherwise every blank of every pane a fade touched went, 7 KB a click
-/// for three shells. Only a short gap between two cells that are sent
-/// anyway is cheaper sent than jumped (a cursor move): it takes the ink of
-/// the cell before it, so the run needs no colour change either.
-fn blankInk(vx: *const vaxis.Vaxis, row: []const pardes.Cell, x: u16, y: u16, style: *vaxis.Style, left: ?Written) void {
- const last = lastCell(vx, x, y) orelse return;
- if (vx.refresh or last.default or last.skipped or !std.mem.eql(u8, last.char.items, " ")) return;
- if (sameColor(last.style.fg, style.fg) or !sameButInk(last.style, style.*)) return;
- var kept = style.*;
- kept.fg = last.style.fg;
- if (left) |l| if (sends(vx, x - 1, y, l.grapheme, l.style)) {
- var end = x + 1;
- while (end < row.len and end - x < 8) : (end += 1) {
- const next = &row[end];
- if (next.default) break;
- const next_style = vaxisStyle(next.style);
- if (inkless(next.grapheme(), next_style)) continue;
- if (sends(vx, end, y, next.grapheme(), next_style)) {
- style.fg = l.style.fg;
- return;
- }
- break;
- }
- };
- style.* = kept;
-}
-
-/// `Style.eql` but for the ink, and cheap: this runs for every blank of
-/// every frame.
-fn sameButInk(a: vaxis.Style, b: vaxis.Style) bool {
- return sameColor(a.bg, b.bg) and sameColor(a.ul, b.ul) and a.ul_style == b.ul_style and
- a.bold == b.bold and a.dim == b.dim and a.italic == b.italic and a.blink == b.blink and
- a.reverse == b.reverse and a.invisible == b.invisible and a.strikethrough == b.strikethrough;
-}
-
-fn sameColor(a: vaxis.Color, b: vaxis.Color) bool {
- return switch (a) {
- .default => b == .default,
- .index => |i| b == .index and b.index == i,
- .rgb => |rgb| b == .rgb and std.mem.eql(u8, &rgb, &b.rgb),
- };
-}
-
-/// Whether vaxis sends this cell: it differs from what the terminal shows.
-fn sends(vx: *const vaxis.Vaxis, x: u16, y: u16, grapheme: []const u8, style: vaxis.Style) bool {
- const last = lastCell(vx, x, y) orelse return true;
- return vx.refresh or last.default or last.skipped or !std.mem.eql(u8, last.char.items, grapheme) or
- !sameColor(last.style.fg, style.fg) or !sameButInk(last.style, style);
-}
-
-fn lastCell(vx: *const vaxis.Vaxis, x: u16, y: u16) ?*const vaxis.AllocatingScreen.InternalCell {
- if (x >= vx.screen.width) return null;
- const i = @as(usize, y) * vx.screen.width + x;
- return if (i < vx.screen_last.buf.len) &vx.screen_last.buf[i] else null;
-}
-
test "a fade sends the text it dims, not the blanks beside it" {
const gpa = std.testing.allocator;
var env = try std.testing.environ.createMap(gpa);
@@ -2267,8 +2199,8 @@ test "a fade sends the text it dims, not the blanks beside it" {
defer out.deinit();
defer vx.deinit(gpa, &out.writer);
try vx.resize(gpa, &out.writer, .{ .rows = 1, .cols = 32, .x_pixel = 0, .y_pixel = 0 });
- // `a`, a long gap, `b`, a two-cell gap, `c`, a long gap, an underlined
- // blank (which shows its ink) and one more blank.
+ // `a`, a gap, `b`, a two-cell gap, `c`, a gap, an underlined blank
+ // (which shows its ink) and one more blank.
var cells: [32]pardes.Cell = @splat(.{ .default = false });
cells[0].text[0] = 'a';
cells[12].text[0] = 'b';
@@ -2279,16 +2211,16 @@ test "a fade sends the text it dims, not the blanks beside it" {
for ([_][3]u8{ .{ 200, 200, 200 }, .{ 90, 90, 90 } }, 0..) |ink, frame| {
for (&cells) |*cell| cell.style.fg = .{ .rgb = ink };
out.clearRetainingCapacity();
- paintCells(&vx, vx.window(), &cells, 32, 1);
+ paintCells(vx.window(), &cells, 32, 1);
try vx.render(&out.writer);
sent[frame] = try out.toOwnedSlice();
}
// Against a cleared screen every cell goes. After the fade the letters
- // go, the short gap between two of them (cheaper than a cursor move),
- // and the underlined blank; the long gaps are jumped.
+ // go and the underlined blank; every plain blank is jumped.
try std.testing.expectEqual(@as(usize, 29), std.mem.count(u8, sent[0], " "));
- try std.testing.expectEqual(@as(usize, 3), std.mem.count(u8, sent[1], " "));
- try std.testing.expect(std.mem.indexOf(u8, sent[1], "b c") != null);
+ try std.testing.expectEqual(@as(usize, 1), std.mem.count(u8, sent[1], " "));
+ try std.testing.expect(std.mem.indexOfScalar(u8, sent[1], 'b') != null);
+ try std.testing.expect(std.mem.indexOfScalar(u8, sent[1], 'c') != null);
try std.testing.expect(std.mem.indexOf(u8, sent[1], "90:90:90") != null);
try std.testing.expect(std.mem.indexOfScalar(u8, sent[1], 'a') != null);
}
@@ -2688,7 +2620,7 @@ const Attach = struct {
fn paint(a: *Attach) void {
a.dirty = false;
const win = a.vx.window();
- paintCells(a.vx, win, a.client.grid.items, a.client.cols, a.client.rows);
+ paintCells(win, a.client.grid.items, a.client.cols, a.client.rows);
if (a.client.cursor) |cur| paintCursor(win, cur.x, cur.y, cur.bar);
a.vx.render(a.tty.writer()) catch {};
}