From 5f41978054f0e60ab58baf26870811bd77fb41eb Mon Sep 17 00:00:00 2001 From: Tim Culverhouse Date: Wed, 29 May 2024 13:13:54 -0500 Subject: [PATCH] refactor(vaxis): require an io.AnyWriter for writes All vaxis functions that write to the terminal now require an io.AnyWriter as a parameter --- README.md | 97 +++++++----- examples/cli.zig | 16 +- examples/image.zig | 27 ++-- examples/main.zig | 19 +-- examples/nvim.zig | 18 ++- examples/pathological.zig | 61 -------- examples/pause_tui.zig | 29 ++-- examples/readline.zig | 75 ---------- examples/shell.zig | 126 ---------------- examples/table.zig | 16 +- examples/text_input.zig | 4 +- examples/vaxis.zig | 18 ++- src/Vaxis.zig | 305 +++++++++++++++++++------------------- 13 files changed, 294 insertions(+), 517 deletions(-) delete mode 100644 examples/pathological.zig delete mode 100644 examples/readline.zig delete mode 100644 examples/shell.zig diff --git a/README.md b/README.md index 25b0574..74cf78e 100644 --- a/README.md +++ b/README.md @@ -15,38 +15,46 @@ Contributions are welcome. Vaxis uses zig `0.12.0`. -## Feature comparison +## Features -| Feature | Vaxis | libvaxis | notcurses | -| ------------------------------ | :---: | :------: | :-------: | -| RGB | ✅ | ✅ | ✅ | -| Hyperlinks | ✅ | ✅ | ❌ | -| Bracketed Paste | ✅ | ✅ | ❌ | -| Kitty Keyboard | ✅ | ✅ | ✅ | -| Styled Underlines | ✅ | ✅ | ✅ | -| Mouse Shapes (OSC 22) | ✅ | ✅ | ❌ | -| System Clipboard (OSC 52) | ✅ | ✅ | ❌ | -| System Notifications (OSC 9) | ✅ | ✅ | ❌ | -| System Notifications (OSC 777) | ✅ | ✅ | ❌ | -| Synchronized Output (DEC 2026) | ✅ | ✅ | ✅ | -| Unicode Core (DEC 2027) | ✅ | ✅ | ❌ | -| Color Mode Updates (DEC 2031) | ✅ | ✅ | ❌ | -| Images (full/space) | ✅ | planned | ✅ | -| Images (half block) | ✅ | planned | ✅ | -| Images (quadrant) | ✅ | planned | ✅ | -| Images (sextant) | ❌ | ❌ | ✅ | -| Images (sixel) | ✅ | ❌ | ✅ | -| Images (kitty) | ✅ | ✅ | ✅ | -| Images (iterm2) | ❌ | ❌ | ✅ | -| Video | ❌ | ❌ | ✅ | -| Dank | 🆗 | 🆗 | ✅ | +| Feature | libvaxis | +| ------------------------------ | :------: | +| RGB | ✅ | +| Hyperlinks | ✅ | +| Bracketed Paste | ✅ | +| Kitty Keyboard | ✅ | +| Styled Underlines | ✅ | +| Mouse Shapes (OSC 22) | ✅ | +| System Clipboard (OSC 52) | ✅ | +| System Notifications (OSC 9) | ✅ | +| System Notifications (OSC 777) | ✅ | +| Synchronized Output (DEC 2026) | ✅ | +| Unicode Core (DEC 2027) | ✅ | +| Color Mode Updates (DEC 2031) | ✅ | +| Images (kitty) | ✅ | ## Usage [Documentation](https://rockorager.github.io/libvaxis/#vaxis.Vaxis) -The below example can be run using `zig build run 2>log`. stderr must be -redirected in order to not print to the same screen. +Vaxis requires three basic primitives to operate: + +1. A TTY instance +2. An instance of Vaxis +3. An event loop + +The library provides a general purpose posix TTY implementation, as well as a +multi-threaded event loop implementation. Users of the library are encouraged to +use the event loop of their choice. The event loop is responsible for reading +the TTY, passing the read bytes to the vaxis parser, and handling events. + +A core feature of Vaxis is it's ability to detect features via terminal queries +instead of relying on a terminfo database. This requires that the event loop +also handle these query responses and update the Vaxis.caps struct accordingly. +See the `Loop` implementation to see how this is done if writing your own event +loop. + +## Example ```zig const std = @import("std"); @@ -76,21 +84,35 @@ pub fn main() !void { } const alloc = gpa.allocator(); + // Initialize a tty + var tty = try vaxis.Tty.init(); + defer tty.deinit(); + // Initialize Vaxis var vx = try vaxis.init(alloc, .{}); // deinit takes an optional allocator. If your program is exiting, you can // choose to pass a null allocator to save some exit time. - defer vx.deinit(alloc); + defer vx.deinit(alloc, tty.anyWriter()); - var loop: vaxis.Loop(Event) = .{}; + // The event loop requires an intrusive init. We create an instance with + // stable points to Vaxis and our TTY, then init the instance. Doing so + // installs a signal handler for SIGWINCH on posix TTYs + // + // This event loop is thread safe. It reads the tty in a separate thread + var loop: vaxis.Loop(Event) = .{ + .tty = &tty, + .vaxis = &vaxis, + }; + try loop.init(); + // Start the read loop. This puts the terminal in raw mode and begins // reading user input - try loop.run(); + try loop.start(); defer loop.stop(); // Optionally enter the alternate screen - try vx.enterAltScreen(); + try vx.enterAltScreen(tty.anyWriter()); // We'll adjust the color index every keypress for the border var color_idx: u8 = 0; @@ -100,12 +122,10 @@ pub fn main() !void { var text_input = TextInput.init(alloc); defer text_input.deinit(); - // Sends queries to terminal to detect certain features. This should - // _always_ be called, but is left to the application to decide when - try vx.queryTerminal(); + // Sends queries to terminal to detect certain features. This should always + // be called after entering the alt screen, if you are using the alt screen + try vx.queryTerminal(tty.anyWriter(), 1 * std.time.ns_per_s); - // The main event loop. Vaxis provides a thread safe, blocking, buffered - // queue which can serve as the primary event queue for an application while (true) { // nextEvent blocks until an event is in the queue const event = loop.nextEvent(); @@ -141,7 +161,7 @@ pub fn main() !void { // more than one byte will incur an allocation on the first render // after it is drawn. Thereafter, it will not allocate unless the // screen is resized - .winsize => |ws| try vx.resize(alloc, ws), + .winsize => |ws| try vx.resize(alloc, tty.anyWriter(), ws), else => {}, } @@ -175,8 +195,9 @@ pub fn main() !void { // Draw the text_input in the child window text_input.draw(child); - // Render the screen - try vx.render(); + // Render the screen. Using a buffered writer will offer much better + // performance, but is not required + try vx.render(tty.anyWriter()); } } ``` diff --git a/examples/cli.zig b/examples/cli.zig index c149acb..f5f4b62 100644 --- a/examples/cli.zig +++ b/examples/cli.zig @@ -14,15 +14,19 @@ pub fn main() !void { } const alloc = gpa.allocator(); + var tty = try vaxis.Tty.init(); + defer tty.deinit(); + var vx = try vaxis.init(alloc, .{}); - defer vx.deinit(alloc); + defer vx.deinit(alloc, tty.anyWriter()); - var loop: vaxis.Loop(Event) = .{ .vaxis = &vx }; + var loop: vaxis.Loop(Event) = .{ .tty = &tty, .vaxis = &vx }; + try loop.init(); - try loop.run(); + try loop.start(); defer loop.stop(); - try vx.queryTerminal(); + try vx.queryTerminal(tty.anyWriter(), 1 * std.time.ns_per_s); var text_input = TextInput.init(alloc, &vx.unicode); defer text_input.deinit(); @@ -70,7 +74,7 @@ pub fn main() !void { } }, .winsize => |ws| { - try vx.resize(alloc, ws); + try vx.resize(alloc, tty.anyWriter(), ws); }, else => {}, } @@ -91,7 +95,7 @@ pub fn main() !void { _ = try win.print(&seg, .{ .row_offset = j + 1 }); } } - try vx.render(); + try vx.render(tty.anyWriter()); } } diff --git a/examples/image.zig b/examples/image.zig index f8008e0..d3340dd 100644 --- a/examples/image.zig +++ b/examples/image.zig @@ -18,24 +18,27 @@ pub fn main() !void { } const alloc = gpa.allocator(); + var tty = try vaxis.Tty.init(); + defer tty.deinit(); + var vx = try vaxis.init(alloc, .{}); - defer vx.deinit(alloc); + defer vx.deinit(alloc, tty.anyWriter()); - var loop: vaxis.Loop(Event) = .{ .vaxis = &vx }; + var loop: vaxis.Loop(Event) = .{ .tty = &tty, .vaxis = &vx }; + try loop.init(); - try loop.run(); + try loop.start(); defer loop.stop(); - try vx.enterAltScreen(); - - try vx.queryTerminal(); + try vx.enterAltScreen(tty.anyWriter()); + try vx.queryTerminal(tty.anyWriter(), 1 * std.time.ns_per_s); const imgs = [_]vaxis.Image{ - try vx.loadImage(alloc, .{ .path = "examples/zig.png" }), - try vx.loadImage(alloc, .{ .path = "examples/vaxis.png" }), + try vx.loadImage(alloc, tty.anyWriter(), .{ .path = "examples/zig.png" }), + try vx.loadImage(alloc, tty.anyWriter(), .{ .path = "examples/vaxis.png" }), }; - defer vx.freeImage(imgs[0].id); - defer vx.freeImage(imgs[1].id); + defer vx.freeImage(tty.anyWriter(), imgs[0].id); + defer vx.freeImage(tty.anyWriter(), imgs[1].id); var n: usize = 0; @@ -54,7 +57,7 @@ pub fn main() !void { else if (key.matches('k', .{})) clip_y -|= 1; }, - .winsize => |ws| try vx.resize(alloc, ws), + .winsize => |ws| try vx.resize(alloc, tty.anyWriter(), ws), } n = (n + 1) % imgs.len; @@ -68,6 +71,6 @@ pub fn main() !void { .y = clip_y, } }); - try vx.render(); + try vx.render(tty.anyWriter()); } } diff --git a/examples/main.zig b/examples/main.zig index b7537c5..386f87b 100644 --- a/examples/main.zig +++ b/examples/main.zig @@ -14,19 +14,20 @@ pub fn main() !void { } const alloc = gpa.allocator(); - // Initialize Vaxis + var tty = try vaxis.Tty.init(); + defer tty.deinit(); + var vx = try vaxis.init(alloc, .{}); - defer vx.deinit(alloc); + defer vx.deinit(alloc, tty.anyWriter()); - var loop: vaxis.Loop(Event) = .{ .vaxis = &vx }; + var loop: vaxis.Loop(Event) = .{ .tty = &tty, .vaxis = &vx }; + try loop.init(); - // Start the read loop. This puts the terminal in raw mode and begins - // reading user input - try loop.run(); + try loop.start(); defer loop.stop(); // Optionally enter the alternate screen - try vx.enterAltScreen(); + try vx.enterAltScreen(tty.anyWriter()); // We'll adjust the color index every keypress var color_idx: u8 = 0; @@ -51,7 +52,7 @@ pub fn main() !void { } }, .winsize => |ws| { - try vx.resize(alloc, ws); + try vx.resize(alloc, tty.anyWriter(), ws); }, else => {}, } @@ -85,7 +86,7 @@ pub fn main() !void { child.writeCell(i, 0, cell); } // Render the screen - try vx.render(); + try vx.render(tty.anyWriter()); } } diff --git a/examples/nvim.zig b/examples/nvim.zig index 156290d..1c845a2 100644 --- a/examples/nvim.zig +++ b/examples/nvim.zig @@ -23,18 +23,22 @@ pub fn main() !void { } const alloc = gpa.allocator(); + var tty = try vaxis.Tty.init(); + defer tty.deinit(); + // Initialize Vaxis var vx = try vaxis.init(alloc, .{}); - defer vx.deinit(alloc); + defer vx.deinit(alloc, tty.anyWriter()); - var loop: vaxis.Loop(Event) = .{ .vaxis = &vx }; + var loop: vaxis.Loop(Event) = .{ .tty = &tty, .vaxis = &vx }; + try loop.init(); - try loop.run(); + try loop.start(); defer loop.stop(); // Optionally enter the alternate screen - try vx.enterAltScreen(); - try vx.queryTerminal(); + // try vx.enterAltScreen(tty.anyWriter()); + try vx.queryTerminal(tty.anyWriter(), 1 * std.time.ns_per_s); var nvim = try vaxis.widgets.nvim.Nvim(Event).init(alloc, &loop); try nvim.spawn(); @@ -53,7 +57,7 @@ pub fn main() !void { try nvim.update(.{ .key_press = key }); }, .winsize => |ws| { - try vx.resize(alloc, ws); + try vx.resize(alloc, tty.anyWriter(), ws); }, .nvim => |nvim_event| { switch (nvim_event) { @@ -74,6 +78,6 @@ pub fn main() !void { }, ); try nvim.draw(child); - try vx.render(); + try vx.render(tty.anyWriter()); } } diff --git a/examples/pathological.zig b/examples/pathological.zig deleted file mode 100644 index 1af65f1..0000000 --- a/examples/pathological.zig +++ /dev/null @@ -1,61 +0,0 @@ -const std = @import("std"); -const vaxis = @import("vaxis"); -const Cell = vaxis.Cell; - -const log = std.log.scoped(.main); - -const Event = union(enum) { - winsize: vaxis.Winsize, -}; - -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - const alloc = gpa.allocator(); - var vx = try vaxis.init(alloc, .{}); - defer vx.deinit(alloc); - - var loop: vaxis.Loop(Event) = .{ .vaxis = &vx }; - - try loop.run(); - defer loop.stop(); - try vx.enterAltScreen(); - try vx.queryTerminal(); - - while (true) { - const event = loop.nextEvent(); - switch (event) { - .winsize => |ws| { - try vx.resize(alloc, ws); - break; - }, - } - } - - const timer_start = std.time.microTimestamp(); - var iter: usize = 0; - while (iter < 10_000) : (iter += 1) { - const win = vx.window(); - const child = win.initChild(0, 0, .{ .limit = 20 }, .{ .limit = 20 }); - win.clear(); - var row: usize = 0; - while (row < child.height) : (row += 1) { - var col: usize = 0; - while (col < child.width) : (col += 1) { - child.writeCell(col, row, .{ - .char = .{ - .grapheme = " ", - .width = 1, - }, - .style = .{ - .bg = .{ .index = @truncate(col + iter) }, - }, - }); - } - } - try vx.render(); - } - try vx.exitAltScreen(); - const took = std.time.microTimestamp() - timer_start; - const stdout = std.io.getStdOut().writer(); - try stdout.print("\r\ntook {d}ms to render 10,000 times\r\n", .{@divTrunc(took, std.time.us_per_ms)}); -} diff --git a/examples/pause_tui.zig b/examples/pause_tui.zig index 1d29460..bb49995 100644 --- a/examples/pause_tui.zig +++ b/examples/pause_tui.zig @@ -31,20 +31,17 @@ pub fn main() !void { } const alloc = gpa.allocator(); - // Initialize Vaxis with our event type - var vx = try vaxis.init(Event, .{}); - // deinit takes an optional allocator. If your program is exiting, you can - // choose to pass a null allocator to save some exit time. - defer vx.deinit(alloc); + var tty = try vaxis.Tty.init(); + defer tty.deinit(); - // Start the read loop. This puts the terminal in raw mode and begins - // reading user input - try vx.startReadThread(); - defer vx.stopReadThread(); + var vx = try vaxis.init(alloc, .{}); + defer vx.deinit(alloc, tty.anyWriter()); - // Optionally enter the alternate screen - try vx.enterAltScreen(); - defer vx.exitAltScreen() catch {}; + var loop: vaxis.Loop(Event) = .{ .tty = &tty, .vaxis = &vx }; + try loop.init(); + + try loop.start(); + defer loop.stop(); // We'll adjust the color index every keypress for the border var color_idx: u8 = 0; @@ -56,7 +53,7 @@ pub fn main() !void { // Sends queries to terminal to detect certain features. This should // _always_ be called, but is left to the application to decide when - try vx.queryTerminal(); + try vx.queryTerminal(tty.anyWriter(), 1 * std.time.ns_per_s); try vx.setMouseMode(true); @@ -78,8 +75,6 @@ pub fn main() !void { break; } else if (key.matches('l', .{ .ctrl = true })) { vx.queueRefresh(); - } else if (key.matches('n', .{ .ctrl = true })) { - try vx.notify("vaxis", "hello from vaxis"); } else if (key.matches('z', .{ .ctrl = true })) { try openDirVim(alloc, &vx, "examples"); } else { @@ -102,7 +97,7 @@ pub fn main() !void { // more than one byte will incur an allocation on the first render // after it is drawn. Thereafter, it will not allocate unless the // screen is resized - .winsize => |ws| try vx.resize(alloc, ws), + .winsize => |ws| try vx.resize(alloc, tty.anyWriter(), ws), else => {}, } @@ -128,7 +123,7 @@ pub fn main() !void { text_input.draw(border.all(child, style)); // Render the screen - try vx.render(); + try vx.render(tty.anyWriter()); } } diff --git a/examples/readline.zig b/examples/readline.zig deleted file mode 100644 index 4c75970..0000000 --- a/examples/readline.zig +++ /dev/null @@ -1,75 +0,0 @@ -const std = @import("std"); -const vaxis = @import("vaxis"); -const Cell = vaxis.Cell; -const TextInput = vaxis.widgets.TextInput; -const border = vaxis.widgets.border; - -const log = std.log.scoped(.main); - -const Event = union(enum) { - key_press: vaxis.Key, - winsize: vaxis.Winsize, -}; - -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - defer { - const deinit_status = gpa.deinit(); - //fail test; can't try in defer as defer is executed after we return - if (deinit_status == .leak) { - log.err("memory leak", .{}); - } - } - const alloc = gpa.allocator(); - - var line: ?[]const u8 = null; - defer { - // do this in defer so that vaxis cleans up terminal state before we - // print to stdout - if (line) |_| { - const stdout = std.io.getStdOut().writer(); - stdout.print("\n{s}\n", .{line.?}) catch {}; - alloc.free(line.?); - } - } - var vx = try vaxis.init(alloc, .{}); - defer vx.deinit(alloc); - - var loop: vaxis.Loop(Event) = .{ .vaxis = &vx }; - try loop.run(); - defer loop.stop(); - - var text_input = TextInput.init(alloc, &vx.unicode); - defer text_input.deinit(); - - try vx.queryTerminal(); - - const prompt: vaxis.Segment = .{ .text = "$ " }; - - while (true) { - const event = loop.nextEvent(); - switch (event) { - .key_press => |key| { - if (key.matches('c', .{ .ctrl = true })) { - break; - } else if (key.matches(vaxis.Key.enter, .{})) { - line = try text_input.toOwnedSlice(); - text_input.clearAndFree(); - break; - } else { - try text_input.update(.{ .key_press = key }); - } - }, - .winsize => |ws| try vx.resize(alloc, ws), - } - - const win = vx.window(); - - win.clear(); - _ = try win.printSegment(prompt, .{}); - - const input_win = win.child(.{ .x_off = 2 }); - text_input.draw(input_win); - try vx.render(); - } -} diff --git a/examples/shell.zig b/examples/shell.zig deleted file mode 100644 index fb0c5df..0000000 --- a/examples/shell.zig +++ /dev/null @@ -1,126 +0,0 @@ -const std = @import("std"); -const vaxis = @import("vaxis"); -const Cell = vaxis.Cell; -const TextInput = vaxis.widgets.TextInput; - -const log = std.log.scoped(.main); -pub fn main() !void { - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; - defer { - const deinit_status = gpa.deinit(); - if (deinit_status == .leak) { - log.err("memory leak", .{}); - } - } - const alloc = gpa.allocator(); - - var vx = try vaxis.init(alloc, .{}); - defer vx.deinit(alloc); - - var loop: vaxis.Loop(Event) = .{ .vaxis = &vx }; - - try loop.run(); - defer loop.stop(); - - try vx.queryTerminal(); - - var text_input = TextInput.init(alloc, &vx.unicode); - defer text_input.deinit(); - - var selected_option: ?usize = null; - - const options = [_][]const u8{ - "yes", - "no", - }; - - // The main event loop. Vaxis provides a thread safe, blocking, buffered - // queue which can serve as the primary event queue for an application - while (true) { - // nextEvent blocks until an event is in the queue - const event = loop.nextEvent(); - // exhaustive switching ftw. Vaxis will send events if your Event - // enum has the fields for those events (ie "key_press", "winsize") - switch (event) { - .key_press => |key| { - if (key.codepoint == 'c' and key.mods.ctrl) { - break; - } else if (key.matches(vaxis.Key.tab, .{})) { - if (selected_option == null) { - selected_option = 0; - } else { - selected_option.? = @min(options.len - 1, selected_option.? + 1); - } - } else if (key.matches(vaxis.Key.tab, .{ .shift = true })) { - if (selected_option == null) { - selected_option = 0; - } else { - selected_option.? = selected_option.? -| 1; - } - } else if (key.matches(vaxis.Key.enter, .{})) { - if (selected_option) |i| { - log.err("enter", .{}); - try text_input.insertSliceAtCursor(options[i]); - selected_option = null; - } - } else { - if (selected_option == null) - try text_input.update(.{ .key_press = key }); - } - }, - .winsize => |ws| { - try vx.resize(alloc, ws); - }, - else => {}, - } - - const win = vx.window(); - win.clear(); - - const right_win = win.child(.{ - .x_off = win.width - 4, - }); - const left_win = win.child(.{ - .width = .{ .limit = 8 }, - }); - var right_prompt = [_]vaxis.Segment{ - .{ .text = "👩‍🚀🏳️‍🌈" }, - }; - var left_prompt = [_]vaxis.Segment{ - .{ .text = "👩‍🚀🏳️‍🌈~ ", .style = .{ .fg = .{ .index = 4 } } }, - .{ .text = "", .style = .{ .fg = .{ .index = 5 } } }, - }; - _ = try right_win.print(&right_prompt, .{}); - _ = try left_win.print(&left_prompt, .{}); - - const input_win = win.child(.{ - .x_off = 8, - .width = .{ .limit = win.width - 12 }, - }); - - text_input.draw(input_win); - - if (selected_option) |i| { - win.hideCursor(); - for (options, 0..) |opt, j| { - log.err("i = {d}, j = {d}, opt = {s}", .{ i, j, opt }); - var seg = [_]vaxis.Segment{.{ - .text = opt, - .style = if (j == i) .{ .reverse = true } else .{}, - }}; - _ = try win.print(&seg, .{ .row_offset = j + 1 }); - } - } - try vx.render(); - } -} - -// Our Event. This can contain internal events as well as Vaxis events. -// Internal events can be posted into the same queue as vaxis events to allow -// for a single event loop with exhaustive switching. Booya -const Event = union(enum) { - key_press: vaxis.Key, - winsize: vaxis.Winsize, - focus_in, - foo: u8, -}; diff --git a/examples/table.zig b/examples/table.zig index 4b17dba..3de8503 100644 --- a/examples/table.zig +++ b/examples/table.zig @@ -24,18 +24,20 @@ pub fn main() !void { const user_list = std.ArrayList(User).fromOwnedSlice(alloc, users_buf); defer user_list.deinit(); + var tty = try vaxis.Tty.init(); + var vx = try vaxis.init(alloc, .{}); - defer vx.deinit(alloc); + defer vx.deinit(alloc, tty.anyWriter()); var loop: vaxis.Loop(union(enum) { key_press: vaxis.Key, winsize: vaxis.Winsize, - }) = .{ .vaxis = &vx }; + }) = .{ .tty = &tty, .vaxis = &vx }; - try loop.run(); + try loop.start(); defer loop.stop(); - try vx.enterAltScreen(); - try vx.queryTerminal(); + try vx.enterAltScreen(tty.anyWriter()); + try vx.queryTerminal(tty.anyWriter(), 1 * std.time.ns_per_s); const logo = \\░█░█░█▀█░█░█░▀█▀░█▀▀░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░ @@ -145,7 +147,7 @@ pub fn main() !void { } moving = false; }, - .winsize => |ws| try vx.resize(alloc, ws), + .winsize => |ws| try vx.resize(alloc, tty.anyWriter(), ws), //else => {}, } @@ -203,7 +205,7 @@ pub fn main() !void { cmd_input.draw(bottom_bar); // Render the screen - try vx.render(); + try vx.render(tty.anyWriter()); } } diff --git a/examples/text_input.zig b/examples/text_input.zig index 168ee1b..650d937 100644 --- a/examples/text_input.zig +++ b/examples/text_input.zig @@ -40,7 +40,7 @@ pub fn main() !void { // Initialize Vaxis var vx = try vaxis.init(alloc, .{}); - defer vx.deinit(tty.anyWriter(), alloc); + defer vx.deinit(alloc, tty.anyWriter()); var loop: vaxis.Loop(Event) = .{ .vaxis = &vx, @@ -120,7 +120,7 @@ pub fn main() !void { // more than one byte will incur an allocation on the first render // after it is drawn. Thereafter, it will not allocate unless the // screen is resized - .winsize => |ws| try vx.resize(alloc, ws, tty.anyWriter()), + .winsize => |ws| try vx.resize(alloc, tty.anyWriter(), ws), else => {}, } diff --git a/examples/vaxis.zig b/examples/vaxis.zig index cf51a48..5f4dabb 100644 --- a/examples/vaxis.zig +++ b/examples/vaxis.zig @@ -18,16 +18,18 @@ pub fn main() !void { } const alloc = gpa.allocator(); + var tty = try vaxis.Tty.init(); var vx = try vaxis.init(alloc, .{}); - defer vx.deinit(alloc); + defer vx.deinit(alloc, tty.anyWriter()); - var loop: vaxis.Loop(Event) = .{ .vaxis = &vx }; + var loop: vaxis.Loop(Event) = .{ .tty = &tty, .vaxis = &vx }; + try loop.init(); - try loop.run(); + try loop.start(); defer loop.stop(); - try vx.enterAltScreen(); - try vx.queryTerminal(); + try vx.enterAltScreen(tty.anyWriter()); + try vx.queryTerminal(tty.anyWriter(), 1 * std.time.ns_per_s); const lower_limit = 30; var color_idx: u8 = lower_limit; @@ -42,7 +44,7 @@ pub fn main() !void { switch (event) { .key_press => |key| if (key.matches('c', .{ .ctrl = true })) return, .winsize => |ws| { - try vx.resize(alloc, ws); + try vx.resize(alloc, tty.anyWriter(), ws); break; }, } @@ -52,7 +54,7 @@ pub fn main() !void { while (loop.tryEvent()) |event| { switch (event) { .key_press => |key| if (key.matches('c', .{ .ctrl = true })) return, - .winsize => |ws| try vx.resize(alloc, ws), + .winsize => |ws| try vx.resize(alloc, tty.anyWriter(), ws), } } @@ -67,7 +69,7 @@ pub fn main() !void { }; const center = vaxis.widgets.alignment.center(win, 28, 4); _ = try center.printSegment(segment, .{ .wrap = .grapheme }); - try vx.render(); + try vx.render(tty.anyWriter()); std.time.sleep(8 * std.time.ns_per_ms); switch (dir) { .up => { diff --git a/src/Vaxis.zig b/src/Vaxis.zig index f095020..ecb253c 100644 --- a/src/Vaxis.zig +++ b/src/Vaxis.zig @@ -9,7 +9,6 @@ const InternalScreen = @import("InternalScreen.zig"); const Key = @import("Key.zig"); const Mouse = @import("Mouse.zig"); const Screen = @import("Screen.zig"); -const tty = @import("tty.zig"); const Unicode = @import("Unicode.zig"); const Window = @import("Window.zig"); @@ -18,7 +17,7 @@ const Hyperlink = Cell.Hyperlink; const KittyFlags = Key.KittyFlags; const Shape = Mouse.Shape; const Style = Cell.Style; -const Winsize = tty.Winsize; +const Winsize = @import("tty.zig").Winsize; const ctlseqs = @import("ctlseqs.zig"); const gwidth = @import("gwidth.zig"); @@ -57,6 +56,7 @@ opts: Options = .{}, /// if we should redraw the entire screen on the next render refresh: bool = false, +// FIXME: remove before committing /// blocks the main thread until a DA1 query has been received, or the /// futex times out query_futex: atomic.Value(u32) = atomic.Value(u32).init(0), @@ -106,40 +106,49 @@ pub fn init(alloc: std.mem.Allocator, opts: Options) !Vaxis { /// passed, this will free resources associated with Vaxis. This is left as an /// optional so applications can choose to not free resources when the /// application will be exiting anyways -pub fn deinit(self: *Vaxis, writer: AnyWriter, alloc: ?std.mem.Allocator) void { - self.resetState(writer) catch {}; +pub fn deinit(self: *Vaxis, alloc: ?std.mem.Allocator, tty: AnyWriter) void { + self.resetState(tty) catch {}; // always show the cursor on exit - writer.writeAll(ctlseqs.show_cursor) catch {}; + tty.writeAll(ctlseqs.show_cursor) catch {}; if (alloc) |a| { self.screen.deinit(a); self.screen_last.deinit(a); } if (self.renders > 0) { const tpr = @divTrunc(self.render_dur, self.renders); - log.debug("total renders = {d}", .{self.renders}); - log.debug("microseconds per render = {d}", .{tpr}); + log.debug("total renders = {d}\r", .{self.renders}); + log.debug("microseconds per render = {d}\r", .{tpr}); } self.unicode.deinit(); } -/// resets enabled features -pub fn resetState(self: *Vaxis, writer: AnyWriter) !void { +/// resets enabled features, sends cursor to home and clears below cursor +pub fn resetState(self: *Vaxis, tty: AnyWriter) !void { if (self.state.kitty_keyboard) { - try writer.writeAll(ctlseqs.csi_u_pop); + try tty.writeAll(ctlseqs.csi_u_pop); self.state.kitty_keyboard = false; } if (self.state.mouse) { - try self.setMouseMode(writer, false); + try self.setMouseMode(tty, false); } if (self.state.bracketed_paste) { - try self.setBracketedPaste(writer, false); + try self.setBracketedPaste(tty, false); } if (self.state.alt_screen) { - try self.exitAltScreen(writer); + try tty.writeAll(ctlseqs.home); + try tty.writeAll(ctlseqs.erase_below_cursor); + try self.exitAltScreen(tty); + } else { + try tty.writeByte('\r'); + var i: usize = 0; + while (i < self.state.cursor.row) : (i += 1) { + try tty.writeAll(ctlseqs.ri); + } + try tty.writeAll(ctlseqs.erase_below_cursor); } if (self.state.color_scheme_updates) { - try writer.writeAll(ctlseqs.color_scheme_reset); + try tty.writeAll(ctlseqs.color_scheme_reset); self.state.color_scheme_updates = false; } } @@ -148,7 +157,12 @@ pub fn resetState(self: *Vaxis, writer: AnyWriter) !void { /// required to display the screen (ie width x height). Any previous screen is /// freed when resizing. The cursor will be sent to it's home position and a /// hardware clear-below-cursor will be sent -pub fn resize(self: *Vaxis, alloc: std.mem.Allocator, winsize: Winsize, writer: AnyWriter) !void { +pub fn resize( + self: *Vaxis, + alloc: std.mem.Allocator, + tty: AnyWriter, + winsize: Winsize, +) !void { log.debug("resizing screen: width={d} height={d}", .{ winsize.cols, winsize.rows }); self.screen.deinit(alloc); self.screen = try Screen.init(alloc, winsize, &self.unicode); @@ -159,15 +173,15 @@ pub fn resize(self: *Vaxis, alloc: std.mem.Allocator, winsize: Winsize, writer: self.screen_last.deinit(alloc); self.screen_last = try InternalScreen.init(alloc, winsize.cols, winsize.rows); if (self.state.alt_screen) - try writer.writeAll(ctlseqs.home) + try tty.writeAll(ctlseqs.home) else { - try writer.writeByte('\r'); + try tty.writeByte('\r'); var i: usize = 0; while (i < self.state.cursor.row) : (i += 1) { - try writer.writeAll(ctlseqs.ri); + try tty.writeAll(ctlseqs.ri); } } - try writer.writeAll(ctlseqs.erase_below_cursor); + try tty.writeAll(ctlseqs.erase_below_cursor); } /// returns a Window comprising of the entire terminal screen @@ -183,33 +197,34 @@ pub fn window(self: *Vaxis) Window { /// enter the alternate screen. The alternate screen will automatically /// be exited if calling deinit while in the alt screen -pub fn enterAltScreen(self: *Vaxis, writer: AnyWriter) !void { - try writer.writeAll(ctlseqs.smcup); +pub fn enterAltScreen(self: *Vaxis, tty: AnyWriter) !void { + try tty.writeAll(ctlseqs.smcup); self.state.alt_screen = true; } /// exit the alternate screen -pub fn exitAltScreen(self: *Vaxis, writer: AnyWriter) !void { - try writer.writeAll(ctlseqs.rmcup); +pub fn exitAltScreen(self: *Vaxis, tty: AnyWriter) !void { + try tty.writeAll(ctlseqs.rmcup); self.state.alt_screen = false; } /// write queries to the terminal to determine capabilities. Individual /// capabilities will be delivered to the client and possibly intercepted by -/// Vaxis to enable features -pub fn queryTerminal(self: *Vaxis) !void { - try self.queryTerminalSend(); - +/// Vaxis to enable features. +/// +/// This call will block until Vaxis.query_futex is woken up, or the timeout. +/// Event loops can wake up this futex when cap_da1 is received +pub fn queryTerminal(self: *Vaxis, tty: AnyWriter, timeout_ns: u64) !void { + try self.queryTerminalSend(tty); // 1 second timeout - std.Thread.Futex.timedWait(&self.query_futex, 0, 1 * std.time.ns_per_s) catch {}; - - try self.enableDetectedFeatures(); + std.Thread.Futex.timedWait(&self.query_futex, 0, timeout_ns) catch {}; + try self.enableDetectedFeatures(tty); } /// write queries to the terminal to determine capabilities. This function /// is only for use with a custom main loop. Call Vaxis.queryTerminal() if /// you are using Loop.run() -pub fn queryTerminalSend(_: Vaxis, writer: AnyWriter) !void { +pub fn queryTerminalSend(_: Vaxis, tty: AnyWriter) !void { // TODO: re-enable this // const colorterm = std.posix.getenv("COLORTERM") orelse ""; @@ -221,30 +236,24 @@ pub fn queryTerminalSend(_: Vaxis, writer: AnyWriter) !void { // } // } + // TODO: XTGETTCAP queries ("RGB", "Smulx") // TODO: decide if we actually want to query for focus and sync. It // doesn't hurt to blindly use them // _ = try tty.write(ctlseqs.decrqm_focus); // _ = try tty.write(ctlseqs.decrqm_sync); - try writer.writeAll(ctlseqs.decrqm_sgr_pixels); - try writer.writeAll(ctlseqs.decrqm_unicode); - try writer.writeAll(ctlseqs.decrqm_color_scheme); - // TODO: XTVERSION has a DCS response. uncomment when we can parse - // that - // _ = try tty.write(ctlseqs.xtversion); - try writer.writeAll(ctlseqs.csi_u_query); - try writer.writeAll(ctlseqs.kitty_graphics_query); - // TODO: sixel geometry query interferes with F4 keys. - // _ = try tty.write(ctlseqs.sixel_geometry_query); - - // TODO: XTGETTCAP queries ("RGB", "Smulx") - - try writer.writeAll(ctlseqs.primary_device_attrs); + try tty.writeAll(ctlseqs.decrqm_sgr_pixels ++ + ctlseqs.decrqm_unicode ++ + ctlseqs.decrqm_color_scheme ++ + ctlseqs.xtversion ++ + ctlseqs.csi_u_query ++ + ctlseqs.kitty_graphics_query ++ + ctlseqs.primary_device_attrs); } /// Enable features detected by responses to queryTerminal. This function /// is only for use with a custom main loop. Call Vaxis.queryTerminal() if /// you are using Loop.run() -pub fn enableDetectedFeatures(self: *Vaxis) !void { +pub fn enableDetectedFeatures(self: *Vaxis, tty: AnyWriter) !void { // Apply any environment variables if (std.posix.getenv("ASCIINEMA_REC")) |_| self.sgr = .legacy; @@ -263,10 +272,10 @@ pub fn enableDetectedFeatures(self: *Vaxis) !void { // enable detected features if (self.caps.kitty_keyboard) { - try self.enableKittyKeyboard(self.opts.kitty_keyboard_flags); + try self.enableKittyKeyboard(tty, self.opts.kitty_keyboard_flags); } if (self.caps.unicode == .unicode) { - _ = try tty.write(ctlseqs.unicode_set); + try tty.writeAll(ctlseqs.unicode_set); } } @@ -276,7 +285,7 @@ pub fn queueRefresh(self: *Vaxis) void { } /// draws the screen to the terminal -pub fn render(self: *Vaxis, writer: AnyWriter) !void { +pub fn render(self: *Vaxis, tty: AnyWriter) !void { self.renders += 1; self.render_timer.reset(); defer { @@ -289,24 +298,21 @@ pub fn render(self: *Vaxis, writer: AnyWriter) !void { // TODO: optimize sync so we only sync _when we have changes_. This // requires a smarter buffered writer, we'll probably have to write // our own - try writer.writeAll(ctlseqs.sync_set); - defer writer.writeAll(ctlseqs.sync_reset) catch {}; + try tty.writeAll(ctlseqs.sync_set); + defer tty.writeAll(ctlseqs.sync_reset) catch {}; // Send the cursor to 0,0 // TODO: this needs to move after we optimize writes. We only do // this if we have an update to make. We also need to hide cursor // and then reshow it if needed - try writer.writeAll(ctlseqs.hide_cursor); + try tty.writeAll(ctlseqs.hide_cursor); if (self.state.alt_screen) - try writer.writeAll(ctlseqs.home) + try tty.writeAll(ctlseqs.home) else { - try writer.writeAll("\r"); - var i: usize = 0; - while (i < self.state.cursor.row) : (i += 1) { - try writer.writeAll(ctlseqs.ri); - } + try tty.writeByte('\r'); + try tty.writeBytesNTimes(ctlseqs.ri, self.state.cursor.row); } - try writer.writeAll(ctlseqs.sgr_reset); + try tty.writeAll(ctlseqs.sgr_reset); // initialize some variables var reposition: bool = false; @@ -321,7 +327,7 @@ pub fn render(self: *Vaxis, writer: AnyWriter) !void { // Clear all images if (self.caps.kitty_graphics) - try writer.writeAll(ctlseqs.kitty_graphics_clear); + try tty.writeAll(ctlseqs.kitty_graphics_clear); var i: usize = 0; while (i < self.screen.buf.len) { @@ -357,7 +363,7 @@ pub fn render(self: *Vaxis, writer: AnyWriter) !void { // Close any osc8 sequence we might be in before // repositioning if (link.uri.len > 0) { - try writer.writeAll(ctlseqs.osc8_clear); + try tty.writeAll(ctlseqs.osc8_clear); } continue; } @@ -373,52 +379,53 @@ pub fn render(self: *Vaxis, writer: AnyWriter) !void { if (reposition) { reposition = false; if (self.state.alt_screen) - try writer.print(ctlseqs.cup, .{ row + 1, col + 1 }) + try tty.print(ctlseqs.cup, .{ row + 1, col + 1 }) else { if (cursor_pos.row == row) { const n = col - cursor_pos.col; - try writer.print(ctlseqs.cuf, .{n}); + if (n > 0) + try tty.print(ctlseqs.cuf, .{n}); } else { - try writer.writeByte('\r'); + try tty.writeByte('\r'); const n = row - cursor_pos.row; - try writer.writeByteNTimes('\n', n); + try tty.writeByteNTimes('\n', n); + if (col > 0) + try tty.print(ctlseqs.cuf, .{col}); } - if (col > 0) - try writer.print(ctlseqs.cuf, .{col}); } } if (cell.image) |img| { - try writer.print( + try tty.print( ctlseqs.kitty_graphics_preamble, .{img.img_id}, ); if (img.options.pixel_offset) |offset| { - try writer.print( + try tty.print( ",X={d},Y={d}", .{ offset.x, offset.y }, ); } if (img.options.clip_region) |clip| { if (clip.x) |x| - try writer.print(",x={d}", .{x}); + try tty.print(",x={d}", .{x}); if (clip.y) |y| - try writer.print(",y={d}", .{y}); + try tty.print(",y={d}", .{y}); if (clip.width) |width| - try writer.print(",w={d}", .{width}); + try tty.print(",w={d}", .{width}); if (clip.height) |height| - try writer.print(",h={d}", .{height}); + try tty.print(",h={d}", .{height}); } if (img.options.size) |size| { if (size.rows) |rows| - try writer.print(",r={d}", .{rows}); + try tty.print(",r={d}", .{rows}); if (size.cols) |cols| - try writer.print(",c={d}", .{cols}); + try tty.print(",c={d}", .{cols}); } if (img.options.z_index) |z| { - try writer.print(",z={d}", .{z}); + try tty.print(",z={d}", .{z}); } - try writer.writeAll(ctlseqs.kitty_graphics_closing); + try tty.writeAll(ctlseqs.kitty_graphics_closing); } // something is different, so let's loop through everything and @@ -427,23 +434,23 @@ pub fn render(self: *Vaxis, writer: AnyWriter) !void { // foreground if (!Cell.Color.eql(cursor.fg, cell.style.fg)) { switch (cell.style.fg) { - .default => try writer.writeAll(ctlseqs.fg_reset), + .default => try tty.writeAll(ctlseqs.fg_reset), .index => |idx| { switch (idx) { - 0...7 => try writer.print(ctlseqs.fg_base, .{idx}), - 8...15 => try writer.print(ctlseqs.fg_bright, .{idx - 8}), + 0...7 => try tty.print(ctlseqs.fg_base, .{idx}), + 8...15 => try tty.print(ctlseqs.fg_bright, .{idx - 8}), else => { switch (self.sgr) { - .standard => try writer.print(ctlseqs.fg_indexed, .{idx}), - .legacy => try writer.print(ctlseqs.fg_indexed_legacy, .{idx}), + .standard => try tty.print(ctlseqs.fg_indexed, .{idx}), + .legacy => try tty.print(ctlseqs.fg_indexed_legacy, .{idx}), } }, } }, .rgb => |rgb| { switch (self.sgr) { - .standard => try writer.print(ctlseqs.fg_rgb, .{ rgb[0], rgb[1], rgb[2] }), - .legacy => try writer.print(ctlseqs.fg_rgb_legacy, .{ rgb[0], rgb[1], rgb[2] }), + .standard => try tty.print(ctlseqs.fg_rgb, .{ rgb[0], rgb[1], rgb[2] }), + .legacy => try tty.print(ctlseqs.fg_rgb_legacy, .{ rgb[0], rgb[1], rgb[2] }), } }, } @@ -451,23 +458,23 @@ pub fn render(self: *Vaxis, writer: AnyWriter) !void { // background if (!Cell.Color.eql(cursor.bg, cell.style.bg)) { switch (cell.style.bg) { - .default => try writer.writeAll(ctlseqs.bg_reset), + .default => try tty.writeAll(ctlseqs.bg_reset), .index => |idx| { switch (idx) { - 0...7 => try writer.print(ctlseqs.bg_base, .{idx}), - 8...15 => try writer.print(ctlseqs.bg_bright, .{idx - 8}), + 0...7 => try tty.print(ctlseqs.bg_base, .{idx}), + 8...15 => try tty.print(ctlseqs.bg_bright, .{idx - 8}), else => { switch (self.sgr) { - .standard => try writer.print(ctlseqs.bg_indexed, .{idx}), - .legacy => try writer.print(ctlseqs.bg_indexed_legacy, .{idx}), + .standard => try tty.print(ctlseqs.bg_indexed, .{idx}), + .legacy => try tty.print(ctlseqs.bg_indexed_legacy, .{idx}), } }, } }, .rgb => |rgb| { switch (self.sgr) { - .standard => try writer.print(ctlseqs.bg_rgb, .{ rgb[0], rgb[1], rgb[2] }), - .legacy => try writer.print(ctlseqs.bg_rgb_legacy, .{ rgb[0], rgb[1], rgb[2] }), + .standard => try tty.print(ctlseqs.bg_rgb, .{ rgb[0], rgb[1], rgb[2] }), + .legacy => try tty.print(ctlseqs.bg_rgb_legacy, .{ rgb[0], rgb[1], rgb[2] }), } }, } @@ -475,17 +482,17 @@ pub fn render(self: *Vaxis, writer: AnyWriter) !void { // underline color if (!Cell.Color.eql(cursor.ul, cell.style.ul)) { switch (cell.style.bg) { - .default => try writer.writeAll(ctlseqs.ul_reset), + .default => try tty.writeAll(ctlseqs.ul_reset), .index => |idx| { switch (self.sgr) { - .standard => try writer.print(ctlseqs.ul_indexed, .{idx}), - .legacy => try writer.print(ctlseqs.ul_indexed_legacy, .{idx}), + .standard => try tty.print(ctlseqs.ul_indexed, .{idx}), + .legacy => try tty.print(ctlseqs.ul_indexed_legacy, .{idx}), } }, .rgb => |rgb| { switch (self.sgr) { - .standard => try writer.print(ctlseqs.ul_rgb, .{ rgb[0], rgb[1], rgb[2] }), - .legacy => try writer.print(ctlseqs.ul_rgb_legacy, .{ rgb[0], rgb[1], rgb[2] }), + .standard => try tty.print(ctlseqs.ul_rgb, .{ rgb[0], rgb[1], rgb[2] }), + .legacy => try tty.print(ctlseqs.ul_rgb_legacy, .{ rgb[0], rgb[1], rgb[2] }), } }, } @@ -500,7 +507,7 @@ pub fn render(self: *Vaxis, writer: AnyWriter) !void { .dotted => ctlseqs.ul_dotted, .dashed => ctlseqs.ul_dashed, }; - try writer.writeAll(seq); + try tty.writeAll(seq); } // bold if (cursor.bold != cell.style.bold) { @@ -508,9 +515,9 @@ pub fn render(self: *Vaxis, writer: AnyWriter) !void { true => ctlseqs.bold_set, false => ctlseqs.bold_dim_reset, }; - try writer.writeAll(seq); + try tty.writeAll(seq); if (cell.style.dim) { - try writer.writeAll(ctlseqs.dim_set); + try tty.writeAll(ctlseqs.dim_set); } } // dim @@ -519,9 +526,9 @@ pub fn render(self: *Vaxis, writer: AnyWriter) !void { true => ctlseqs.dim_set, false => ctlseqs.bold_dim_reset, }; - try writer.writeAll(seq); + try tty.writeAll(seq); if (cell.style.bold) { - try writer.writeAll(ctlseqs.bold_set); + try tty.writeAll(ctlseqs.bold_set); } } // dim @@ -530,7 +537,7 @@ pub fn render(self: *Vaxis, writer: AnyWriter) !void { true => ctlseqs.italic_set, false => ctlseqs.italic_reset, }; - try writer.writeAll(seq); + try tty.writeAll(seq); } // dim if (cursor.blink != cell.style.blink) { @@ -538,7 +545,7 @@ pub fn render(self: *Vaxis, writer: AnyWriter) !void { true => ctlseqs.blink_set, false => ctlseqs.blink_reset, }; - try writer.writeAll(seq); + try tty.writeAll(seq); } // reverse if (cursor.reverse != cell.style.reverse) { @@ -546,7 +553,7 @@ pub fn render(self: *Vaxis, writer: AnyWriter) !void { true => ctlseqs.reverse_set, false => ctlseqs.reverse_reset, }; - try writer.writeAll(seq); + try tty.writeAll(seq); } // invisible if (cursor.invisible != cell.style.invisible) { @@ -554,7 +561,7 @@ pub fn render(self: *Vaxis, writer: AnyWriter) !void { true => ctlseqs.invisible_set, false => ctlseqs.invisible_reset, }; - try writer.writeAll(seq); + try tty.writeAll(seq); } // strikethrough if (cursor.strikethrough != cell.style.strikethrough) { @@ -562,7 +569,7 @@ pub fn render(self: *Vaxis, writer: AnyWriter) !void { true => ctlseqs.strikethrough_set, false => ctlseqs.strikethrough_reset, }; - try writer.writeAll(seq); + try tty.writeAll(seq); } // url @@ -573,15 +580,15 @@ pub fn render(self: *Vaxis, writer: AnyWriter) !void { // a url ps = ""; } - try writer.print(ctlseqs.osc8, .{ ps, cell.link.uri }); + try tty.print(ctlseqs.osc8, .{ ps, cell.link.uri }); } - try writer.writeAll(cell.char.grapheme); + try tty.writeAll(cell.char.grapheme); cursor_pos.col = col + w; cursor_pos.row = row; } if (self.screen.cursor_vis) { if (self.state.alt_screen) { - try writer.print( + try tty.print( ctlseqs.cup, .{ self.screen.cursor_row + 1, @@ -590,30 +597,30 @@ pub fn render(self: *Vaxis, writer: AnyWriter) !void { ); } else { // TODO: position cursor relative to current location - try writer.writeByte('\r'); + try tty.writeByte('\r'); if (self.screen.cursor_row >= cursor_pos.row) - try writer.writeByteNTimes('\n', self.screen.cursor_row - cursor_pos.row) + try tty.writeByteNTimes('\n', self.screen.cursor_row - cursor_pos.row) else - try writer.writeBytesNTimes(ctlseqs.ri, cursor_pos.row - self.screen.cursor_row); + try tty.writeBytesNTimes(ctlseqs.ri, cursor_pos.row - self.screen.cursor_row); if (self.screen.cursor_col > 0) - try writer.print(ctlseqs.cuf, .{self.screen.cursor_col}); + try tty.print(ctlseqs.cuf, .{self.screen.cursor_col}); } self.state.cursor.row = self.screen.cursor_row; self.state.cursor.col = self.screen.cursor_col; - try writer.writeAll(ctlseqs.show_cursor); + try tty.writeAll(ctlseqs.show_cursor); } else { self.state.cursor.row = cursor_pos.row; self.state.cursor.col = cursor_pos.col; } if (self.screen.mouse_shape != self.screen_last.mouse_shape) { - try writer.print( + try tty.print( ctlseqs.osc22_mouse_shape, .{@tagName(self.screen.mouse_shape)}, ); self.screen_last.mouse_shape = self.screen.mouse_shape; } if (self.screen.cursor_shape != self.screen_last.cursor_shape) { - try writer.print( + try tty.print( ctlseqs.cursor_shape, .{@intFromEnum(self.screen.cursor_shape)}, ); @@ -621,34 +628,34 @@ pub fn render(self: *Vaxis, writer: AnyWriter) !void { } } -fn enableKittyKeyboard(self: *Vaxis, writer: AnyWriter, flags: Key.KittyFlags) !void { +fn enableKittyKeyboard(self: *Vaxis, tty: AnyWriter, flags: Key.KittyFlags) !void { const flag_int: u5 = @bitCast(flags); - try writer.print(ctlseqs.csi_u_push, .{flag_int}); + try tty.print(ctlseqs.csi_u_push, .{flag_int}); self.state.kitty_keyboard = true; } /// send a system notification -pub fn notify(_: *Vaxis, writer: AnyWriter, title: ?[]const u8, body: []const u8) !void { +pub fn notify(_: *Vaxis, tty: AnyWriter, title: ?[]const u8, body: []const u8) !void { if (title) |t| - try writer.print(ctlseqs.osc777_notify, .{ t, body }) + try tty.print(ctlseqs.osc777_notify, .{ t, body }) else - try writer.print(ctlseqs.osc9_notify, .{body}); + try tty.print(ctlseqs.osc9_notify, .{body}); } /// sets the window title -pub fn setTitle(_: *Vaxis, writer: AnyWriter, title: []const u8) !void { - try writer.print(ctlseqs.osc2_set_title, .{title}); +pub fn setTitle(_: *Vaxis, tty: AnyWriter, title: []const u8) !void { + try tty.print(ctlseqs.osc2_set_title, .{title}); } // turn bracketed paste on or off. An event will be sent at the // beginning and end of a detected paste. All keystrokes between these // events were pasted -pub fn setBracketedPaste(self: *Vaxis, writer: AnyWriter, enable: bool) !void { +pub fn setBracketedPaste(self: *Vaxis, tty: AnyWriter, enable: bool) !void { const seq = if (enable) ctlseqs.bp_set else ctlseqs.bp_reset; - try writer.writeAll(seq); + try tty.writeAll(seq); self.state.bracketed_paste = enable; } @@ -658,19 +665,19 @@ pub fn setMouseShape(self: *Vaxis, shape: Shape) void { } /// Change the mouse reporting mode -pub fn setMouseMode(self: *Vaxis, writer: AnyWriter, enable: bool) !void { +pub fn setMouseMode(self: *Vaxis, tty: AnyWriter, enable: bool) !void { if (enable) { self.state.mouse = true; if (self.caps.sgr_pixels) { log.debug("enabling mouse mode: pixel coordinates", .{}); self.state.pixel_mouse = true; - try writer.writeAll(ctlseqs.mouse_set_pixels); + try tty.writeAll(ctlseqs.mouse_set_pixels); } else { log.debug("enabling mouse mode: cell coordinates", .{}); - try writer.writeAll(ctlseqs.mouse_set); + try tty.writeAll(ctlseqs.mouse_set); } } else { - try writer.writeAll(ctlseqs.mouse_reset); + try tty.writeAll(ctlseqs.mouse_reset); } } @@ -709,7 +716,7 @@ pub fn translateMouse(self: Vaxis, mouse: Mouse) Mouse { pub fn loadImage( self: *Vaxis, alloc: std.mem.Allocator, - writer: AnyWriter, + tty: AnyWriter, src: Image.Source, ) !Image { if (!self.caps.kitty_graphics) return error.NoGraphicsCapability; @@ -730,7 +737,7 @@ pub fn loadImage( const id = self.next_img_id; if (encoded.len < 4096) { - try writer.print( + try tty.print( "\x1b_Gf=100,i={d};{s}\x1b\\", .{ id, @@ -740,14 +747,14 @@ pub fn loadImage( } else { var n: usize = 4096; - try writer.print( + try tty.print( "\x1b_Gf=100,i={d},m=1;{s}\x1b\\", .{ id, encoded[0..n] }, ); while (n < encoded.len) : (n += 4096) { const end: usize = @min(n + 4096, encoded.len); const m: u2 = if (end == encoded.len) 0 else 1; - try writer.print( + try tty.print( "\x1b_Gm={d};{s}\x1b\\", .{ m, @@ -764,28 +771,28 @@ pub fn loadImage( } /// deletes an image from the terminal's memory -pub fn freeImage(_: Vaxis, writer: AnyWriter, id: u32) void { - writer.print("\x1b_Ga=d,d=I,i={d};\x1b\\", .{id}) catch |err| { +pub fn freeImage(_: Vaxis, tty: AnyWriter, id: u32) void { + tty.print("\x1b_Ga=d,d=I,i={d};\x1b\\", .{id}) catch |err| { log.err("couldn't delete image {d}: {}", .{ id, err }); return; }; } -pub fn copyToSystemClipboard(_: Vaxis, writer: AnyWriter, text: []const u8, encode_allocator: std.mem.Allocator) !void { +pub fn copyToSystemClipboard(_: Vaxis, tty: AnyWriter, text: []const u8, encode_allocator: std.mem.Allocator) !void { const encoder = std.base64.standard.Encoder; const size = encoder.calcSize(text.len); const buf = try encode_allocator.alloc(u8, size); const b64 = encoder.encode(buf, text); defer encode_allocator.free(buf); - try writer.print( + try tty.print( ctlseqs.osc52_clipboard_copy, .{b64}, ); } -pub fn requestSystemClipboard(self: Vaxis, writer: AnyWriter) !void { +pub fn requestSystemClipboard(self: Vaxis, tty: AnyWriter) !void { if (self.opts.system_clipboard_allocator == null) return error.NoClipboardAllocator; - try writer.print( + try tty.print( ctlseqs.osc52_clipboard_request, .{}, ); @@ -794,12 +801,12 @@ pub fn requestSystemClipboard(self: Vaxis, writer: AnyWriter) !void { /// Request a color report from the terminal. Note: not all terminals support /// reporting colors. It is always safe to try, but you may not receive a /// response. -pub fn queryColor(_: Vaxis, writer: AnyWriter, kind: Cell.Color.Kind) !void { +pub fn queryColor(_: Vaxis, tty: AnyWriter, kind: Cell.Color.Kind) !void { switch (kind) { - .fg => try writer.writeAll(ctlseqs.osc10_query), - .bg => try writer.writeAll(ctlseqs.osc11_query), - .cursor => try writer.writeAll(ctlseqs.osc12_query), - .index => |idx| try writer.print(ctlseqs.osc4_query, .{idx}), + .fg => try tty.writeAll(ctlseqs.osc10_query), + .bg => try tty.writeAll(ctlseqs.osc11_query), + .cursor => try tty.writeAll(ctlseqs.osc12_query), + .index => |idx| try tty.print(ctlseqs.osc4_query, .{idx}), } } @@ -808,12 +815,12 @@ pub fn queryColor(_: Vaxis, writer: AnyWriter, kind: Cell.Color.Kind) !void { /// capability. Support can be detected by checking the value of /// vaxis.caps.color_scheme_updates. The initial scheme will be reported when /// subscribing. -pub fn subscribeToColorSchemeUpdates(self: Vaxis, writer: AnyWriter) !void { - try writer.writeAll(ctlseqs.color_scheme_request); - try writer.writeAll(ctlseqs.color_scheme_set); +pub fn subscribeToColorSchemeUpdates(self: Vaxis, tty: AnyWriter) !void { + try tty.writeAll(ctlseqs.color_scheme_request); + try tty.writeAll(ctlseqs.color_scheme_set); self.state.color_scheme_updates = true; } -pub fn deviceStatusReport(_: Vaxis, writer: AnyWriter) !void { - try writer.writeAll(ctlseqs.device_status_report); +pub fn deviceStatusReport(_: Vaxis, tty: AnyWriter) !void { + try tty.writeAll(ctlseqs.device_status_report); }