this repo has no description
13
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(vaxis): require an io.AnyWriter for writes

All vaxis functions that write to the terminal now require an
io.AnyWriter as a parameter

+294 -517
+59 -38
README.md
··· 15 15 16 16 Vaxis uses zig `0.12.0`. 17 17 18 - ## Feature comparison 18 + ## Features 19 19 20 - | Feature | Vaxis | libvaxis | notcurses | 21 - | ------------------------------ | :---: | :------: | :-------: | 22 - | RGB | ✅ | ✅ | ✅ | 23 - | Hyperlinks | ✅ | ✅ | ❌ | 24 - | Bracketed Paste | ✅ | ✅ | ❌ | 25 - | Kitty Keyboard | ✅ | ✅ | ✅ | 26 - | Styled Underlines | ✅ | ✅ | ✅ | 27 - | Mouse Shapes (OSC 22) | ✅ | ✅ | ❌ | 28 - | System Clipboard (OSC 52) | ✅ | ✅ | ❌ | 29 - | System Notifications (OSC 9) | ✅ | ✅ | ❌ | 30 - | System Notifications (OSC 777) | ✅ | ✅ | ❌ | 31 - | Synchronized Output (DEC 2026) | ✅ | ✅ | ✅ | 32 - | Unicode Core (DEC 2027) | ✅ | ✅ | ❌ | 33 - | Color Mode Updates (DEC 2031) | ✅ | ✅ | ❌ | 34 - | Images (full/space) | ✅ | planned | ✅ | 35 - | Images (half block) | ✅ | planned | ✅ | 36 - | Images (quadrant) | ✅ | planned | ✅ | 37 - | Images (sextant) | ❌ | ❌ | ✅ | 38 - | Images (sixel) | ✅ | ❌ | ✅ | 39 - | Images (kitty) | ✅ | ✅ | ✅ | 40 - | Images (iterm2) | ❌ | ❌ | ✅ | 41 - | Video | ❌ | ❌ | ✅ | 42 - | Dank | 🆗 | 🆗 | ✅ | 20 + | Feature | libvaxis | 21 + | ------------------------------ | :------: | 22 + | RGB | ✅ | 23 + | Hyperlinks | ✅ | 24 + | Bracketed Paste | ✅ | 25 + | Kitty Keyboard | ✅ | 26 + | Styled Underlines | ✅ | 27 + | Mouse Shapes (OSC 22) | ✅ | 28 + | System Clipboard (OSC 52) | ✅ | 29 + | System Notifications (OSC 9) | ✅ | 30 + | System Notifications (OSC 777) | ✅ | 31 + | Synchronized Output (DEC 2026) | ✅ | 32 + | Unicode Core (DEC 2027) | ✅ | 33 + | Color Mode Updates (DEC 2031) | ✅ | 34 + | Images (kitty) | ✅ | 43 35 44 36 ## Usage 45 37 46 38 [Documentation](https://rockorager.github.io/libvaxis/#vaxis.Vaxis) 47 39 48 - The below example can be run using `zig build run 2>log`. stderr must be 49 - redirected in order to not print to the same screen. 40 + Vaxis requires three basic primitives to operate: 41 + 42 + 1. A TTY instance 43 + 2. An instance of Vaxis 44 + 3. An event loop 45 + 46 + The library provides a general purpose posix TTY implementation, as well as a 47 + multi-threaded event loop implementation. Users of the library are encouraged to 48 + use the event loop of their choice. The event loop is responsible for reading 49 + the TTY, passing the read bytes to the vaxis parser, and handling events. 50 + 51 + A core feature of Vaxis is it's ability to detect features via terminal queries 52 + instead of relying on a terminfo database. This requires that the event loop 53 + also handle these query responses and update the Vaxis.caps struct accordingly. 54 + See the `Loop` implementation to see how this is done if writing your own event 55 + loop. 56 + 57 + ## Example 50 58 51 59 ```zig 52 60 const std = @import("std"); ··· 76 84 } 77 85 const alloc = gpa.allocator(); 78 86 87 + // Initialize a tty 88 + var tty = try vaxis.Tty.init(); 89 + defer tty.deinit(); 90 + 79 91 // Initialize Vaxis 80 92 var vx = try vaxis.init(alloc, .{}); 81 93 // deinit takes an optional allocator. If your program is exiting, you can 82 94 // choose to pass a null allocator to save some exit time. 83 - defer vx.deinit(alloc); 95 + defer vx.deinit(alloc, tty.anyWriter()); 84 96 85 97 86 - var loop: vaxis.Loop(Event) = .{}; 98 + // The event loop requires an intrusive init. We create an instance with 99 + // stable points to Vaxis and our TTY, then init the instance. Doing so 100 + // installs a signal handler for SIGWINCH on posix TTYs 101 + // 102 + // This event loop is thread safe. It reads the tty in a separate thread 103 + var loop: vaxis.Loop(Event) = .{ 104 + .tty = &tty, 105 + .vaxis = &vaxis, 106 + }; 107 + try loop.init(); 108 + 87 109 // Start the read loop. This puts the terminal in raw mode and begins 88 110 // reading user input 89 - try loop.run(); 111 + try loop.start(); 90 112 defer loop.stop(); 91 113 92 114 // Optionally enter the alternate screen 93 - try vx.enterAltScreen(); 115 + try vx.enterAltScreen(tty.anyWriter()); 94 116 95 117 // We'll adjust the color index every keypress for the border 96 118 var color_idx: u8 = 0; ··· 100 122 var text_input = TextInput.init(alloc); 101 123 defer text_input.deinit(); 102 124 103 - // Sends queries to terminal to detect certain features. This should 104 - // _always_ be called, but is left to the application to decide when 105 - try vx.queryTerminal(); 125 + // Sends queries to terminal to detect certain features. This should always 126 + // be called after entering the alt screen, if you are using the alt screen 127 + try vx.queryTerminal(tty.anyWriter(), 1 * std.time.ns_per_s); 106 128 107 - // The main event loop. Vaxis provides a thread safe, blocking, buffered 108 - // queue which can serve as the primary event queue for an application 109 129 while (true) { 110 130 // nextEvent blocks until an event is in the queue 111 131 const event = loop.nextEvent(); ··· 141 161 // more than one byte will incur an allocation on the first render 142 162 // after it is drawn. Thereafter, it will not allocate unless the 143 163 // screen is resized 144 - .winsize => |ws| try vx.resize(alloc, ws), 164 + .winsize => |ws| try vx.resize(alloc, tty.anyWriter(), ws), 145 165 else => {}, 146 166 } 147 167 ··· 175 195 // Draw the text_input in the child window 176 196 text_input.draw(child); 177 197 178 - // Render the screen 179 - try vx.render(); 198 + // Render the screen. Using a buffered writer will offer much better 199 + // performance, but is not required 200 + try vx.render(tty.anyWriter()); 180 201 } 181 202 } 182 203 ```
+10 -6
examples/cli.zig
··· 14 14 } 15 15 const alloc = gpa.allocator(); 16 16 17 + var tty = try vaxis.Tty.init(); 18 + defer tty.deinit(); 19 + 17 20 var vx = try vaxis.init(alloc, .{}); 18 - defer vx.deinit(alloc); 21 + defer vx.deinit(alloc, tty.anyWriter()); 19 22 20 - var loop: vaxis.Loop(Event) = .{ .vaxis = &vx }; 23 + var loop: vaxis.Loop(Event) = .{ .tty = &tty, .vaxis = &vx }; 24 + try loop.init(); 21 25 22 - try loop.run(); 26 + try loop.start(); 23 27 defer loop.stop(); 24 28 25 - try vx.queryTerminal(); 29 + try vx.queryTerminal(tty.anyWriter(), 1 * std.time.ns_per_s); 26 30 27 31 var text_input = TextInput.init(alloc, &vx.unicode); 28 32 defer text_input.deinit(); ··· 70 74 } 71 75 }, 72 76 .winsize => |ws| { 73 - try vx.resize(alloc, ws); 77 + try vx.resize(alloc, tty.anyWriter(), ws); 74 78 }, 75 79 else => {}, 76 80 } ··· 91 95 _ = try win.print(&seg, .{ .row_offset = j + 1 }); 92 96 } 93 97 } 94 - try vx.render(); 98 + try vx.render(tty.anyWriter()); 95 99 } 96 100 } 97 101
+15 -12
examples/image.zig
··· 18 18 } 19 19 const alloc = gpa.allocator(); 20 20 21 + var tty = try vaxis.Tty.init(); 22 + defer tty.deinit(); 23 + 21 24 var vx = try vaxis.init(alloc, .{}); 22 - defer vx.deinit(alloc); 25 + defer vx.deinit(alloc, tty.anyWriter()); 23 26 24 - var loop: vaxis.Loop(Event) = .{ .vaxis = &vx }; 27 + var loop: vaxis.Loop(Event) = .{ .tty = &tty, .vaxis = &vx }; 28 + try loop.init(); 25 29 26 - try loop.run(); 30 + try loop.start(); 27 31 defer loop.stop(); 28 32 29 - try vx.enterAltScreen(); 30 - 31 - try vx.queryTerminal(); 33 + try vx.enterAltScreen(tty.anyWriter()); 34 + try vx.queryTerminal(tty.anyWriter(), 1 * std.time.ns_per_s); 32 35 33 36 const imgs = [_]vaxis.Image{ 34 - try vx.loadImage(alloc, .{ .path = "examples/zig.png" }), 35 - try vx.loadImage(alloc, .{ .path = "examples/vaxis.png" }), 37 + try vx.loadImage(alloc, tty.anyWriter(), .{ .path = "examples/zig.png" }), 38 + try vx.loadImage(alloc, tty.anyWriter(), .{ .path = "examples/vaxis.png" }), 36 39 }; 37 - defer vx.freeImage(imgs[0].id); 38 - defer vx.freeImage(imgs[1].id); 40 + defer vx.freeImage(tty.anyWriter(), imgs[0].id); 41 + defer vx.freeImage(tty.anyWriter(), imgs[1].id); 39 42 40 43 var n: usize = 0; 41 44 ··· 54 57 else if (key.matches('k', .{})) 55 58 clip_y -|= 1; 56 59 }, 57 - .winsize => |ws| try vx.resize(alloc, ws), 60 + .winsize => |ws| try vx.resize(alloc, tty.anyWriter(), ws), 58 61 } 59 62 60 63 n = (n + 1) % imgs.len; ··· 68 71 .y = clip_y, 69 72 } }); 70 73 71 - try vx.render(); 74 + try vx.render(tty.anyWriter()); 72 75 } 73 76 }
+10 -9
examples/main.zig
··· 14 14 } 15 15 const alloc = gpa.allocator(); 16 16 17 - // Initialize Vaxis 17 + var tty = try vaxis.Tty.init(); 18 + defer tty.deinit(); 19 + 18 20 var vx = try vaxis.init(alloc, .{}); 19 - defer vx.deinit(alloc); 21 + defer vx.deinit(alloc, tty.anyWriter()); 20 22 21 - var loop: vaxis.Loop(Event) = .{ .vaxis = &vx }; 23 + var loop: vaxis.Loop(Event) = .{ .tty = &tty, .vaxis = &vx }; 24 + try loop.init(); 22 25 23 - // Start the read loop. This puts the terminal in raw mode and begins 24 - // reading user input 25 - try loop.run(); 26 + try loop.start(); 26 27 defer loop.stop(); 27 28 28 29 // Optionally enter the alternate screen 29 - try vx.enterAltScreen(); 30 + try vx.enterAltScreen(tty.anyWriter()); 30 31 31 32 // We'll adjust the color index every keypress 32 33 var color_idx: u8 = 0; ··· 51 52 } 52 53 }, 53 54 .winsize => |ws| { 54 - try vx.resize(alloc, ws); 55 + try vx.resize(alloc, tty.anyWriter(), ws); 55 56 }, 56 57 else => {}, 57 58 } ··· 85 86 child.writeCell(i, 0, cell); 86 87 } 87 88 // Render the screen 88 - try vx.render(); 89 + try vx.render(tty.anyWriter()); 89 90 } 90 91 } 91 92
+11 -7
examples/nvim.zig
··· 23 23 } 24 24 const alloc = gpa.allocator(); 25 25 26 + var tty = try vaxis.Tty.init(); 27 + defer tty.deinit(); 28 + 26 29 // Initialize Vaxis 27 30 var vx = try vaxis.init(alloc, .{}); 28 - defer vx.deinit(alloc); 31 + defer vx.deinit(alloc, tty.anyWriter()); 29 32 30 - var loop: vaxis.Loop(Event) = .{ .vaxis = &vx }; 33 + var loop: vaxis.Loop(Event) = .{ .tty = &tty, .vaxis = &vx }; 34 + try loop.init(); 31 35 32 - try loop.run(); 36 + try loop.start(); 33 37 defer loop.stop(); 34 38 35 39 // Optionally enter the alternate screen 36 - try vx.enterAltScreen(); 37 - try vx.queryTerminal(); 40 + // try vx.enterAltScreen(tty.anyWriter()); 41 + try vx.queryTerminal(tty.anyWriter(), 1 * std.time.ns_per_s); 38 42 39 43 var nvim = try vaxis.widgets.nvim.Nvim(Event).init(alloc, &loop); 40 44 try nvim.spawn(); ··· 53 57 try nvim.update(.{ .key_press = key }); 54 58 }, 55 59 .winsize => |ws| { 56 - try vx.resize(alloc, ws); 60 + try vx.resize(alloc, tty.anyWriter(), ws); 57 61 }, 58 62 .nvim => |nvim_event| { 59 63 switch (nvim_event) { ··· 74 78 }, 75 79 ); 76 80 try nvim.draw(child); 77 - try vx.render(); 81 + try vx.render(tty.anyWriter()); 78 82 } 79 83 }
-61
examples/pathological.zig
··· 1 - const std = @import("std"); 2 - const vaxis = @import("vaxis"); 3 - const Cell = vaxis.Cell; 4 - 5 - const log = std.log.scoped(.main); 6 - 7 - const Event = union(enum) { 8 - winsize: vaxis.Winsize, 9 - }; 10 - 11 - pub fn main() !void { 12 - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 13 - const alloc = gpa.allocator(); 14 - var vx = try vaxis.init(alloc, .{}); 15 - defer vx.deinit(alloc); 16 - 17 - var loop: vaxis.Loop(Event) = .{ .vaxis = &vx }; 18 - 19 - try loop.run(); 20 - defer loop.stop(); 21 - try vx.enterAltScreen(); 22 - try vx.queryTerminal(); 23 - 24 - while (true) { 25 - const event = loop.nextEvent(); 26 - switch (event) { 27 - .winsize => |ws| { 28 - try vx.resize(alloc, ws); 29 - break; 30 - }, 31 - } 32 - } 33 - 34 - const timer_start = std.time.microTimestamp(); 35 - var iter: usize = 0; 36 - while (iter < 10_000) : (iter += 1) { 37 - const win = vx.window(); 38 - const child = win.initChild(0, 0, .{ .limit = 20 }, .{ .limit = 20 }); 39 - win.clear(); 40 - var row: usize = 0; 41 - while (row < child.height) : (row += 1) { 42 - var col: usize = 0; 43 - while (col < child.width) : (col += 1) { 44 - child.writeCell(col, row, .{ 45 - .char = .{ 46 - .grapheme = " ", 47 - .width = 1, 48 - }, 49 - .style = .{ 50 - .bg = .{ .index = @truncate(col + iter) }, 51 - }, 52 - }); 53 - } 54 - } 55 - try vx.render(); 56 - } 57 - try vx.exitAltScreen(); 58 - const took = std.time.microTimestamp() - timer_start; 59 - const stdout = std.io.getStdOut().writer(); 60 - try stdout.print("\r\ntook {d}ms to render 10,000 times\r\n", .{@divTrunc(took, std.time.us_per_ms)}); 61 - }
+12 -17
examples/pause_tui.zig
··· 31 31 } 32 32 const alloc = gpa.allocator(); 33 33 34 - // Initialize Vaxis with our event type 35 - var vx = try vaxis.init(Event, .{}); 36 - // deinit takes an optional allocator. If your program is exiting, you can 37 - // choose to pass a null allocator to save some exit time. 38 - defer vx.deinit(alloc); 34 + var tty = try vaxis.Tty.init(); 35 + defer tty.deinit(); 36 + 37 + var vx = try vaxis.init(alloc, .{}); 38 + defer vx.deinit(alloc, tty.anyWriter()); 39 39 40 - // Start the read loop. This puts the terminal in raw mode and begins 41 - // reading user input 42 - try vx.startReadThread(); 43 - defer vx.stopReadThread(); 40 + var loop: vaxis.Loop(Event) = .{ .tty = &tty, .vaxis = &vx }; 41 + try loop.init(); 44 42 45 - // Optionally enter the alternate screen 46 - try vx.enterAltScreen(); 47 - defer vx.exitAltScreen() catch {}; 43 + try loop.start(); 44 + defer loop.stop(); 48 45 49 46 // We'll adjust the color index every keypress for the border 50 47 var color_idx: u8 = 0; ··· 56 53 57 54 // Sends queries to terminal to detect certain features. This should 58 55 // _always_ be called, but is left to the application to decide when 59 - try vx.queryTerminal(); 56 + try vx.queryTerminal(tty.anyWriter(), 1 * std.time.ns_per_s); 60 57 61 58 try vx.setMouseMode(true); 62 59 ··· 78 75 break; 79 76 } else if (key.matches('l', .{ .ctrl = true })) { 80 77 vx.queueRefresh(); 81 - } else if (key.matches('n', .{ .ctrl = true })) { 82 - try vx.notify("vaxis", "hello from vaxis"); 83 78 } else if (key.matches('z', .{ .ctrl = true })) { 84 79 try openDirVim(alloc, &vx, "examples"); 85 80 } else { ··· 102 97 // more than one byte will incur an allocation on the first render 103 98 // after it is drawn. Thereafter, it will not allocate unless the 104 99 // screen is resized 105 - .winsize => |ws| try vx.resize(alloc, ws), 100 + .winsize => |ws| try vx.resize(alloc, tty.anyWriter(), ws), 106 101 else => {}, 107 102 } 108 103 ··· 128 123 text_input.draw(border.all(child, style)); 129 124 130 125 // Render the screen 131 - try vx.render(); 126 + try vx.render(tty.anyWriter()); 132 127 } 133 128 } 134 129
-75
examples/readline.zig
··· 1 - const std = @import("std"); 2 - const vaxis = @import("vaxis"); 3 - const Cell = vaxis.Cell; 4 - const TextInput = vaxis.widgets.TextInput; 5 - const border = vaxis.widgets.border; 6 - 7 - const log = std.log.scoped(.main); 8 - 9 - const Event = union(enum) { 10 - key_press: vaxis.Key, 11 - winsize: vaxis.Winsize, 12 - }; 13 - 14 - pub fn main() !void { 15 - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 16 - defer { 17 - const deinit_status = gpa.deinit(); 18 - //fail test; can't try in defer as defer is executed after we return 19 - if (deinit_status == .leak) { 20 - log.err("memory leak", .{}); 21 - } 22 - } 23 - const alloc = gpa.allocator(); 24 - 25 - var line: ?[]const u8 = null; 26 - defer { 27 - // do this in defer so that vaxis cleans up terminal state before we 28 - // print to stdout 29 - if (line) |_| { 30 - const stdout = std.io.getStdOut().writer(); 31 - stdout.print("\n{s}\n", .{line.?}) catch {}; 32 - alloc.free(line.?); 33 - } 34 - } 35 - var vx = try vaxis.init(alloc, .{}); 36 - defer vx.deinit(alloc); 37 - 38 - var loop: vaxis.Loop(Event) = .{ .vaxis = &vx }; 39 - try loop.run(); 40 - defer loop.stop(); 41 - 42 - var text_input = TextInput.init(alloc, &vx.unicode); 43 - defer text_input.deinit(); 44 - 45 - try vx.queryTerminal(); 46 - 47 - const prompt: vaxis.Segment = .{ .text = "$ " }; 48 - 49 - while (true) { 50 - const event = loop.nextEvent(); 51 - switch (event) { 52 - .key_press => |key| { 53 - if (key.matches('c', .{ .ctrl = true })) { 54 - break; 55 - } else if (key.matches(vaxis.Key.enter, .{})) { 56 - line = try text_input.toOwnedSlice(); 57 - text_input.clearAndFree(); 58 - break; 59 - } else { 60 - try text_input.update(.{ .key_press = key }); 61 - } 62 - }, 63 - .winsize => |ws| try vx.resize(alloc, ws), 64 - } 65 - 66 - const win = vx.window(); 67 - 68 - win.clear(); 69 - _ = try win.printSegment(prompt, .{}); 70 - 71 - const input_win = win.child(.{ .x_off = 2 }); 72 - text_input.draw(input_win); 73 - try vx.render(); 74 - } 75 - }
-126
examples/shell.zig
··· 1 - const std = @import("std"); 2 - const vaxis = @import("vaxis"); 3 - const Cell = vaxis.Cell; 4 - const TextInput = vaxis.widgets.TextInput; 5 - 6 - const log = std.log.scoped(.main); 7 - pub fn main() !void { 8 - var gpa = std.heap.GeneralPurposeAllocator(.{}){}; 9 - defer { 10 - const deinit_status = gpa.deinit(); 11 - if (deinit_status == .leak) { 12 - log.err("memory leak", .{}); 13 - } 14 - } 15 - const alloc = gpa.allocator(); 16 - 17 - var vx = try vaxis.init(alloc, .{}); 18 - defer vx.deinit(alloc); 19 - 20 - var loop: vaxis.Loop(Event) = .{ .vaxis = &vx }; 21 - 22 - try loop.run(); 23 - defer loop.stop(); 24 - 25 - try vx.queryTerminal(); 26 - 27 - var text_input = TextInput.init(alloc, &vx.unicode); 28 - defer text_input.deinit(); 29 - 30 - var selected_option: ?usize = null; 31 - 32 - const options = [_][]const u8{ 33 - "yes", 34 - "no", 35 - }; 36 - 37 - // The main event loop. Vaxis provides a thread safe, blocking, buffered 38 - // queue which can serve as the primary event queue for an application 39 - while (true) { 40 - // nextEvent blocks until an event is in the queue 41 - const event = loop.nextEvent(); 42 - // exhaustive switching ftw. Vaxis will send events if your Event 43 - // enum has the fields for those events (ie "key_press", "winsize") 44 - switch (event) { 45 - .key_press => |key| { 46 - if (key.codepoint == 'c' and key.mods.ctrl) { 47 - break; 48 - } else if (key.matches(vaxis.Key.tab, .{})) { 49 - if (selected_option == null) { 50 - selected_option = 0; 51 - } else { 52 - selected_option.? = @min(options.len - 1, selected_option.? + 1); 53 - } 54 - } else if (key.matches(vaxis.Key.tab, .{ .shift = true })) { 55 - if (selected_option == null) { 56 - selected_option = 0; 57 - } else { 58 - selected_option.? = selected_option.? -| 1; 59 - } 60 - } else if (key.matches(vaxis.Key.enter, .{})) { 61 - if (selected_option) |i| { 62 - log.err("enter", .{}); 63 - try text_input.insertSliceAtCursor(options[i]); 64 - selected_option = null; 65 - } 66 - } else { 67 - if (selected_option == null) 68 - try text_input.update(.{ .key_press = key }); 69 - } 70 - }, 71 - .winsize => |ws| { 72 - try vx.resize(alloc, ws); 73 - }, 74 - else => {}, 75 - } 76 - 77 - const win = vx.window(); 78 - win.clear(); 79 - 80 - const right_win = win.child(.{ 81 - .x_off = win.width - 4, 82 - }); 83 - const left_win = win.child(.{ 84 - .width = .{ .limit = 8 }, 85 - }); 86 - var right_prompt = [_]vaxis.Segment{ 87 - .{ .text = "👩‍🚀🏳️‍🌈" }, 88 - }; 89 - var left_prompt = [_]vaxis.Segment{ 90 - .{ .text = "👩‍🚀🏳️‍🌈~ ", .style = .{ .fg = .{ .index = 4 } } }, 91 - .{ .text = "", .style = .{ .fg = .{ .index = 5 } } }, 92 - }; 93 - _ = try right_win.print(&right_prompt, .{}); 94 - _ = try left_win.print(&left_prompt, .{}); 95 - 96 - const input_win = win.child(.{ 97 - .x_off = 8, 98 - .width = .{ .limit = win.width - 12 }, 99 - }); 100 - 101 - text_input.draw(input_win); 102 - 103 - if (selected_option) |i| { 104 - win.hideCursor(); 105 - for (options, 0..) |opt, j| { 106 - log.err("i = {d}, j = {d}, opt = {s}", .{ i, j, opt }); 107 - var seg = [_]vaxis.Segment{.{ 108 - .text = opt, 109 - .style = if (j == i) .{ .reverse = true } else .{}, 110 - }}; 111 - _ = try win.print(&seg, .{ .row_offset = j + 1 }); 112 - } 113 - } 114 - try vx.render(); 115 - } 116 - } 117 - 118 - // Our Event. This can contain internal events as well as Vaxis events. 119 - // Internal events can be posted into the same queue as vaxis events to allow 120 - // for a single event loop with exhaustive switching. Booya 121 - const Event = union(enum) { 122 - key_press: vaxis.Key, 123 - winsize: vaxis.Winsize, 124 - focus_in, 125 - foo: u8, 126 - };
+9 -7
examples/table.zig
··· 24 24 const user_list = std.ArrayList(User).fromOwnedSlice(alloc, users_buf); 25 25 defer user_list.deinit(); 26 26 27 + var tty = try vaxis.Tty.init(); 28 + 27 29 var vx = try vaxis.init(alloc, .{}); 28 - defer vx.deinit(alloc); 30 + defer vx.deinit(alloc, tty.anyWriter()); 29 31 30 32 var loop: vaxis.Loop(union(enum) { 31 33 key_press: vaxis.Key, 32 34 winsize: vaxis.Winsize, 33 - }) = .{ .vaxis = &vx }; 35 + }) = .{ .tty = &tty, .vaxis = &vx }; 34 36 35 - try loop.run(); 37 + try loop.start(); 36 38 defer loop.stop(); 37 - try vx.enterAltScreen(); 38 - try vx.queryTerminal(); 39 + try vx.enterAltScreen(tty.anyWriter()); 40 + try vx.queryTerminal(tty.anyWriter(), 1 * std.time.ns_per_s); 39 41 40 42 const logo = 41 43 \\░█░█░█▀█░█░█░▀█▀░█▀▀░░░▀█▀░█▀█░█▀▄░█░░░█▀▀░ ··· 145 147 } 146 148 moving = false; 147 149 }, 148 - .winsize => |ws| try vx.resize(alloc, ws), 150 + .winsize => |ws| try vx.resize(alloc, tty.anyWriter(), ws), 149 151 //else => {}, 150 152 } 151 153 ··· 203 205 cmd_input.draw(bottom_bar); 204 206 205 207 // Render the screen 206 - try vx.render(); 208 + try vx.render(tty.anyWriter()); 207 209 } 208 210 } 209 211
+2 -2
examples/text_input.zig
··· 40 40 41 41 // Initialize Vaxis 42 42 var vx = try vaxis.init(alloc, .{}); 43 - defer vx.deinit(tty.anyWriter(), alloc); 43 + defer vx.deinit(alloc, tty.anyWriter()); 44 44 45 45 var loop: vaxis.Loop(Event) = .{ 46 46 .vaxis = &vx, ··· 120 120 // more than one byte will incur an allocation on the first render 121 121 // after it is drawn. Thereafter, it will not allocate unless the 122 122 // screen is resized 123 - .winsize => |ws| try vx.resize(alloc, ws, tty.anyWriter()), 123 + .winsize => |ws| try vx.resize(alloc, tty.anyWriter(), ws), 124 124 else => {}, 125 125 } 126 126
+10 -8
examples/vaxis.zig
··· 18 18 } 19 19 const alloc = gpa.allocator(); 20 20 21 + var tty = try vaxis.Tty.init(); 21 22 var vx = try vaxis.init(alloc, .{}); 22 - defer vx.deinit(alloc); 23 + defer vx.deinit(alloc, tty.anyWriter()); 23 24 24 - var loop: vaxis.Loop(Event) = .{ .vaxis = &vx }; 25 + var loop: vaxis.Loop(Event) = .{ .tty = &tty, .vaxis = &vx }; 26 + try loop.init(); 25 27 26 - try loop.run(); 28 + try loop.start(); 27 29 defer loop.stop(); 28 30 29 - try vx.enterAltScreen(); 30 - try vx.queryTerminal(); 31 + try vx.enterAltScreen(tty.anyWriter()); 32 + try vx.queryTerminal(tty.anyWriter(), 1 * std.time.ns_per_s); 31 33 32 34 const lower_limit = 30; 33 35 var color_idx: u8 = lower_limit; ··· 42 44 switch (event) { 43 45 .key_press => |key| if (key.matches('c', .{ .ctrl = true })) return, 44 46 .winsize => |ws| { 45 - try vx.resize(alloc, ws); 47 + try vx.resize(alloc, tty.anyWriter(), ws); 46 48 break; 47 49 }, 48 50 } ··· 52 54 while (loop.tryEvent()) |event| { 53 55 switch (event) { 54 56 .key_press => |key| if (key.matches('c', .{ .ctrl = true })) return, 55 - .winsize => |ws| try vx.resize(alloc, ws), 57 + .winsize => |ws| try vx.resize(alloc, tty.anyWriter(), ws), 56 58 } 57 59 } 58 60 ··· 67 69 }; 68 70 const center = vaxis.widgets.alignment.center(win, 28, 4); 69 71 _ = try center.printSegment(segment, .{ .wrap = .grapheme }); 70 - try vx.render(); 72 + try vx.render(tty.anyWriter()); 71 73 std.time.sleep(8 * std.time.ns_per_ms); 72 74 switch (dir) { 73 75 .up => {
+156 -149
src/Vaxis.zig
··· 9 9 const Key = @import("Key.zig"); 10 10 const Mouse = @import("Mouse.zig"); 11 11 const Screen = @import("Screen.zig"); 12 - const tty = @import("tty.zig"); 13 12 const Unicode = @import("Unicode.zig"); 14 13 const Window = @import("Window.zig"); 15 14 ··· 18 17 const KittyFlags = Key.KittyFlags; 19 18 const Shape = Mouse.Shape; 20 19 const Style = Cell.Style; 21 - const Winsize = tty.Winsize; 20 + const Winsize = @import("tty.zig").Winsize; 22 21 23 22 const ctlseqs = @import("ctlseqs.zig"); 24 23 const gwidth = @import("gwidth.zig"); ··· 57 56 /// if we should redraw the entire screen on the next render 58 57 refresh: bool = false, 59 58 59 + // FIXME: remove before committing 60 60 /// blocks the main thread until a DA1 query has been received, or the 61 61 /// futex times out 62 62 query_futex: atomic.Value(u32) = atomic.Value(u32).init(0), ··· 106 106 /// passed, this will free resources associated with Vaxis. This is left as an 107 107 /// optional so applications can choose to not free resources when the 108 108 /// application will be exiting anyways 109 - pub fn deinit(self: *Vaxis, writer: AnyWriter, alloc: ?std.mem.Allocator) void { 110 - self.resetState(writer) catch {}; 109 + pub fn deinit(self: *Vaxis, alloc: ?std.mem.Allocator, tty: AnyWriter) void { 110 + self.resetState(tty) catch {}; 111 111 112 112 // always show the cursor on exit 113 - writer.writeAll(ctlseqs.show_cursor) catch {}; 113 + tty.writeAll(ctlseqs.show_cursor) catch {}; 114 114 if (alloc) |a| { 115 115 self.screen.deinit(a); 116 116 self.screen_last.deinit(a); 117 117 } 118 118 if (self.renders > 0) { 119 119 const tpr = @divTrunc(self.render_dur, self.renders); 120 - log.debug("total renders = {d}", .{self.renders}); 121 - log.debug("microseconds per render = {d}", .{tpr}); 120 + log.debug("total renders = {d}\r", .{self.renders}); 121 + log.debug("microseconds per render = {d}\r", .{tpr}); 122 122 } 123 123 self.unicode.deinit(); 124 124 } 125 125 126 - /// resets enabled features 127 - pub fn resetState(self: *Vaxis, writer: AnyWriter) !void { 126 + /// resets enabled features, sends cursor to home and clears below cursor 127 + pub fn resetState(self: *Vaxis, tty: AnyWriter) !void { 128 128 if (self.state.kitty_keyboard) { 129 - try writer.writeAll(ctlseqs.csi_u_pop); 129 + try tty.writeAll(ctlseqs.csi_u_pop); 130 130 self.state.kitty_keyboard = false; 131 131 } 132 132 if (self.state.mouse) { 133 - try self.setMouseMode(writer, false); 133 + try self.setMouseMode(tty, false); 134 134 } 135 135 if (self.state.bracketed_paste) { 136 - try self.setBracketedPaste(writer, false); 136 + try self.setBracketedPaste(tty, false); 137 137 } 138 138 if (self.state.alt_screen) { 139 - try self.exitAltScreen(writer); 139 + try tty.writeAll(ctlseqs.home); 140 + try tty.writeAll(ctlseqs.erase_below_cursor); 141 + try self.exitAltScreen(tty); 142 + } else { 143 + try tty.writeByte('\r'); 144 + var i: usize = 0; 145 + while (i < self.state.cursor.row) : (i += 1) { 146 + try tty.writeAll(ctlseqs.ri); 147 + } 148 + try tty.writeAll(ctlseqs.erase_below_cursor); 140 149 } 141 150 if (self.state.color_scheme_updates) { 142 - try writer.writeAll(ctlseqs.color_scheme_reset); 151 + try tty.writeAll(ctlseqs.color_scheme_reset); 143 152 self.state.color_scheme_updates = false; 144 153 } 145 154 } ··· 148 157 /// required to display the screen (ie width x height). Any previous screen is 149 158 /// freed when resizing. The cursor will be sent to it's home position and a 150 159 /// hardware clear-below-cursor will be sent 151 - pub fn resize(self: *Vaxis, alloc: std.mem.Allocator, winsize: Winsize, writer: AnyWriter) !void { 160 + pub fn resize( 161 + self: *Vaxis, 162 + alloc: std.mem.Allocator, 163 + tty: AnyWriter, 164 + winsize: Winsize, 165 + ) !void { 152 166 log.debug("resizing screen: width={d} height={d}", .{ winsize.cols, winsize.rows }); 153 167 self.screen.deinit(alloc); 154 168 self.screen = try Screen.init(alloc, winsize, &self.unicode); ··· 159 173 self.screen_last.deinit(alloc); 160 174 self.screen_last = try InternalScreen.init(alloc, winsize.cols, winsize.rows); 161 175 if (self.state.alt_screen) 162 - try writer.writeAll(ctlseqs.home) 176 + try tty.writeAll(ctlseqs.home) 163 177 else { 164 - try writer.writeByte('\r'); 178 + try tty.writeByte('\r'); 165 179 var i: usize = 0; 166 180 while (i < self.state.cursor.row) : (i += 1) { 167 - try writer.writeAll(ctlseqs.ri); 181 + try tty.writeAll(ctlseqs.ri); 168 182 } 169 183 } 170 - try writer.writeAll(ctlseqs.erase_below_cursor); 184 + try tty.writeAll(ctlseqs.erase_below_cursor); 171 185 } 172 186 173 187 /// returns a Window comprising of the entire terminal screen ··· 183 197 184 198 /// enter the alternate screen. The alternate screen will automatically 185 199 /// be exited if calling deinit while in the alt screen 186 - pub fn enterAltScreen(self: *Vaxis, writer: AnyWriter) !void { 187 - try writer.writeAll(ctlseqs.smcup); 200 + pub fn enterAltScreen(self: *Vaxis, tty: AnyWriter) !void { 201 + try tty.writeAll(ctlseqs.smcup); 188 202 self.state.alt_screen = true; 189 203 } 190 204 191 205 /// exit the alternate screen 192 - pub fn exitAltScreen(self: *Vaxis, writer: AnyWriter) !void { 193 - try writer.writeAll(ctlseqs.rmcup); 206 + pub fn exitAltScreen(self: *Vaxis, tty: AnyWriter) !void { 207 + try tty.writeAll(ctlseqs.rmcup); 194 208 self.state.alt_screen = false; 195 209 } 196 210 197 211 /// write queries to the terminal to determine capabilities. Individual 198 212 /// capabilities will be delivered to the client and possibly intercepted by 199 - /// Vaxis to enable features 200 - pub fn queryTerminal(self: *Vaxis) !void { 201 - try self.queryTerminalSend(); 202 - 213 + /// Vaxis to enable features. 214 + /// 215 + /// This call will block until Vaxis.query_futex is woken up, or the timeout. 216 + /// Event loops can wake up this futex when cap_da1 is received 217 + pub fn queryTerminal(self: *Vaxis, tty: AnyWriter, timeout_ns: u64) !void { 218 + try self.queryTerminalSend(tty); 203 219 // 1 second timeout 204 - std.Thread.Futex.timedWait(&self.query_futex, 0, 1 * std.time.ns_per_s) catch {}; 205 - 206 - try self.enableDetectedFeatures(); 220 + std.Thread.Futex.timedWait(&self.query_futex, 0, timeout_ns) catch {}; 221 + try self.enableDetectedFeatures(tty); 207 222 } 208 223 209 224 /// write queries to the terminal to determine capabilities. This function 210 225 /// is only for use with a custom main loop. Call Vaxis.queryTerminal() if 211 226 /// you are using Loop.run() 212 - pub fn queryTerminalSend(_: Vaxis, writer: AnyWriter) !void { 227 + pub fn queryTerminalSend(_: Vaxis, tty: AnyWriter) !void { 213 228 214 229 // TODO: re-enable this 215 230 // const colorterm = std.posix.getenv("COLORTERM") orelse ""; ··· 221 236 // } 222 237 // } 223 238 239 + // TODO: XTGETTCAP queries ("RGB", "Smulx") 224 240 // TODO: decide if we actually want to query for focus and sync. It 225 241 // doesn't hurt to blindly use them 226 242 // _ = try tty.write(ctlseqs.decrqm_focus); 227 243 // _ = try tty.write(ctlseqs.decrqm_sync); 228 - try writer.writeAll(ctlseqs.decrqm_sgr_pixels); 229 - try writer.writeAll(ctlseqs.decrqm_unicode); 230 - try writer.writeAll(ctlseqs.decrqm_color_scheme); 231 - // TODO: XTVERSION has a DCS response. uncomment when we can parse 232 - // that 233 - // _ = try tty.write(ctlseqs.xtversion); 234 - try writer.writeAll(ctlseqs.csi_u_query); 235 - try writer.writeAll(ctlseqs.kitty_graphics_query); 236 - // TODO: sixel geometry query interferes with F4 keys. 237 - // _ = try tty.write(ctlseqs.sixel_geometry_query); 238 - 239 - // TODO: XTGETTCAP queries ("RGB", "Smulx") 240 - 241 - try writer.writeAll(ctlseqs.primary_device_attrs); 244 + try tty.writeAll(ctlseqs.decrqm_sgr_pixels ++ 245 + ctlseqs.decrqm_unicode ++ 246 + ctlseqs.decrqm_color_scheme ++ 247 + ctlseqs.xtversion ++ 248 + ctlseqs.csi_u_query ++ 249 + ctlseqs.kitty_graphics_query ++ 250 + ctlseqs.primary_device_attrs); 242 251 } 243 252 244 253 /// Enable features detected by responses to queryTerminal. This function 245 254 /// is only for use with a custom main loop. Call Vaxis.queryTerminal() if 246 255 /// you are using Loop.run() 247 - pub fn enableDetectedFeatures(self: *Vaxis) !void { 256 + pub fn enableDetectedFeatures(self: *Vaxis, tty: AnyWriter) !void { 248 257 // Apply any environment variables 249 258 if (std.posix.getenv("ASCIINEMA_REC")) |_| 250 259 self.sgr = .legacy; ··· 263 272 264 273 // enable detected features 265 274 if (self.caps.kitty_keyboard) { 266 - try self.enableKittyKeyboard(self.opts.kitty_keyboard_flags); 275 + try self.enableKittyKeyboard(tty, self.opts.kitty_keyboard_flags); 267 276 } 268 277 if (self.caps.unicode == .unicode) { 269 - _ = try tty.write(ctlseqs.unicode_set); 278 + try tty.writeAll(ctlseqs.unicode_set); 270 279 } 271 280 } 272 281 ··· 276 285 } 277 286 278 287 /// draws the screen to the terminal 279 - pub fn render(self: *Vaxis, writer: AnyWriter) !void { 288 + pub fn render(self: *Vaxis, tty: AnyWriter) !void { 280 289 self.renders += 1; 281 290 self.render_timer.reset(); 282 291 defer { ··· 289 298 // TODO: optimize sync so we only sync _when we have changes_. This 290 299 // requires a smarter buffered writer, we'll probably have to write 291 300 // our own 292 - try writer.writeAll(ctlseqs.sync_set); 293 - defer writer.writeAll(ctlseqs.sync_reset) catch {}; 301 + try tty.writeAll(ctlseqs.sync_set); 302 + defer tty.writeAll(ctlseqs.sync_reset) catch {}; 294 303 295 304 // Send the cursor to 0,0 296 305 // TODO: this needs to move after we optimize writes. We only do 297 306 // this if we have an update to make. We also need to hide cursor 298 307 // and then reshow it if needed 299 - try writer.writeAll(ctlseqs.hide_cursor); 308 + try tty.writeAll(ctlseqs.hide_cursor); 300 309 if (self.state.alt_screen) 301 - try writer.writeAll(ctlseqs.home) 310 + try tty.writeAll(ctlseqs.home) 302 311 else { 303 - try writer.writeAll("\r"); 304 - var i: usize = 0; 305 - while (i < self.state.cursor.row) : (i += 1) { 306 - try writer.writeAll(ctlseqs.ri); 307 - } 312 + try tty.writeByte('\r'); 313 + try tty.writeBytesNTimes(ctlseqs.ri, self.state.cursor.row); 308 314 } 309 - try writer.writeAll(ctlseqs.sgr_reset); 315 + try tty.writeAll(ctlseqs.sgr_reset); 310 316 311 317 // initialize some variables 312 318 var reposition: bool = false; ··· 321 327 322 328 // Clear all images 323 329 if (self.caps.kitty_graphics) 324 - try writer.writeAll(ctlseqs.kitty_graphics_clear); 330 + try tty.writeAll(ctlseqs.kitty_graphics_clear); 325 331 326 332 var i: usize = 0; 327 333 while (i < self.screen.buf.len) { ··· 357 363 // Close any osc8 sequence we might be in before 358 364 // repositioning 359 365 if (link.uri.len > 0) { 360 - try writer.writeAll(ctlseqs.osc8_clear); 366 + try tty.writeAll(ctlseqs.osc8_clear); 361 367 } 362 368 continue; 363 369 } ··· 373 379 if (reposition) { 374 380 reposition = false; 375 381 if (self.state.alt_screen) 376 - try writer.print(ctlseqs.cup, .{ row + 1, col + 1 }) 382 + try tty.print(ctlseqs.cup, .{ row + 1, col + 1 }) 377 383 else { 378 384 if (cursor_pos.row == row) { 379 385 const n = col - cursor_pos.col; 380 - try writer.print(ctlseqs.cuf, .{n}); 386 + if (n > 0) 387 + try tty.print(ctlseqs.cuf, .{n}); 381 388 } else { 382 - try writer.writeByte('\r'); 389 + try tty.writeByte('\r'); 383 390 const n = row - cursor_pos.row; 384 - try writer.writeByteNTimes('\n', n); 391 + try tty.writeByteNTimes('\n', n); 392 + if (col > 0) 393 + try tty.print(ctlseqs.cuf, .{col}); 385 394 } 386 - if (col > 0) 387 - try writer.print(ctlseqs.cuf, .{col}); 388 395 } 389 396 } 390 397 391 398 if (cell.image) |img| { 392 - try writer.print( 399 + try tty.print( 393 400 ctlseqs.kitty_graphics_preamble, 394 401 .{img.img_id}, 395 402 ); 396 403 if (img.options.pixel_offset) |offset| { 397 - try writer.print( 404 + try tty.print( 398 405 ",X={d},Y={d}", 399 406 .{ offset.x, offset.y }, 400 407 ); 401 408 } 402 409 if (img.options.clip_region) |clip| { 403 410 if (clip.x) |x| 404 - try writer.print(",x={d}", .{x}); 411 + try tty.print(",x={d}", .{x}); 405 412 if (clip.y) |y| 406 - try writer.print(",y={d}", .{y}); 413 + try tty.print(",y={d}", .{y}); 407 414 if (clip.width) |width| 408 - try writer.print(",w={d}", .{width}); 415 + try tty.print(",w={d}", .{width}); 409 416 if (clip.height) |height| 410 - try writer.print(",h={d}", .{height}); 417 + try tty.print(",h={d}", .{height}); 411 418 } 412 419 if (img.options.size) |size| { 413 420 if (size.rows) |rows| 414 - try writer.print(",r={d}", .{rows}); 421 + try tty.print(",r={d}", .{rows}); 415 422 if (size.cols) |cols| 416 - try writer.print(",c={d}", .{cols}); 423 + try tty.print(",c={d}", .{cols}); 417 424 } 418 425 if (img.options.z_index) |z| { 419 - try writer.print(",z={d}", .{z}); 426 + try tty.print(",z={d}", .{z}); 420 427 } 421 - try writer.writeAll(ctlseqs.kitty_graphics_closing); 428 + try tty.writeAll(ctlseqs.kitty_graphics_closing); 422 429 } 423 430 424 431 // something is different, so let's loop through everything and ··· 427 434 // foreground 428 435 if (!Cell.Color.eql(cursor.fg, cell.style.fg)) { 429 436 switch (cell.style.fg) { 430 - .default => try writer.writeAll(ctlseqs.fg_reset), 437 + .default => try tty.writeAll(ctlseqs.fg_reset), 431 438 .index => |idx| { 432 439 switch (idx) { 433 - 0...7 => try writer.print(ctlseqs.fg_base, .{idx}), 434 - 8...15 => try writer.print(ctlseqs.fg_bright, .{idx - 8}), 440 + 0...7 => try tty.print(ctlseqs.fg_base, .{idx}), 441 + 8...15 => try tty.print(ctlseqs.fg_bright, .{idx - 8}), 435 442 else => { 436 443 switch (self.sgr) { 437 - .standard => try writer.print(ctlseqs.fg_indexed, .{idx}), 438 - .legacy => try writer.print(ctlseqs.fg_indexed_legacy, .{idx}), 444 + .standard => try tty.print(ctlseqs.fg_indexed, .{idx}), 445 + .legacy => try tty.print(ctlseqs.fg_indexed_legacy, .{idx}), 439 446 } 440 447 }, 441 448 } 442 449 }, 443 450 .rgb => |rgb| { 444 451 switch (self.sgr) { 445 - .standard => try writer.print(ctlseqs.fg_rgb, .{ rgb[0], rgb[1], rgb[2] }), 446 - .legacy => try writer.print(ctlseqs.fg_rgb_legacy, .{ rgb[0], rgb[1], rgb[2] }), 452 + .standard => try tty.print(ctlseqs.fg_rgb, .{ rgb[0], rgb[1], rgb[2] }), 453 + .legacy => try tty.print(ctlseqs.fg_rgb_legacy, .{ rgb[0], rgb[1], rgb[2] }), 447 454 } 448 455 }, 449 456 } ··· 451 458 // background 452 459 if (!Cell.Color.eql(cursor.bg, cell.style.bg)) { 453 460 switch (cell.style.bg) { 454 - .default => try writer.writeAll(ctlseqs.bg_reset), 461 + .default => try tty.writeAll(ctlseqs.bg_reset), 455 462 .index => |idx| { 456 463 switch (idx) { 457 - 0...7 => try writer.print(ctlseqs.bg_base, .{idx}), 458 - 8...15 => try writer.print(ctlseqs.bg_bright, .{idx - 8}), 464 + 0...7 => try tty.print(ctlseqs.bg_base, .{idx}), 465 + 8...15 => try tty.print(ctlseqs.bg_bright, .{idx - 8}), 459 466 else => { 460 467 switch (self.sgr) { 461 - .standard => try writer.print(ctlseqs.bg_indexed, .{idx}), 462 - .legacy => try writer.print(ctlseqs.bg_indexed_legacy, .{idx}), 468 + .standard => try tty.print(ctlseqs.bg_indexed, .{idx}), 469 + .legacy => try tty.print(ctlseqs.bg_indexed_legacy, .{idx}), 463 470 } 464 471 }, 465 472 } 466 473 }, 467 474 .rgb => |rgb| { 468 475 switch (self.sgr) { 469 - .standard => try writer.print(ctlseqs.bg_rgb, .{ rgb[0], rgb[1], rgb[2] }), 470 - .legacy => try writer.print(ctlseqs.bg_rgb_legacy, .{ rgb[0], rgb[1], rgb[2] }), 476 + .standard => try tty.print(ctlseqs.bg_rgb, .{ rgb[0], rgb[1], rgb[2] }), 477 + .legacy => try tty.print(ctlseqs.bg_rgb_legacy, .{ rgb[0], rgb[1], rgb[2] }), 471 478 } 472 479 }, 473 480 } ··· 475 482 // underline color 476 483 if (!Cell.Color.eql(cursor.ul, cell.style.ul)) { 477 484 switch (cell.style.bg) { 478 - .default => try writer.writeAll(ctlseqs.ul_reset), 485 + .default => try tty.writeAll(ctlseqs.ul_reset), 479 486 .index => |idx| { 480 487 switch (self.sgr) { 481 - .standard => try writer.print(ctlseqs.ul_indexed, .{idx}), 482 - .legacy => try writer.print(ctlseqs.ul_indexed_legacy, .{idx}), 488 + .standard => try tty.print(ctlseqs.ul_indexed, .{idx}), 489 + .legacy => try tty.print(ctlseqs.ul_indexed_legacy, .{idx}), 483 490 } 484 491 }, 485 492 .rgb => |rgb| { 486 493 switch (self.sgr) { 487 - .standard => try writer.print(ctlseqs.ul_rgb, .{ rgb[0], rgb[1], rgb[2] }), 488 - .legacy => try writer.print(ctlseqs.ul_rgb_legacy, .{ rgb[0], rgb[1], rgb[2] }), 494 + .standard => try tty.print(ctlseqs.ul_rgb, .{ rgb[0], rgb[1], rgb[2] }), 495 + .legacy => try tty.print(ctlseqs.ul_rgb_legacy, .{ rgb[0], rgb[1], rgb[2] }), 489 496 } 490 497 }, 491 498 } ··· 500 507 .dotted => ctlseqs.ul_dotted, 501 508 .dashed => ctlseqs.ul_dashed, 502 509 }; 503 - try writer.writeAll(seq); 510 + try tty.writeAll(seq); 504 511 } 505 512 // bold 506 513 if (cursor.bold != cell.style.bold) { ··· 508 515 true => ctlseqs.bold_set, 509 516 false => ctlseqs.bold_dim_reset, 510 517 }; 511 - try writer.writeAll(seq); 518 + try tty.writeAll(seq); 512 519 if (cell.style.dim) { 513 - try writer.writeAll(ctlseqs.dim_set); 520 + try tty.writeAll(ctlseqs.dim_set); 514 521 } 515 522 } 516 523 // dim ··· 519 526 true => ctlseqs.dim_set, 520 527 false => ctlseqs.bold_dim_reset, 521 528 }; 522 - try writer.writeAll(seq); 529 + try tty.writeAll(seq); 523 530 if (cell.style.bold) { 524 - try writer.writeAll(ctlseqs.bold_set); 531 + try tty.writeAll(ctlseqs.bold_set); 525 532 } 526 533 } 527 534 // dim ··· 530 537 true => ctlseqs.italic_set, 531 538 false => ctlseqs.italic_reset, 532 539 }; 533 - try writer.writeAll(seq); 540 + try tty.writeAll(seq); 534 541 } 535 542 // dim 536 543 if (cursor.blink != cell.style.blink) { ··· 538 545 true => ctlseqs.blink_set, 539 546 false => ctlseqs.blink_reset, 540 547 }; 541 - try writer.writeAll(seq); 548 + try tty.writeAll(seq); 542 549 } 543 550 // reverse 544 551 if (cursor.reverse != cell.style.reverse) { ··· 546 553 true => ctlseqs.reverse_set, 547 554 false => ctlseqs.reverse_reset, 548 555 }; 549 - try writer.writeAll(seq); 556 + try tty.writeAll(seq); 550 557 } 551 558 // invisible 552 559 if (cursor.invisible != cell.style.invisible) { ··· 554 561 true => ctlseqs.invisible_set, 555 562 false => ctlseqs.invisible_reset, 556 563 }; 557 - try writer.writeAll(seq); 564 + try tty.writeAll(seq); 558 565 } 559 566 // strikethrough 560 567 if (cursor.strikethrough != cell.style.strikethrough) { ··· 562 569 true => ctlseqs.strikethrough_set, 563 570 false => ctlseqs.strikethrough_reset, 564 571 }; 565 - try writer.writeAll(seq); 572 + try tty.writeAll(seq); 566 573 } 567 574 568 575 // url ··· 573 580 // a url 574 581 ps = ""; 575 582 } 576 - try writer.print(ctlseqs.osc8, .{ ps, cell.link.uri }); 583 + try tty.print(ctlseqs.osc8, .{ ps, cell.link.uri }); 577 584 } 578 - try writer.writeAll(cell.char.grapheme); 585 + try tty.writeAll(cell.char.grapheme); 579 586 cursor_pos.col = col + w; 580 587 cursor_pos.row = row; 581 588 } 582 589 if (self.screen.cursor_vis) { 583 590 if (self.state.alt_screen) { 584 - try writer.print( 591 + try tty.print( 585 592 ctlseqs.cup, 586 593 .{ 587 594 self.screen.cursor_row + 1, ··· 590 597 ); 591 598 } else { 592 599 // TODO: position cursor relative to current location 593 - try writer.writeByte('\r'); 600 + try tty.writeByte('\r'); 594 601 if (self.screen.cursor_row >= cursor_pos.row) 595 - try writer.writeByteNTimes('\n', self.screen.cursor_row - cursor_pos.row) 602 + try tty.writeByteNTimes('\n', self.screen.cursor_row - cursor_pos.row) 596 603 else 597 - try writer.writeBytesNTimes(ctlseqs.ri, cursor_pos.row - self.screen.cursor_row); 604 + try tty.writeBytesNTimes(ctlseqs.ri, cursor_pos.row - self.screen.cursor_row); 598 605 if (self.screen.cursor_col > 0) 599 - try writer.print(ctlseqs.cuf, .{self.screen.cursor_col}); 606 + try tty.print(ctlseqs.cuf, .{self.screen.cursor_col}); 600 607 } 601 608 self.state.cursor.row = self.screen.cursor_row; 602 609 self.state.cursor.col = self.screen.cursor_col; 603 - try writer.writeAll(ctlseqs.show_cursor); 610 + try tty.writeAll(ctlseqs.show_cursor); 604 611 } else { 605 612 self.state.cursor.row = cursor_pos.row; 606 613 self.state.cursor.col = cursor_pos.col; 607 614 } 608 615 if (self.screen.mouse_shape != self.screen_last.mouse_shape) { 609 - try writer.print( 616 + try tty.print( 610 617 ctlseqs.osc22_mouse_shape, 611 618 .{@tagName(self.screen.mouse_shape)}, 612 619 ); 613 620 self.screen_last.mouse_shape = self.screen.mouse_shape; 614 621 } 615 622 if (self.screen.cursor_shape != self.screen_last.cursor_shape) { 616 - try writer.print( 623 + try tty.print( 617 624 ctlseqs.cursor_shape, 618 625 .{@intFromEnum(self.screen.cursor_shape)}, 619 626 ); ··· 621 628 } 622 629 } 623 630 624 - fn enableKittyKeyboard(self: *Vaxis, writer: AnyWriter, flags: Key.KittyFlags) !void { 631 + fn enableKittyKeyboard(self: *Vaxis, tty: AnyWriter, flags: Key.KittyFlags) !void { 625 632 const flag_int: u5 = @bitCast(flags); 626 - try writer.print(ctlseqs.csi_u_push, .{flag_int}); 633 + try tty.print(ctlseqs.csi_u_push, .{flag_int}); 627 634 self.state.kitty_keyboard = true; 628 635 } 629 636 630 637 /// send a system notification 631 - pub fn notify(_: *Vaxis, writer: AnyWriter, title: ?[]const u8, body: []const u8) !void { 638 + pub fn notify(_: *Vaxis, tty: AnyWriter, title: ?[]const u8, body: []const u8) !void { 632 639 if (title) |t| 633 - try writer.print(ctlseqs.osc777_notify, .{ t, body }) 640 + try tty.print(ctlseqs.osc777_notify, .{ t, body }) 634 641 else 635 - try writer.print(ctlseqs.osc9_notify, .{body}); 642 + try tty.print(ctlseqs.osc9_notify, .{body}); 636 643 } 637 644 638 645 /// sets the window title 639 - pub fn setTitle(_: *Vaxis, writer: AnyWriter, title: []const u8) !void { 640 - try writer.print(ctlseqs.osc2_set_title, .{title}); 646 + pub fn setTitle(_: *Vaxis, tty: AnyWriter, title: []const u8) !void { 647 + try tty.print(ctlseqs.osc2_set_title, .{title}); 641 648 } 642 649 643 650 // turn bracketed paste on or off. An event will be sent at the 644 651 // beginning and end of a detected paste. All keystrokes between these 645 652 // events were pasted 646 - pub fn setBracketedPaste(self: *Vaxis, writer: AnyWriter, enable: bool) !void { 653 + pub fn setBracketedPaste(self: *Vaxis, tty: AnyWriter, enable: bool) !void { 647 654 const seq = if (enable) 648 655 ctlseqs.bp_set 649 656 else 650 657 ctlseqs.bp_reset; 651 - try writer.writeAll(seq); 658 + try tty.writeAll(seq); 652 659 self.state.bracketed_paste = enable; 653 660 } 654 661 ··· 658 665 } 659 666 660 667 /// Change the mouse reporting mode 661 - pub fn setMouseMode(self: *Vaxis, writer: AnyWriter, enable: bool) !void { 668 + pub fn setMouseMode(self: *Vaxis, tty: AnyWriter, enable: bool) !void { 662 669 if (enable) { 663 670 self.state.mouse = true; 664 671 if (self.caps.sgr_pixels) { 665 672 log.debug("enabling mouse mode: pixel coordinates", .{}); 666 673 self.state.pixel_mouse = true; 667 - try writer.writeAll(ctlseqs.mouse_set_pixels); 674 + try tty.writeAll(ctlseqs.mouse_set_pixels); 668 675 } else { 669 676 log.debug("enabling mouse mode: cell coordinates", .{}); 670 - try writer.writeAll(ctlseqs.mouse_set); 677 + try tty.writeAll(ctlseqs.mouse_set); 671 678 } 672 679 } else { 673 - try writer.writeAll(ctlseqs.mouse_reset); 680 + try tty.writeAll(ctlseqs.mouse_reset); 674 681 } 675 682 } 676 683 ··· 709 716 pub fn loadImage( 710 717 self: *Vaxis, 711 718 alloc: std.mem.Allocator, 712 - writer: AnyWriter, 719 + tty: AnyWriter, 713 720 src: Image.Source, 714 721 ) !Image { 715 722 if (!self.caps.kitty_graphics) return error.NoGraphicsCapability; ··· 730 737 const id = self.next_img_id; 731 738 732 739 if (encoded.len < 4096) { 733 - try writer.print( 740 + try tty.print( 734 741 "\x1b_Gf=100,i={d};{s}\x1b\\", 735 742 .{ 736 743 id, ··· 740 747 } else { 741 748 var n: usize = 4096; 742 749 743 - try writer.print( 750 + try tty.print( 744 751 "\x1b_Gf=100,i={d},m=1;{s}\x1b\\", 745 752 .{ id, encoded[0..n] }, 746 753 ); 747 754 while (n < encoded.len) : (n += 4096) { 748 755 const end: usize = @min(n + 4096, encoded.len); 749 756 const m: u2 = if (end == encoded.len) 0 else 1; 750 - try writer.print( 757 + try tty.print( 751 758 "\x1b_Gm={d};{s}\x1b\\", 752 759 .{ 753 760 m, ··· 764 771 } 765 772 766 773 /// deletes an image from the terminal's memory 767 - pub fn freeImage(_: Vaxis, writer: AnyWriter, id: u32) void { 768 - writer.print("\x1b_Ga=d,d=I,i={d};\x1b\\", .{id}) catch |err| { 774 + pub fn freeImage(_: Vaxis, tty: AnyWriter, id: u32) void { 775 + tty.print("\x1b_Ga=d,d=I,i={d};\x1b\\", .{id}) catch |err| { 769 776 log.err("couldn't delete image {d}: {}", .{ id, err }); 770 777 return; 771 778 }; 772 779 } 773 780 774 - pub fn copyToSystemClipboard(_: Vaxis, writer: AnyWriter, text: []const u8, encode_allocator: std.mem.Allocator) !void { 781 + pub fn copyToSystemClipboard(_: Vaxis, tty: AnyWriter, text: []const u8, encode_allocator: std.mem.Allocator) !void { 775 782 const encoder = std.base64.standard.Encoder; 776 783 const size = encoder.calcSize(text.len); 777 784 const buf = try encode_allocator.alloc(u8, size); 778 785 const b64 = encoder.encode(buf, text); 779 786 defer encode_allocator.free(buf); 780 - try writer.print( 787 + try tty.print( 781 788 ctlseqs.osc52_clipboard_copy, 782 789 .{b64}, 783 790 ); 784 791 } 785 792 786 - pub fn requestSystemClipboard(self: Vaxis, writer: AnyWriter) !void { 793 + pub fn requestSystemClipboard(self: Vaxis, tty: AnyWriter) !void { 787 794 if (self.opts.system_clipboard_allocator == null) return error.NoClipboardAllocator; 788 - try writer.print( 795 + try tty.print( 789 796 ctlseqs.osc52_clipboard_request, 790 797 .{}, 791 798 ); ··· 794 801 /// Request a color report from the terminal. Note: not all terminals support 795 802 /// reporting colors. It is always safe to try, but you may not receive a 796 803 /// response. 797 - pub fn queryColor(_: Vaxis, writer: AnyWriter, kind: Cell.Color.Kind) !void { 804 + pub fn queryColor(_: Vaxis, tty: AnyWriter, kind: Cell.Color.Kind) !void { 798 805 switch (kind) { 799 - .fg => try writer.writeAll(ctlseqs.osc10_query), 800 - .bg => try writer.writeAll(ctlseqs.osc11_query), 801 - .cursor => try writer.writeAll(ctlseqs.osc12_query), 802 - .index => |idx| try writer.print(ctlseqs.osc4_query, .{idx}), 806 + .fg => try tty.writeAll(ctlseqs.osc10_query), 807 + .bg => try tty.writeAll(ctlseqs.osc11_query), 808 + .cursor => try tty.writeAll(ctlseqs.osc12_query), 809 + .index => |idx| try tty.print(ctlseqs.osc4_query, .{idx}), 803 810 } 804 811 } 805 812 ··· 808 815 /// capability. Support can be detected by checking the value of 809 816 /// vaxis.caps.color_scheme_updates. The initial scheme will be reported when 810 817 /// subscribing. 811 - pub fn subscribeToColorSchemeUpdates(self: Vaxis, writer: AnyWriter) !void { 812 - try writer.writeAll(ctlseqs.color_scheme_request); 813 - try writer.writeAll(ctlseqs.color_scheme_set); 818 + pub fn subscribeToColorSchemeUpdates(self: Vaxis, tty: AnyWriter) !void { 819 + try tty.writeAll(ctlseqs.color_scheme_request); 820 + try tty.writeAll(ctlseqs.color_scheme_set); 814 821 self.state.color_scheme_updates = true; 815 822 } 816 823 817 - pub fn deviceStatusReport(_: Vaxis, writer: AnyWriter) !void { 818 - try writer.writeAll(ctlseqs.device_status_report); 824 + pub fn deviceStatusReport(_: Vaxis, tty: AnyWriter) !void { 825 + try tty.writeAll(ctlseqs.device_status_report); 819 826 }