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

All vaxis functions that write to the terminal now require an
io.AnyWriter as a parameter
This commit is contained in:
Tim Culverhouse 2024-05-29 13:13:54 -05:00
parent 59abd7d7d4
commit 5f41978054
13 changed files with 294 additions and 517 deletions

View file

@ -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());
}
}
```

View file

@ -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());
}
}

View file

@ -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());
}
}

View file

@ -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());
}
}

View file

@ -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());
}
}

View file

@ -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)});
}

View file

@ -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());
}
}

View file

@ -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();
}
}

View file

@ -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,
};

View file

@ -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());
}
}

View file

@ -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 => {},
}

View file

@ -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 => {

View file

@ -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 writer.print(ctlseqs.cuf, .{col});
try tty.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);
}