summaryrefslogtreecommitdiff
path: root/main.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-07-03 17:33:05 -0300
committerGabriel Schneider <[email protected]>2026-08-01 15:02:07 -0300
commitf3b62fe53b19e36f21f8020831718cbf095d6a7b (patch)
treecdfd008869bb5f5aeb69f0ef30584fec86f6f95b /main.zig
parent6b4b1f76bb6bf969fb5106f95fb3f9ad077fac08 (diff)
downloadpardes-f3b62fe53b19e36f21f8020831718cbf095d6a7b.tar.gz
pardes-f3b62fe53b19e36f21f8020831718cbf095d6a7b.zip
performance pass
Diffstat (limited to 'main.zig')
-rw-r--r--main.zig310
1 files changed, 282 insertions, 28 deletions
diff --git a/main.zig b/main.zig
index 93c3324e..cd760ecf 100644
--- a/main.zig
+++ b/main.zig
@@ -34,6 +34,12 @@ fn fileLog(
_ = std.os.linux.write(log_fd, line.ptr, line.len);
}
+fn nowNs() u64 {
+ var ts: std.os.linux.timespec = undefined;
+ _ = std.os.linux.clock_gettime(std.os.linux.CLOCK.MONOTONIC, &ts);
+ return @intCast(ts.sec * 1_000_000_000 + ts.nsec);
+}
+
extern "c" fn forkpty(
amaster: *c_int,
name: ?[*:0]u8,
@@ -99,6 +105,7 @@ const Cli = struct {
tty_only: bool = false,
tty_toggle: u21 = default_tty_toggle,
help: bool = false,
+ perf_startup: bool = false,
};
const help_text =
@@ -138,6 +145,8 @@ fn parseCli(args: []const [:0]const u8) !Cli {
const a = args[i];
if (std.mem.eql(u8, a, "--tty")) {
cli.tty_only = true;
+ } else if (std.mem.eql(u8, a, "--perf-startup")) {
+ cli.perf_startup = true;
} else if (std.mem.eql(u8, a, "-h") or std.mem.eql(u8, a, "--help")) {
cli.help = true;
} else if (std.mem.startsWith(u8, a, "--tty-toggle=")) {
@@ -164,6 +173,10 @@ const MINH: u16 = pane_ui.MINH; // min pane height (cells)
const MAX_TERMS: usize = 16;
const MAX_COLS: usize = 6;
const MAX_PTY_DRAIN_EVENTS: usize = 128;
+const MAX_KEY_DRAIN_EVENTS: usize = 512;
+const MAX_SCROLL_DRAIN_EVENTS: usize = 4096;
+const SYNTAX_CONTEXT_BEFORE_ROWS: usize = 0;
+const SYNTAX_CONTEXT_AFTER_ROWS: usize = 2;
const PREFIX_W: u16 = pane_ui.PREFIX_W; // file-pane line-number prefix width: "%4d "
const Rect = pane_ui.Rect;
@@ -211,7 +224,7 @@ const T = paint.T;
const rgbColor = paint.rgbColor;
const colorOpt = paint.colorOpt;
const synStyle = paint.synStyle;
-const highlightFile = paint.highlightFile;
+const highlightFileRange = paint.highlightFileRange;
const cellStyle = paint.cellStyle;
// Master switch for the per-cell recolor passes (tty ansi + file syntax). Off =
@@ -254,6 +267,27 @@ const file_pane_tail = " " ++ file_pane_builtins_str;
// the stats overlay, Colors the ansi/syntax recolor, NextColor cycles the theme.
const topbar_str = "Kill Newcol Tutor Debug Colors NextColor Dump";
+fn writeStdoutBestEffort(bytes: []const u8) void {
+ var off: usize = 0;
+ while (off < bytes.len) {
+ const rc = std.os.linux.write(1, bytes[off..].ptr, bytes.len - off);
+ const n: isize = @bitCast(rc);
+ if (n <= 0) return;
+ off += @intCast(n);
+ }
+}
+
+fn paintStartupTopbar() void {
+ writeStdoutBestEffort("\x1b[?1049h\x1b[H" ++ topbar_str);
+}
+
+fn writeStartupPerfMarker(start_ns: u64) void {
+ const elapsed_us = (nowNs() - start_ns) / 1000;
+ var buf: [64]u8 = undefined;
+ const marker = std.fmt.bufPrint(&buf, "\x1b]1337;PARDES_STARTUP_US={d}\x07", .{elapsed_us}) catch return;
+ writeStdoutBestEffort(marker);
+}
+
// Path of the generated bash rc that adds OSC 133 semantic prompts (shell
// integration). bash --rcfile reads it for interactive shells; it sources the
// user's ~/.bashrc first, then wraps PS1 so each prompt emits 133;A..B. That
@@ -324,6 +358,22 @@ fn fileLineCount(content: []const u8) usize {
return std.mem.count(u8, content, "\n") + 1;
}
+fn lineStartByte(content: []const u8, row: usize) usize {
+ var off: usize = 0;
+ var r: usize = 0;
+ while (r < row) : (r += 1) {
+ const rel = std.mem.indexOfScalarPos(u8, content, off, '\n') orelse return content.len;
+ off = rel + 1;
+ }
+ return off;
+}
+
+fn visibleByteRange(content: []const u8, row: usize, rows: usize) struct { start: usize, end: usize } {
+ const start = lineStartByte(content, row);
+ const end = lineStartByte(content, row + rows);
+ return .{ .start = start, .end = @max(start, end) };
+}
+
// the pane's current scroll offset (top visible row, in body coordinates). For a
// file it's `scroll`; for a terminal it's the emulator's scrollback offset.
fn paneScroll(t: *Term) i32 {
@@ -335,12 +385,45 @@ fn paneScrollBy(t: *Term, delta: i32) void {
if (t.file) |*f| {
const max: i64 = @intCast(fileLineCount(f.content) -| 1);
const n = std.math.clamp(@as(i64, @intCast(f.scroll)) + delta, 0, max);
- f.scroll = @intCast(n);
+ const next: usize = @intCast(n);
+ if (next != f.scroll) {
+ f.scroll = next;
+ f.syntax_dirty = true;
+ }
} else {
t.term.screens.active.scroll(.{ .delta_row = @intCast(delta) });
}
}
+fn hoveredPane(terms: *const [MAX_TERMS]?*Term, rects: *const [MAX_TERMS]Rect, mcol: u16, mrow: u16) ?usize {
+ return for (terms, 0..) |slot, i| {
+ if (slot == null) continue;
+ const r = rects[i];
+ if (mcol >= r.x and mcol < r.x + r.w and mrow >= r.y and mrow < r.y + r.h) break i;
+ } else null;
+}
+
+fn handleWheelMouse(
+ terms: *[MAX_TERMS]?*Term,
+ rects: *const [MAX_TERMS]Rect,
+ screen_w: u16,
+ screen_h: u16,
+ hover_col: *u16,
+ hover_row: *u16,
+ mouse: vaxis.Mouse,
+) bool {
+ if (mouse.button != .wheel_down and mouse.button != .wheel_up) return false;
+ const mpos = pane_ui.clampMousePos(screen_w, screen_h, mouse);
+ const mcol = mpos.col;
+ const mrow = mpos.row;
+ hover_col.* = mcol;
+ hover_row.* = mrow;
+ if (hoveredPane(terms, rects, mcol, mrow)) |id| {
+ if (terms[id]) |t| paneScrollBy(t, if (mouse.button == .wheel_down) 1 else -1);
+ }
+ return true;
+}
+
// Before running a composed command in a terminal, push the shell down so its
// output lands BELOW the last row the user typed overlay text on. The shell owns
// its cursor, so we move it the only way we can: emit newlines to the pty until
@@ -781,15 +864,63 @@ fn readFileAlloc(gpa: std.mem.Allocator, path: [*:0]const u8) ![]u8 {
return buf.toOwnedSlice(gpa);
}
-// Replace a file pane's content (freeing the old buffer) and re-derive syntax
-// highlighting to match. Every file edit funnels through here, so highlights stay
-// in sync with content (and can never index past it). The reparse is skipped when
-// colors are off, keeping the edit path exactly as fast as before colors existed.
+fn refreshFileHighlightsVisible(gpa: std.mem.Allocator, f: *Term.File, rows: usize) void {
+ const start_row = f.scroll -| SYNTAX_CONTEXT_BEFORE_ROWS;
+ const range = visibleByteRange(f.content, start_row, SYNTAX_CONTEXT_BEFORE_ROWS + rows + SYNTAX_CONTEXT_AFTER_ROWS);
+ if (!colors_on) {
+ if (f.highlights.len > 0) gpa.free(f.highlights);
+ f.highlights = &.{};
+ f.highlight_start = 0;
+ f.syntax_dirty = false;
+ return;
+ }
+ const new_highlights = highlightFileRange(gpa, f.path, f.content, range.start, range.end) catch {
+ f.syntax_dirty = false;
+ return;
+ };
+ if (f.highlights.len > 0) gpa.free(f.highlights);
+ f.highlights = new_highlights;
+ f.highlight_start = if (f.highlights.len > 0) range.start else 0;
+ f.syntax_dirty = false;
+}
+
+fn refreshDirtyFileHighlightsVisible(gpa: std.mem.Allocator, terms: *[MAX_TERMS]?*Term) bool {
+ var refreshed = false;
+ for (terms) |*slot| {
+ if (slot.*) |t| {
+ if (t.file) |*f| {
+ if (f.syntax_dirty) {
+ refreshFileHighlightsVisible(gpa, f, t.rows);
+ refreshed = true;
+ }
+ }
+ }
+ }
+ return refreshed;
+}
+
+// Replace a file pane's content without synchronously reparsing the whole file.
+// The old style map remains in use until a bounded visible-range refresh catches
+// up, so edits do not flash to unhighlighted text.
fn setFileContent(gpa: std.mem.Allocator, f: *Term.File, new: []u8) void {
+ const had_syntax = f.syntax_dirty or f.highlights.len > 0;
gpa.free(f.content);
f.content = new;
- if (f.highlights.len > 0) gpa.free(f.highlights);
- f.highlights = if (colors_on) (highlightFile(gpa, f.path, new) catch &.{}) else &.{};
+ if (had_syntax and colors_on) {
+ // Keep the old style map while dirty. This preserves most visible color
+ // state during input batching; the synchronous visible-range refresh below
+ // swaps in a correctly offset map before the next frame is rendered.
+ f.syntax_dirty = true;
+ } else {
+ if (f.highlights.len > 0) gpa.free(f.highlights);
+ f.highlights = &.{};
+ f.highlight_start = 0;
+ f.syntax_dirty = false;
+ }
+}
+
+fn setFileContentStaleSyntax(gpa: std.mem.Allocator, f: *Term.File, new: []u8) void {
+ setFileContent(gpa, f, new);
}
// Open a read/editable FILE pane: a Term with `file` set, sharing all the
@@ -801,8 +932,6 @@ fn openFileView(gpa: std.mem.Allocator, rp: [*:0]const u8, line: usize, cols: u1
errdefer gpa.free(content);
const path = try gpa.dupe(u8, std.mem.span(rp));
errdefer gpa.free(path);
- const highlights = try highlightFile(gpa, path, content);
- errdefer if (highlights.len > 0) gpa.free(highlights);
const t = try gpa.create(Term);
errdefer gpa.destroy(t);
const total = fileLineCount(content);
@@ -813,7 +942,7 @@ fn openFileView(gpa: std.mem.Allocator, rp: [*:0]const u8, line: usize, cols: u1
.term = try ghostty_vt.Terminal.init(gpa, .{ .cols = 1, .rows = 1 }),
.stream = undefined,
.reader = .{ .any_future = null, .result = {} },
- .file = .{ .path = path, .content = content, .scroll = scroll, .highlights = highlights },
+ .file = .{ .path = path, .content = content, .scroll = scroll, .highlights = &.{}, .syntax_dirty = true },
.cols = cols,
.rows = rows,
.cur_pinned = true,
@@ -1083,7 +1212,11 @@ fn renderPane(root: vaxis.Window, alloc: std.mem.Allocator, t: *Term, r: Rect, a
const base = @intFromPtr(line.ptr) - @intFromPtr(f.content.ptr);
var c: usize = 0;
while (c < line.len and pw + c < bw) : (c += 1) {
- const ss = synStyle(@enumFromInt(f.highlights[base + c])) orelse continue;
+ const idx = base + c;
+ if (idx < f.highlight_start) continue;
+ const hidx = idx - f.highlight_start;
+ if (hidx >= f.highlights.len) continue;
+ const ss = synStyle(@enumFromInt(f.highlights[hidx])) orelse continue;
const cx: u16 = @intCast(pw + c);
if (text.readCell(cx, BOX_H + vr)) |cell| {
var nc = cell;
@@ -1391,7 +1524,7 @@ fn enterInsert(gpa: std.mem.Allocator, talloc: std.mem.Allocator, t: *Term, wher
.open_below => {
if (t.file) |*f| {
const new = modal.insertAt(gpa, f.content, .{ .row = cur.row, .col = llen }, "\n") catch return;
- setFileContent(gpa, f, new);
+ setFileContentStaleSyntax(gpa, f, new);
t.cur_row = @as(i32, @intCast(cur.row + 1)) + pl.row0;
t.cur_col = 0;
} else {
@@ -1405,7 +1538,7 @@ fn enterInsert(gpa: std.mem.Allocator, talloc: std.mem.Allocator, t: *Term, wher
.open_above => {
if (t.file) |*f| {
const new = modal.insertAt(gpa, f.content, .{ .row = cur.row, .col = 0 }, "\n") catch return;
- setFileContent(gpa, f, new);
+ setFileContentStaleSyntax(gpa, f, new);
// current line shifts down; the new empty line is at cur.row
t.cur_col = 0;
} else {
@@ -2047,7 +2180,7 @@ fn handleInsert(gpa: std.mem.Allocator, talloc: std.mem.Allocator, t: *Term, key
const c = modal.Cursor{ .row = @intCast(@max(0, t.cur_row)), .col = @intCast(@max(0, t.cur_col)) };
if (key.text) |text| {
const new = modal.insertAt(gpa, f.content, c, text) catch return;
- setFileContent(gpa, f, new);
+ setFileContentStaleSyntax(gpa, f, new);
for (text) |ch| {
if (ch == '\n') {
t.cur_row += 1;
@@ -2061,7 +2194,7 @@ fn handleInsert(gpa: std.mem.Allocator, talloc: std.mem.Allocator, t: *Term, key
switch (key.codepoint) {
vaxis.Key.enter => {
const new = modal.insertAt(gpa, f.content, c, "\n") catch return;
- setFileContent(gpa, f, new);
+ setFileContentStaleSyntax(gpa, f, new);
t.cur_row += 1;
t.cur_col = 0;
t.cur_pinned = true;
@@ -2070,12 +2203,12 @@ fn handleInsert(gpa: std.mem.Allocator, talloc: std.mem.Allocator, t: *Term, key
vaxis.Key.backspace => {
if (t.cur_col > 0) {
const new = modal.deleteChar(gpa, f.content, .{ .row = c.row, .col = c.col - 1 }) catch return;
- setFileContent(gpa, f, new);
+ setFileContentStaleSyntax(gpa, f, new);
t.cur_col -= 1;
} else if (t.cur_row > 0) {
const prevlen = modal.lineSlice(f.content, c.row - 1).len;
const new = modal.deleteChar(gpa, f.content, .{ .row = c.row - 1, .col = prevlen }) catch return;
- setFileContent(gpa, f, new);
+ setFileContentStaleSyntax(gpa, f, new);
t.cur_row -= 1;
t.cur_col = @intCast(prevlen);
}
@@ -2146,6 +2279,24 @@ fn handleInsert(gpa: std.mem.Allocator, talloc: std.mem.Allocator, t: *Term, key
}
}
+fn isBatchableInsertKey(key: vaxis.Key) bool {
+ if (key.mods.ctrl or key.mods.alt) return false;
+ if (key.text != null) return true;
+ return key.matches(vaxis.Key.backspace, .{});
+}
+
+fn isBatchableNormalMoveKey(key: vaxis.Key) bool {
+ if (key.mods.alt) return false;
+ if (key.matches('h', .{}) or key.matches(vaxis.Key.left, .{})) return true;
+ if (key.matches('j', .{}) or key.matches(vaxis.Key.down, .{})) return true;
+ if (key.matches('k', .{}) or key.matches(vaxis.Key.up, .{})) return true;
+ if (key.matches('l', .{}) or key.matches(vaxis.Key.right, .{})) return true;
+ if (key.matches('d', .{ .ctrl = true }) or key.matches(vaxis.Key.page_down, .{})) return true;
+ if (key.matches('u', .{ .ctrl = true }) or key.matches(vaxis.Key.page_up, .{})) return true;
+ if (key.matches('f', .{ .ctrl = true }) or key.matches('b', .{ .ctrl = true })) return true;
+ return false;
+}
+
// tty mode: forward the key to the pty as terminal input. Ctrl+letter is sent as
// the raw control byte (so the shell sees Ctrl-D EOF, Ctrl-L clear, ...).
fn forwardToPty(t: *Term, key: vaxis.Key) void {
@@ -2323,7 +2474,11 @@ fn actOnSelection(
active.* = fid;
if (!as_image and pl.line > 0) if (terms[fid]) |tt| if (tt.file) |*ff|
if (pl.line <= fileLineCount(ff.content)) {
- ff.scroll = pl.line - 1;
+ const next = pl.line - 1;
+ if (next != ff.scroll) {
+ ff.scroll = next;
+ ff.syntax_dirty = true;
+ }
};
} else {
const slot: ?usize = for (terms.*, 0..) |sl, i| {
@@ -2497,6 +2652,7 @@ fn cursorWordSel(t: *Term) Term.Sel {
}
pub fn main(init: std.process.Init) !void {
+ const startup_main_t0 = nowNs();
const io = init.io;
const gpa = init.gpa;
@@ -2511,11 +2667,24 @@ pub fn main(init: std.process.Init) !void {
}
const tty_only = cli.tty_only;
const tty_toggle = cli.tty_toggle;
+ const perf_startup = cli.perf_startup;
var screen_w: u16 = 80;
var screen_h: u16 = 24;
var resize_count: usize = 0;
+ var startup_alt_screen = false;
+ if (vaxis.supports_terminal_prepaint) {
+ paintStartupTopbar();
+ if (perf_startup) writeStartupPerfMarker(startup_main_t0);
+ startup_alt_screen = true;
+ }
+ errdefer if (startup_alt_screen)
+ writeStdoutBestEffort("\x1b[?1049l");
+
+ var tty_buf: [0x10000]u8 = undefined;
+ var tty = try vaxis.Tty.init(io, &tty_buf);
+
// stb_image allocator for image panes (see image.zig).
image.start(io, gpa);
defer image.stop();
@@ -2527,18 +2696,20 @@ pub fn main(init: std.process.Init) !void {
defer freeYank(gpa);
var ov_buf: [128]u8 = undefined; // overlay text; must outlive each vx.render()
- var tty_buf: [0x10000]u8 = undefined;
- var tty = try vaxis.Tty.init(io, &tty_buf);
-
// system_clipboard_allocator lets the loop's reader thread decode OSC 52 paste
// replies (delivered as .paste events). gpa is already used cross-thread by the
// pty readers, so it's safe here too.
var vx = try vaxis.init(io, gpa, init.environ_map, .{ .system_clipboard_allocator = gpa });
defer vx.deinit(gpa, tty.writer());
+ if (startup_alt_screen) {
+ vaxis.adoptAltScreen(&vx);
+ } else {
+ try vx.enterAltScreen(tty.writer());
+ }
+ defer vx.exitAltScreen(tty.writer()) catch {};
+ startup_alt_screen = false;
try vx.setMouseMode(tty.writer(), true);
- try vx.enterAltScreen(tty.writer());
- defer vx.exitAltScreen(tty.writer()) catch {};
// ---- terminals + layout ----
var terms: [MAX_TERMS]?*Term = @splat(null);
@@ -2598,7 +2769,7 @@ pub fn main(init: std.process.Init) !void {
// from the replies. We only need kitty graphics (for image panes). Keep the
// timeout tiny — a real terminal answers in well under a millisecond, and we
// don't want to stall startup on one that never will.
- vx.queryTerminal(tty.writer(), std.Io.Duration.fromMilliseconds(20)) catch {};
+ vx.queryTerminal(tty.writer(), std.Io.Duration.fromMilliseconds(2)) catch {};
// now safe to spawn the per-terminal reader tasks (no forking here)
for (&terms, 0..) |*slot, id| {
@@ -2619,7 +2790,9 @@ pub fn main(init: std.process.Init) !void {
// middle release, freed on overwrite / app exit.
var chord_arg: ?[]u8 = null;
defer if (chord_arg) |a| gpa.free(a);
- var pending_event: ?@TypeOf(Command.value) = null;
+ // Paint the chrome once immediately. Without this, startup waits for the
+ // first winsize/pty event before the user sees a frame.
+ var pending_event: ?@TypeOf(Command.value) = .tick;
while (true) {
const event = if (pending_event) |ev| blk: {
@@ -2627,6 +2800,9 @@ pub fn main(init: std.process.Init) !void {
break :blk ev;
} else try loop.nextEvent();
var drain_pty_after = false;
+ var drain_insert_keys = false;
+ var drain_scroll_events = false;
+ var drain_normal_moves = false;
switch (event) {
.mouse => |mouse| {
const mpos = pane_ui.clampMousePos(screen_w, screen_h, mouse);
@@ -2656,8 +2832,10 @@ pub fn main(init: std.process.Init) !void {
if (mouse.button == .wheel_down) {
if (hovered) |id| paneScrollBy(terms[id].?, 1);
+ drain_scroll_events = true;
} else if (mouse.button == .wheel_up) {
if (hovered) |id| paneScrollBy(terms[id].?, -1);
+ drain_scroll_events = true;
} else if (mouse.type == .press and mrow < TOPBAR_H) {
// global tagbar: MIDDLE-click runs the command under the click
// (acme "execute"; left-click must NOT execute — a stray left-click
@@ -3016,13 +3194,19 @@ pub fn main(init: std.process.Init) !void {
// on terminals (above).
// tty forwards every key to the pty.
switch (at.mode) {
- .normal => handleNormal(gpa, frame_arena.allocator(), at, key),
+ .normal => {
+ drain_normal_moves = isBatchableNormalMoveKey(key);
+ handleNormal(gpa, frame_arena.allocator(), at, key);
+ },
.insert => {
if (key.matches(vaxis.Key.escape, .{})) {
at.mode = .normal;
at.msel.active = false;
at.pending = 0;
- } else handleInsert(gpa, frame_arena.allocator(), at, key);
+ } else {
+ drain_insert_keys = isBatchableInsertKey(key);
+ handleInsert(gpa, frame_arena.allocator(), at, key);
+ }
},
.tty => forwardToPty(at, key),
}
@@ -3067,6 +3251,74 @@ pub fn main(init: std.process.Init) !void {
else => {},
}
+ if (drain_normal_moves and pending_event == null) {
+ var drained: usize = 0;
+ while (drained < MAX_KEY_DRAIN_EVENTS) : (drained += 1) {
+ const next = try loop.tryEvent() orelse break;
+ var handled = false;
+ switch (next) {
+ .key_press => |next_key| {
+ if (isBatchableNormalMoveKey(next_key)) {
+ if (terms[active]) |at| {
+ if (!at.tag_edit and at.mode == .normal) {
+ handleNormal(gpa, frame_arena.allocator(), at, next_key);
+ handled = true;
+ }
+ }
+ }
+ },
+ else => {},
+ }
+ if (!handled) {
+ pending_event = next;
+ break;
+ }
+ }
+ }
+
+ if (drain_scroll_events and pending_event == null) {
+ var drained: usize = 0;
+ while (drained < MAX_SCROLL_DRAIN_EVENTS) : (drained += 1) {
+ const next = try loop.tryEvent() orelse break;
+ var handled = false;
+ switch (next) {
+ .mouse => |next_mouse| {
+ handled = handleWheelMouse(&terms, &rects, screen_w, screen_h, &hover_col, &hover_row, next_mouse);
+ },
+ else => {},
+ }
+ if (!handled) {
+ pending_event = next;
+ break;
+ }
+ }
+ }
+
+ if (drain_insert_keys and pending_event == null) {
+ var drained: usize = 0;
+ while (drained < MAX_KEY_DRAIN_EVENTS) : (drained += 1) {
+ const next = try loop.tryEvent() orelse break;
+ var handled = false;
+ switch (next) {
+ .key_press => |next_key| {
+ if (isBatchableInsertKey(next_key)) {
+ if (terms[active]) |at| {
+ if (!at.tag_edit and at.mode == .insert) {
+ handleInsert(gpa, frame_arena.allocator(), at, next_key);
+ handled = true;
+ }
+ }
+ }
+ },
+ else => {},
+ }
+ if (!handled) {
+ pending_event = next;
+ break;
+ }
+ }
+ }
+
if (drain_pty_after) {
var drained: usize = 0;
while (drained < MAX_PTY_DRAIN_EVENTS) : (drained += 1) {
@@ -3120,6 +3372,8 @@ pub fn main(init: std.process.Init) !void {
}
}
+ _ = refreshDirtyFileHighlightsVisible(gpa, &terms);
+
// ---- render ----
const win = vx.window();
win.clear();