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 |
|
| Feature | Vaxis | libvaxis | notcurses |
|
||||||
| ------------------------------ | :---: | :------: | :-------: |
|
| ------------------------------ | :---: | :------: | :-------: |
|
||||||
| RGB | ✅ | ✅ | ✅ |
|
| RGB | ✅ | ✅ | ✅ |
|
||||||
| Hyperlinks | ✅ | planned | ❌ |
|
| Hyperlinks | ✅ | ✅ | ❌ |
|
||||||
| Bracketed Paste | ✅ | ✅ | ❌ |
|
| Bracketed Paste | ✅ | ✅ | ❌ |
|
||||||
| Kitty Keyboard | ✅ | ✅ | ✅ |
|
| Kitty Keyboard | ✅ | ✅ | ✅ |
|
||||||
| Styled Underlines | ✅ | ✅ | ✅ |
|
| Styled Underlines | ✅ | ✅ | ✅ |
|
||||||
|
|
|
@ -11,11 +11,16 @@ const InternalScreen = @This();
|
||||||
pub const InternalCell = struct {
|
pub const InternalCell = struct {
|
||||||
char: std.ArrayList(u8) = undefined,
|
char: std.ArrayList(u8) = undefined,
|
||||||
style: Style = .{},
|
style: Style = .{},
|
||||||
|
uri: std.ArrayList(u8) = undefined,
|
||||||
|
uri_id: std.ArrayList(u8) = undefined,
|
||||||
// if we got skipped because of a wide character
|
// if we got skipped because of a wide character
|
||||||
skipped: bool = false,
|
skipped: bool = false,
|
||||||
|
|
||||||
pub fn eql(self: InternalCell, cell: Cell) bool {
|
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| {
|
for (screen.buf, 0..) |_, i| {
|
||||||
screen.buf[i] = .{
|
screen.buf[i] = .{
|
||||||
.char = try std.ArrayList(u8).initCapacity(alloc, 1),
|
.char = try std.ArrayList(u8).initCapacity(alloc, 1),
|
||||||
|
.uri = std.ArrayList(u8).init(alloc),
|
||||||
|
.uri_id = std.ArrayList(u8).init(alloc),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
screen.width = w;
|
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 {
|
pub fn deinit(self: *InternalScreen, alloc: std.mem.Allocator) void {
|
||||||
for (self.buf, 0..) |_, i| {
|
for (self.buf, 0..) |_, i| {
|
||||||
self.buf[i].char.deinit();
|
self.buf[i].char.deinit();
|
||||||
|
self.buf[i].uri.deinit();
|
||||||
|
self.buf[i].uri_id.deinit();
|
||||||
}
|
}
|
||||||
|
|
||||||
alloc.free(self.buf);
|
alloc.free(self.buf);
|
||||||
|
@ -57,8 +66,7 @@ pub fn writeCell(
|
||||||
self: *InternalScreen,
|
self: *InternalScreen,
|
||||||
col: usize,
|
col: usize,
|
||||||
row: usize,
|
row: usize,
|
||||||
char: []const u8,
|
cell: Cell,
|
||||||
style: Style,
|
|
||||||
) void {
|
) void {
|
||||||
if (self.width < col) {
|
if (self.width < col) {
|
||||||
// column out of bounds
|
// column out of bounds
|
||||||
|
@ -71,8 +79,16 @@ pub fn writeCell(
|
||||||
const i = (row * self.width) + col;
|
const i = (row * self.width) + col;
|
||||||
assert(i < self.buf.len);
|
assert(i < self.buf.len);
|
||||||
self.buf[i].char.clearRetainingCapacity();
|
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", .{});
|
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 {
|
pub const Cell = struct {
|
||||||
char: Character = .{},
|
char: Character = .{},
|
||||||
style: Style = .{},
|
style: Style = .{},
|
||||||
|
link: Hyperlink = .{},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub const Character = struct {
|
pub const Character = struct {
|
||||||
|
@ -8,6 +9,12 @@ pub const Character = struct {
|
||||||
width: usize = 1,
|
width: usize = 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Hyperlink = struct {
|
||||||
|
uri: []const u8 = "",
|
||||||
|
/// ie "id=app-1234"
|
||||||
|
params: []const u8 = "",
|
||||||
|
};
|
||||||
|
|
||||||
pub const Style = struct {
|
pub const Style = struct {
|
||||||
pub const Underline = enum {
|
pub const Underline = enum {
|
||||||
off,
|
off,
|
||||||
|
@ -24,8 +31,6 @@ pub const Style = struct {
|
||||||
ul_style: Underline = .off,
|
ul_style: Underline = .off,
|
||||||
// TODO: url should maybe go outside of style. We'll need to allocate these
|
// TODO: url should maybe go outside of style. We'll need to allocate these
|
||||||
// in the internal screen
|
// in the internal screen
|
||||||
url: ?[]const u8 = null,
|
|
||||||
url_params: ?[]const u8 = null,
|
|
||||||
|
|
||||||
bold: bool = false,
|
bold: bool = false,
|
||||||
dim: bool = false,
|
dim: bool = false,
|
||||||
|
|
|
@ -11,6 +11,7 @@ const InternalScreen = @import("InternalScreen.zig");
|
||||||
const Window = @import("Window.zig");
|
const Window = @import("Window.zig");
|
||||||
const Options = @import("Options.zig");
|
const Options = @import("Options.zig");
|
||||||
const Style = @import("cell.zig").Style;
|
const Style = @import("cell.zig").Style;
|
||||||
|
const Hyperlink = @import("cell.zig").Hyperlink;
|
||||||
const gwidth = @import("gwidth.zig");
|
const gwidth = @import("gwidth.zig");
|
||||||
const Shape = @import("Mouse.zig").Shape;
|
const Shape = @import("Mouse.zig").Shape;
|
||||||
|
|
||||||
|
@ -275,6 +276,7 @@ pub fn Vaxis(comptime T: type) type {
|
||||||
var row: usize = 0;
|
var row: usize = 0;
|
||||||
var col: usize = 0;
|
var col: usize = 0;
|
||||||
var cursor: Style = .{};
|
var cursor: Style = .{};
|
||||||
|
var link: Hyperlink = .{};
|
||||||
|
|
||||||
var i: usize = 0;
|
var i: usize = 0;
|
||||||
while (i < self.screen.buf.len) {
|
while (i < self.screen.buf.len) {
|
||||||
|
@ -301,15 +303,18 @@ pub fn Vaxis(comptime T: type) type {
|
||||||
reposition = true;
|
reposition = true;
|
||||||
// Close any osc8 sequence we might be in before
|
// Close any osc8 sequence we might be in before
|
||||||
// repositioning
|
// repositioning
|
||||||
if (cursor.url) |_| {
|
if (link.uri.len > 0) {
|
||||||
_ = try tty.write(ctlseqs.osc8_clear);
|
_ = try tty.write(ctlseqs.osc8_clear);
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
self.screen_last.buf[i].skipped = false;
|
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
|
// 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
|
// reposition the cursor, if needed
|
||||||
if (reposition) {
|
if (reposition) {
|
||||||
|
@ -442,16 +447,15 @@ pub fn Vaxis(comptime T: type) type {
|
||||||
}
|
}
|
||||||
|
|
||||||
// url
|
// url
|
||||||
if (!std.meta.eql(cursor.url, cell.style.url)) {
|
if (!std.meta.eql(link.uri, cell.link.uri)) {
|
||||||
const url = cell.style.url orelse "";
|
var ps = cell.link.params;
|
||||||
var ps = cell.style.url_params orelse "";
|
if (cell.link.uri.len == 0) {
|
||||||
if (url.len == 0) {
|
|
||||||
// Empty out the params no matter what if we don't have
|
// Empty out the params no matter what if we don't have
|
||||||
// a url
|
// a url
|
||||||
ps = "";
|
ps = "";
|
||||||
}
|
}
|
||||||
const writer = tty.buffered_writer.writer();
|
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);
|
_ = try tty.write(cell.char.grapheme);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue