Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions build.zig.zon
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
.hash = "zigimg-0.1.0-8_eo2nWlEgCddu8EGLOM_RkYshx3sC8tWv-yYA4-htS6",
},
.zg = .{
.url = "git+https://codeberg.org/atman/zg#0b05141b033043c5f7bcd72048a48eef6531ea6c",
.hash = "zg-0.14.0-oGqU3KEFswIffnDu8eAE2XlhzwcfgjwtM6akIc5L7cEV",
.url = "https://codeberg.org/atman/zg/archive/v0.14.1.tar.gz",
.hash = "zg-0.14.1-oGqU3IQ_tALZIiBN026_NTaPJqU-Upm8P_C7QED2Rzm8",
},
},
.paths = .{
Expand Down
4 changes: 2 additions & 2 deletions src/InternalScreen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@ pub const InternalCell = struct {
}
};

arena: *std.heap.ArenaAllocator = undefined,
arena: *std.heap.ArenaAllocator,
width: u16 = 0,
height: u16 = 0,

buf: []InternalCell = undefined,
buf: []InternalCell,

cursor_row: u16 = 0,
cursor_col: u16 = 0,
Expand Down
56 changes: 48 additions & 8 deletions src/Loop.zig
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pub fn Loop(comptime T: type) type {
queue: Queue(T, 512) = .{},
thread: ?std.Thread = null,
should_quit: bool = false,
winch_eventfd: std.posix.fd_t = -1,

/// Initialize the event loop. This is an intrusive init so that we have
/// a stable pointer to register signal callbacks with posix TTYs
Expand All @@ -32,6 +33,9 @@ pub fn Loop(comptime T: type) type {
.windows => {},
else => {
if (!builtin.is_test) {
if (self.winch_eventfd < 0) {
self.winch_eventfd = try std.posix.eventfd(0, 0);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eventfd isn't available on macOS or the BSDs. This would need to move a .linux block

}
const handler: Tty.SignalHandler = .{
.context = self,
.callback = Self.winsizeCallback,
Expand Down Expand Up @@ -98,10 +102,9 @@ pub fn Loop(comptime T: type) type {
// We will be receiving winsize updates in-band
if (self.vaxis.state.in_band_resize) return;

const winsize = Tty.getWinsize(self.tty.fd) catch return;
if (@hasField(Event, "winsize")) {
self.postEvent(.{ .winsize = winsize });
}
if (self.winch_eventfd < 0) return;
// notify the event loop that a winsize signal was received
_ = std.posix.write(self.winch_eventfd, &[8]u8{ 0, 0, 0, 0, 0, 0, 0, 1 }) catch {};
}

/// read input from the tty. This is run in a separate thread
Expand All @@ -124,21 +127,53 @@ pub fn Loop(comptime T: type) type {
}
},
else => {
// get our initial winsize
const winsize = try Tty.getWinsize(self.tty.fd);
if (@hasField(Event, "winsize")) {
self.postEvent(.{ .winsize = winsize });
{
// get our initial winsize
const winsize = try Tty.getWinsize(self.tty.fd);
if (@hasField(Event, "winsize")) {
self.postEvent(.{ .winsize = winsize });
}
}

var parser: Parser = .{
.grapheme_data = grapheme_data,
};

// initialize poll fds
var fds: [2]std.posix.pollfd = .{
.{
.fd = self.tty.fd,
.events = std.posix.POLL.IN,
.revents = 0,
},
.{
.fd = self.winch_eventfd,
.events = std.posix.POLL.IN,
.revents = 0,
},
};
// initialize the read buffer
var buf: [1024]u8 = undefined;
var read_start: usize = 0;
// read loop
read_loop: while (!self.should_quit) {
if (@hasField(Event, "winsize")) {
// self.init might be called after start, so we need to check
fds[1].fd = self.winch_eventfd;
_ = try std.posix.poll(&fds, -1);
if (fds[1].revents & std.posix.POLL.IN != 0) {
fds[1].revents = 0;
var tmp: [8]u8 = undefined;
_ = try std.posix.read(self.winch_eventfd, &tmp);

const winsize = try Tty.getWinsize(self.tty.fd);
self.postEvent(.{ .winsize = winsize });
}

if (fds[0].revents & std.posix.POLL.IN == 0) continue :read_loop;
fds[0].revents = 0;
}

const n = try self.tty.read(buf[read_start..]);
var seq_start: usize = 0;
while (seq_start < n) {
Expand Down Expand Up @@ -289,6 +324,11 @@ pub fn handleEventGeneric(self: anytype, vx: *Vaxis, cache: *GraphemeCache, Even
return self.postEvent(.{ .mouse = vx.translateMouse(mouse) });
}
},
.mouse_leave => {
if (@hasField(Event, "mouse_leave")) {
return self.postEvent(.mouse_leave);
}
},
.focus_in => {
if (@hasField(Event, "focus_in")) {
return self.postEvent(.focus_in);
Expand Down
6 changes: 5 additions & 1 deletion src/Parser.zig
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const mouse_bits = struct {
const shift: u8 = 0b00000100;
const alt: u8 = 0b00001000;
const ctrl: u8 = 0b00010000;
const leave: u16 = 0b100000000;
};

// the state of the parser
Expand Down Expand Up @@ -116,7 +117,7 @@ inline fn parseGround(input: []const u8, data: *const Graphemes) !Result {

// Check if we have a multi-codepoint grapheme
var code = cp.code;
var g_state: Graphemes.State = .{};
var g_state: Graphemes.IterState = .{};
var prev_cp = code;
while (iter.next()) |next_cp| {
if (Graphemes.graphemeBreak(prev_cp, next_cp.code, data, &g_state)) {
Expand Down Expand Up @@ -679,6 +680,9 @@ inline fn parseMouse(input: []const u8, full_input: []const u8) Result {
return null_event;
}

if (button_mask & mouse_bits.leave > 0)
return .{ .event = .mouse_leave, .n = if (xterm) 6 else input.len };

const button: Mouse.Button = @enumFromInt(button_mask & mouse_bits.buttons);
const motion = button_mask & mouse_bits.motion > 0;
const shift = button_mask & mouse_bits.shift > 0;
Expand Down
6 changes: 1 addition & 5 deletions src/Screen.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ const Cell = @import("Cell.zig");
const Shape = @import("Mouse.zig").Shape;
const Image = @import("Image.zig");
const Winsize = @import("main.zig").Winsize;
const Unicode = @import("Unicode.zig");
const Method = @import("gwidth.zig").Method;

const Screen = @This();
Expand All @@ -22,14 +21,12 @@ cursor_row: u16 = 0,
cursor_col: u16 = 0,
cursor_vis: bool = false,

unicode: *const Unicode = undefined,

width_method: Method = .wcwidth,

mouse_shape: Shape = .default,
cursor_shape: Cell.CursorShape = .default,

pub fn init(alloc: std.mem.Allocator, winsize: Winsize, unicode: *const Unicode) std.mem.Allocator.Error!Screen {
pub fn init(alloc: std.mem.Allocator, winsize: Winsize) std.mem.Allocator.Error!Screen {
const w = winsize.cols;
const h = winsize.rows;
const self = Screen{
Expand All @@ -38,7 +35,6 @@ pub fn init(alloc: std.mem.Allocator, winsize: Winsize, unicode: *const Unicode)
.height = h,
.width_pix = winsize.x_pixel,
.height_pix = winsize.y_pixel,
.unicode = unicode,
};
const base_cell: Cell = .{};
@memset(self.buf, base_cell);
Expand Down
21 changes: 16 additions & 5 deletions src/Vaxis.zig
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ sgr: enum {
legacy,
} = .standard,

/// Enable workarounds for escape sequence handling issues/bugs in terminals
/// So far this just enables a UL escape sequence workaround for conpty
enable_workarounds: bool = true,

state: struct {
/// if we are in the alt screen
alt_screen: bool = false,
Expand All @@ -104,7 +108,7 @@ pub fn init(alloc: std.mem.Allocator, opts: Options) !Vaxis {
return .{
.opts = opts,
.screen = .{},
.screen_last = try .init(alloc, 80, 24),
.screen_last = try .init(alloc, 0, 0),
.unicode = try Unicode.init(alloc),
};
}
Expand Down Expand Up @@ -189,7 +193,7 @@ pub fn resize(
) !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);
self.screen = try Screen.init(alloc, winsize);
self.screen.width_method = self.caps.unicode;
// try self.screen.int(alloc, winsize.cols, winsize.rows);
// we only init our current screen. This has the effect of redrawing
Expand Down Expand Up @@ -217,6 +221,7 @@ pub fn window(self: *Vaxis) Window {
.width = self.screen.width,
.height = self.screen.height,
.screen = &self.screen,
.unicode = &self.unicode,
};
}

Expand Down Expand Up @@ -574,7 +579,9 @@ pub fn render(self: *Vaxis, tty: AnyWriter) !void {
}
},
.rgb => |rgb| {
switch (self.sgr) {
if (self.enable_workarounds)
try tty.print(ctlseqs.ul_rgb_conpty, .{ rgb[0], rgb[1], rgb[2] })
else switch (self.sgr) {
.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] }),
}
Expand Down Expand Up @@ -1237,9 +1244,13 @@ pub fn prettyPrint(self: *Vaxis, tty: AnyWriter) !void {
}
},
.rgb => |rgb| {
switch (self.sgr) {
if (self.enable_workarounds)
try tty.print(ctlseqs.ul_rgb_conpty, .{ rgb[0], rgb[1], rgb[2] })
else switch (self.sgr) {
.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] }),
.legacy => {
try tty.print(ctlseqs.ul_rgb_legacy, .{ rgb[0], rgb[1], rgb[2] });
},
}
},
}
Expand Down
20 changes: 14 additions & 6 deletions src/Window.zig
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ width: u16,
height: u16,

screen: *Screen,
unicode: *const Unicode,

/// Creates a new window with offset relative to parent and size clamped to the
/// parent's size. Windows do not retain a reference to their parent and are
Expand All @@ -49,6 +50,7 @@ fn initChild(
.width = @min(width, max_width),
.height = @min(height, max_height),
.screen = self.screen,
.unicode = self.unicode,
};
}

Expand Down Expand Up @@ -205,7 +207,7 @@ pub fn clear(self: Window) void {

/// returns the width of the grapheme. This depends on the terminal capabilities
pub fn gwidth(self: Window, str: []const u8) u16 {
return gw.gwidth(str, self.screen.width_method, &self.screen.unicode.width_data);
return gw.gwidth(str, self.screen.width_method, &self.unicode.width_data);
}

/// fills the window with the provided cell
Expand Down Expand Up @@ -293,7 +295,7 @@ pub fn print(self: Window, segments: []const Segment, opts: PrintOptions) PrintR
.grapheme => {
var col: u16 = opts.col_offset;
const overflow: bool = blk: for (segments) |segment| {
var iter = self.screen.unicode.graphemeIterator(segment.text);
var iter = self.unicode.graphemeIterator(segment.text);
while (iter.next()) |grapheme| {
if (col >= self.width) {
row += 1;
Expand Down Expand Up @@ -376,7 +378,7 @@ pub fn print(self: Window, segments: []const Segment, opts: PrintOptions) PrintR
col = 0;
}

var grapheme_iterator = self.screen.unicode.graphemeIterator(word);
var grapheme_iterator = self.unicode.graphemeIterator(word);
while (grapheme_iterator.next()) |grapheme| {
soft_wrapped = false;
if (row >= self.height) {
Expand Down Expand Up @@ -415,7 +417,7 @@ pub fn print(self: Window, segments: []const Segment, opts: PrintOptions) PrintR
.none => {
var col: u16 = opts.col_offset;
const overflow: bool = blk: for (segments) |segment| {
var iter = self.screen.unicode.graphemeIterator(segment.text);
var iter = self.unicode.graphemeIterator(segment.text);
while (iter.next()) |grapheme| {
if (col >= self.width) break :blk true;
const s = grapheme.bytes(segment.text);
Expand Down Expand Up @@ -487,6 +489,7 @@ test "Window size set" {
.width = 20,
.height = 20,
.screen = undefined,
.unicode = undefined,
};

const ch = parent.initChild(1, 1, null, null);
Expand All @@ -503,6 +506,7 @@ test "Window size set too big" {
.width = 20,
.height = 20,
.screen = undefined,
.unicode = undefined,
};

const ch = parent.initChild(0, 0, 21, 21);
Expand All @@ -519,6 +523,7 @@ test "Window size set too big with offset" {
.width = 20,
.height = 20,
.screen = undefined,
.unicode = undefined,
};

const ch = parent.initChild(10, 10, 21, 21);
Expand All @@ -535,6 +540,7 @@ test "Window size nested offsets" {
.width = 20,
.height = 20,
.screen = undefined,
.unicode = undefined,
};

const ch = parent.initChild(10, 10, 21, 21);
Expand All @@ -551,6 +557,7 @@ test "Window offsets" {
.width = 20,
.height = 20,
.screen = undefined,
.unicode = undefined,
};

const ch = parent.initChild(10, 10, 21, 21);
Expand All @@ -565,7 +572,7 @@ test "print: grapheme" {
const alloc = std.testing.allocator_instance.allocator();
const unicode = try Unicode.init(alloc);
defer unicode.deinit(alloc);
var screen: Screen = .{ .width_method = .unicode, .unicode = &unicode };
var screen: Screen = .{ .width_method = .unicode };
const win: Window = .{
.x_off = 0,
.y_off = 0,
Expand All @@ -574,6 +581,7 @@ test "print: grapheme" {
.width = 4,
.height = 2,
.screen = &screen,
.unicode = &unicode,
};
const opts: PrintOptions = .{
.commit = false,
Expand Down Expand Up @@ -633,7 +641,6 @@ test "print: word" {
defer unicode.deinit(alloc);
var screen: Screen = .{
.width_method = .unicode,
.unicode = &unicode,
};
const win: Window = .{
.x_off = 0,
Expand All @@ -643,6 +650,7 @@ test "print: word" {
.width = 4,
.height = 2,
.screen = &screen,
.unicode = &unicode,
};
const opts: PrintOptions = .{
.commit = false,
Expand Down
1 change: 1 addition & 0 deletions src/ctlseqs.zig
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ pub const ul_indexed_legacy = "\x1b[58;5;{d}m";
pub const fg_rgb_legacy = "\x1b[38;2;{d};{d};{d}m";
pub const bg_rgb_legacy = "\x1b[48;2;{d};{d};{d}m";
pub const ul_rgb_legacy = "\x1b[58;2;{d};{d};{d}m";
pub const ul_rgb_conpty = "\x1b[58:2::{d}:{d}:{d}m";

// Underlines
pub const ul_off = "\x1b[24m"; // NOTE: this could be \x1b[4:0m but is not as widely supported
Expand Down
1 change: 1 addition & 0 deletions src/event.zig
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub const Event = union(enum) {
key_press: Key,
key_release: Key,
mouse: Mouse,
mouse_leave,
focus_in,
focus_out,
paste_start, // bracketed paste start
Expand Down
Loading