vaxis: implement hyperlinks (osc8)
This requires additional allocations anytime there is a hyperlink Signed-off-by: Tim Culverhouse <tim@timculverhouse.com>
This commit is contained in:
parent
9950c9eac5
commit
8f311da873
4 changed files with 41 additions and 16 deletions
|
@ -18,7 +18,7 @@ Contributions are welcome.
|
|||
| Feature | Vaxis | libvaxis | notcurses |
|
||||
| ------------------------------ | :---: | :------: | :-------: |
|
||||
| RGB | ✅ | ✅ | ✅ |
|
||||
| Hyperlinks | ✅ | planned | ❌ |
|
||||
| Hyperlinks | ✅ | ✅ | ❌ |
|
||||
| Bracketed Paste | ✅ | ✅ | ❌ |
|
||||
| Kitty Keyboard | ✅ | ✅ | ✅ |
|
||||
| Styled Underlines | ✅ | ✅ | ✅ |
|
||||
|
|
|
@ -11,11 +11,16 @@ const InternalScreen = @This();
|
|||
pub const InternalCell = struct {
|
||||
char: std.ArrayList(u8) = undefined,
|
||||
style: Style = .{},
|
||||
uri: std.ArrayList(u8) = undefined,
|
||||
uri_id: std.ArrayList(u8) = undefined,
|
||||
// if we got skipped because of a wide character
|
||||
skipped: bool = false,
|
||||
|
||||
pub fn eql(self: InternalCell, cell: Cell) bool {
|
||||
return std.mem.eql(u8, self.char.items, cell.char.grapheme) and std.meta.eql(self.style, cell.style);
|
||||
return std.mem.eql(u8, self.char.items, cell.char.grapheme) and
|
||||
std.meta.eql(self.style, cell.style) and
|
||||
std.mem.eql(u8, self.uri.items, cell.link.uri) and
|
||||
std.mem.eql(u8, self.uri_id.items, cell.link.params);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -37,6 +42,8 @@ pub fn init(alloc: std.mem.Allocator, w: usize, h: usize) !InternalScreen {
|
|||
for (screen.buf, 0..) |_, i| {
|
||||
screen.buf[i] = .{
|
||||
.char = try std.ArrayList(u8).initCapacity(alloc, 1),
|
||||
.uri = std.ArrayList(u8).init(alloc),
|
||||
.uri_id = std.ArrayList(u8).init(alloc),
|
||||
};
|
||||
}
|
||||
screen.width = w;
|
||||
|
@ -47,6 +54,8 @@ pub fn init(alloc: std.mem.Allocator, w: usize, h: usize) !InternalScreen {
|
|||
pub fn deinit(self: *InternalScreen, alloc: std.mem.Allocator) void {
|
||||
for (self.buf, 0..) |_, i| {
|
||||
self.buf[i].char.deinit();
|
||||
self.buf[i].uri.deinit();
|
||||
self.buf[i].uri_id.deinit();
|
||||
}
|
||||
|
||||
alloc.free(self.buf);
|
||||
|
@ -57,8 +66,7 @@ pub fn writeCell(
|
|||
self: *InternalScreen,
|
||||
col: usize,
|
||||
row: usize,
|
||||
char: []const u8,
|
||||
style: Style,
|
||||
cell: Cell,
|
||||
) void {
|
||||
if (self.width < col) {
|
||||
// column out of bounds
|
||||
|
@ -71,8 +79,16 @@ pub fn writeCell(
|
|||
const i = (row * self.width) + col;
|
||||
assert(i < self.buf.len);
|
||||
self.buf[i].char.clearRetainingCapacity();
|
||||
self.buf[i].char.appendSlice(char) catch {
|
||||
self.buf[i].char.appendSlice(cell.char.grapheme) catch {
|
||||
log.warn("couldn't write grapheme", .{});
|
||||
};
|
||||
self.buf[i].style = style;
|
||||
self.buf[i].uri.clearRetainingCapacity();
|
||||
self.buf[i].uri.appendSlice(cell.link.uri) catch {
|
||||
log.warn("couldn't write uri", .{});
|
||||
};
|
||||
self.buf[i].uri.clearRetainingCapacity();
|
||||
self.buf[i].uri_id.appendSlice(cell.link.params) catch {
|
||||
log.warn("couldn't write uri_id", .{});
|
||||
};
|
||||
self.buf[i].style = cell.style;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
pub const Cell = struct {
|
||||
char: Character = .{},
|
||||
style: Style = .{},
|
||||
link: Hyperlink = .{},
|
||||
};
|
||||
|
||||
pub const Character = struct {
|
||||
|
@ -8,6 +9,12 @@ pub const Character = struct {
|
|||
width: usize = 1,
|
||||
};
|
||||
|
||||
pub const Hyperlink = struct {
|
||||
uri: []const u8 = "",
|
||||
/// ie "id=app-1234"
|
||||
params: []const u8 = "",
|
||||
};
|
||||
|
||||
pub const Style = struct {
|
||||
pub const Underline = enum {
|
||||
off,
|
||||
|
@ -24,8 +31,6 @@ pub const Style = struct {
|
|||
ul_style: Underline = .off,
|
||||
// TODO: url should maybe go outside of style. We'll need to allocate these
|
||||
// in the internal screen
|
||||
url: ?[]const u8 = null,
|
||||
url_params: ?[]const u8 = null,
|
||||
|
||||
bold: bool = false,
|
||||
dim: bool = false,
|
||||
|
|
|
@ -11,6 +11,7 @@ const InternalScreen = @import("InternalScreen.zig");
|
|||
const Window = @import("Window.zig");
|
||||
const Options = @import("Options.zig");
|
||||
const Style = @import("cell.zig").Style;
|
||||
const Hyperlink = @import("cell.zig").Hyperlink;
|
||||
const gwidth = @import("gwidth.zig");
|
||||
const Shape = @import("Mouse.zig").Shape;
|
||||
|
||||
|
@ -275,6 +276,7 @@ pub fn Vaxis(comptime T: type) type {
|
|||
var row: usize = 0;
|
||||
var col: usize = 0;
|
||||
var cursor: Style = .{};
|
||||
var link: Hyperlink = .{};
|
||||
|
||||
var i: usize = 0;
|
||||
while (i < self.screen.buf.len) {
|
||||
|
@ -301,15 +303,18 @@ pub fn Vaxis(comptime T: type) type {
|
|||
reposition = true;
|
||||
// Close any osc8 sequence we might be in before
|
||||
// repositioning
|
||||
if (cursor.url) |_| {
|
||||
if (link.uri.len > 0) {
|
||||
_ = try tty.write(ctlseqs.osc8_clear);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
self.screen_last.buf[i].skipped = false;
|
||||
defer cursor = cell.style;
|
||||
defer {
|
||||
cursor = cell.style;
|
||||
link = cell.link;
|
||||
}
|
||||
// Set this cell in the last frame
|
||||
self.screen_last.writeCell(col, row, cell.char.grapheme, cell.style);
|
||||
self.screen_last.writeCell(col, row, cell);
|
||||
|
||||
// reposition the cursor, if needed
|
||||
if (reposition) {
|
||||
|
@ -442,16 +447,15 @@ pub fn Vaxis(comptime T: type) type {
|
|||
}
|
||||
|
||||
// url
|
||||
if (!std.meta.eql(cursor.url, cell.style.url)) {
|
||||
const url = cell.style.url orelse "";
|
||||
var ps = cell.style.url_params orelse "";
|
||||
if (url.len == 0) {
|
||||
if (!std.meta.eql(link.uri, cell.link.uri)) {
|
||||
var ps = cell.link.params;
|
||||
if (cell.link.uri.len == 0) {
|
||||
// Empty out the params no matter what if we don't have
|
||||
// a url
|
||||
ps = "";
|
||||
}
|
||||
const writer = tty.buffered_writer.writer();
|
||||
try std.fmt.format(writer, ctlseqs.osc8, .{ ps, url });
|
||||
try std.fmt.format(writer, ctlseqs.osc8, .{ ps, cell.link.uri });
|
||||
}
|
||||
_ = try tty.write(cell.char.grapheme);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue