diff --git a/build.zig.zon b/build.zig.zon index 6172937..61f2788 100644 --- a/build.zig.zon +++ b/build.zig.zon @@ -36,8 +36,8 @@ .hash = "1220214dfb9a0806d9c8a059beb9e3b07811fd138cd5baeb9d1da432588920a084bf", }, .vaxis = .{ - .url = "https://github.com/rockorager/libvaxis/archive/0333e178bdccb786bf8ceedb7203068b8177716d.tar.gz", - .hash = "12208132b6f3dec359721d5df03887bead68615f969475b260863e69944f085308c9", + .url = "https://github.com/neurocyte/libvaxis/archive/417a50548fec88692c2ba8ce69d631fd8de32b37.tar.gz", + .hash = "122018b7ed20a6d52a6905b7e574ad40e8a67f53db1f51887d09897bdea0cf1e95de", }, .zg = .{ .url = "git+https://codeberg.org/dude_the_builder/zg#16735685fcc3410de361ba3411788ad1fb4fe188", diff --git a/src/renderer/notcurses/renderer.zig b/src/renderer/notcurses/renderer.zig index b8cbb1c..5b7b4ec 100644 --- a/src/renderer/notcurses/renderer.zig +++ b/src/renderer/notcurses/renderer.zig @@ -463,42 +463,42 @@ fn handle_bracketed_paste_end(self: *Self) !void { if (self.dispatch_event) |f| f(self.handler_ctx, try self.fmtmsg(.{ "system_clipboard", self.bracketed_paste_buffer.items })); } -pub fn set_terminal_title(text: []const u8) void { +pub fn set_terminal_title(_: *Self, text: []const u8) void { var writer = std.io.getStdOut().writer(); var buf: [std.posix.PATH_MAX]u8 = undefined; const term_cmd = std.fmt.bufPrint(&buf, OSC0_title ++ "{s}" ++ BEL, .{text}) catch return; _ = writer.write(term_cmd) catch return; } -pub fn copy_to_system_clipboard(tmp_a: std.mem.Allocator, text: []const u8) void { - copy_to_system_clipboard_with_errors(tmp_a, text) catch |e| log.logger(log_name).err("copy_to_system_clipboard", e); +pub fn copy_to_system_clipboard(self: *Self, text: []const u8) void { + self.copy_to_system_clipboard_with_errors(text) catch |e| log.logger(log_name).err("copy_to_system_clipboard", e); } -fn copy_to_system_clipboard_with_errors(tmp_a: std.mem.Allocator, text: []const u8) !void { +fn copy_to_system_clipboard_with_errors(self: *Self, text: []const u8) !void { var writer = std.io.getStdOut().writer(); const encoder = std.base64.standard.Encoder; const size = OSC52_clipboard.len + encoder.calcSize(text.len) + ST.len; - const buf = try tmp_a.alloc(u8, size); - defer tmp_a.free(buf); + const buf = try self.a.alloc(u8, size); + defer self.a.free(buf); @memcpy(buf[0..OSC52_clipboard.len], OSC52_clipboard); const b64 = encoder.encode(buf[OSC52_clipboard.len..], text); @memcpy(buf[OSC52_clipboard.len + b64.len ..], ST); _ = try writer.write(buf); } -pub fn request_system_clipboard() void { +pub fn request_system_clipboard(_: *Self) void { write_stdout(OSC52_clipboard ++ "?" ++ ST); } -pub fn request_mouse_cursor_text(push_or_pop: bool) void { +pub fn request_mouse_cursor_text(_: *Self, push_or_pop: bool) void { if (push_or_pop) mouse_cursor_push("text") else mouse_cursor_pop(); } -pub fn request_mouse_cursor_pointer(push_or_pop: bool) void { +pub fn request_mouse_cursor_pointer(_: *Self, push_or_pop: bool) void { if (push_or_pop) mouse_cursor_push("pointer") else mouse_cursor_pop(); } -pub fn request_mouse_cursor_default(push_or_pop: bool) void { +pub fn request_mouse_cursor_default(_: *Self, push_or_pop: bool) void { if (push_or_pop) mouse_cursor_push("default") else mouse_cursor_pop(); } diff --git a/src/renderer/vaxis/renderer.zig b/src/renderer/vaxis/renderer.zig index 4806a2a..ae4c995 100644 --- a/src/renderer/vaxis/renderer.zig +++ b/src/renderer/vaxis/renderer.zig @@ -48,7 +48,13 @@ const Event = union(enum) { pub fn init(a: std.mem.Allocator, handler_ctx: *anyopaque, no_alternate: bool) !Self { const opts: vaxis.Vaxis.Options = .{ - .kitty_keyboard_flags = .{ .report_events = true }, + .kitty_keyboard_flags = .{ + .disambiguate = true, + .report_events = true, + .report_alternate_keys = true, + .report_all_as_ctl_seqs = true, + .report_text = true, + }, }; return .{ .a = a, @@ -72,11 +78,12 @@ pub fn deinit(self: *Self) void { pub fn run(self: *Self) !void { if (self.vx.tty == null) self.vx.tty = try vaxis.Tty.init(); if (!self.no_alternate) try self.vx.enterAltScreen(); - try self.vx.queryTerminal(); + try self.vx.queryTerminalSend(); const ws = try vaxis.Tty.getWinsize(self.input_fd_blocking()); try self.vx.resize(self.a, ws); self.vx.queueRefresh(); try self.vx.setMouseMode(.pixels); + try self.vx.setBracketedPaste(true); } pub fn render(self: *Self) !void { @@ -129,7 +136,7 @@ pub fn process_input(self: *Self, input_: []const u8) !void { } } while (buf.len > 0) { - const result = try parser.parse(buf); + const result = try parser.parse(buf, self.a); if (result.n == 0) return; buf = buf[result.n..]; @@ -141,10 +148,14 @@ pub fn process_input(self: *Self, input_: []const u8) !void { event_type.PRESS, key_.codepoint, key_.shifted_codepoint orelse key_.codepoint, - key_.text orelse input.utils.key_id_string(key_.codepoint), + key_.text orelse input.utils.key_id_string(key_.base_layout_codepoint orelse key_.codepoint), @as(u8, @bitCast(key_.mods)), }); - if (self.dispatch_input) |f| f(self.handler_ctx, cbor_msg); + if (self.bracketed_paste and self.handle_bracketed_paste_input(cbor_msg) catch |e| { + self.bracketed_paste_buffer.clearAndFree(); + self.bracketed_paste = false; + return e; + }) {} else if (self.dispatch_input) |f| f(self.handler_ctx, cbor_msg); }, .key_release => |*key_| { const cbor_msg = try self.fmtmsg(.{ @@ -152,10 +163,10 @@ pub fn process_input(self: *Self, input_: []const u8) !void { event_type.RELEASE, key_.codepoint, key_.shifted_codepoint orelse key_.codepoint, - key_.text orelse input.utils.key_id_string(key_.codepoint), + key_.text orelse input.utils.key_id_string(key_.base_layout_codepoint orelse key_.codepoint), @as(u8, @bitCast(key_.mods)), }); - if (self.dispatch_input) |f| f(self.handler_ctx, cbor_msg); + if (self.bracketed_paste) {} else if (self.dispatch_input) |f| f(self.handler_ctx, cbor_msg); }, .mouse => |mouse| { const ypos = mouse.row - 1; @@ -208,29 +219,30 @@ pub fn process_input(self: *Self, input_: []const u8) !void { }; }, .focus_in => { - // FIXME + if (self.dispatch_event) |f| f(self.handler_ctx, try self.fmtmsg(.{"focus_in"})); }, .focus_out => { - // FIXME + if (self.dispatch_event) |f| f(self.handler_ctx, try self.fmtmsg(.{"focus_out"})); }, .paste_start => { self.bracketed_paste = true; self.bracketed_paste_buffer.clearRetainingCapacity(); }, - .paste_end => { - defer self.bracketed_paste_buffer.clearAndFree(); - if (!self.bracketed_paste) return; - self.bracketed_paste = false; - if (self.dispatch_event) |f| f(self.handler_ctx, try self.fmtmsg(.{ "system_clipboard", self.bracketed_paste_buffer.items })); + .paste_end => try self.handle_bracketed_paste_end(), + .paste => |text| { + defer self.a.free(text); + if (self.dispatch_event) |f| f(self.handler_ctx, try self.fmtmsg(.{ "system_clipboard", text })); }, .cap_unicode => { + self.logger.print("unicode capability detected", .{}); self.vx.caps.unicode = .unicode; self.vx.screen.width_method = .unicode; }, .cap_da1 => { - std.Thread.Futex.wake(&self.vx.query_futex, 10); + self.vx.enableDetectedFeatures() catch |e| self.logger.err("enable features", e); }, .cap_kitty_keyboard => { + self.logger.print("kitty keyboard capability detected", .{}); self.vx.caps.kitty_keyboard = true; }, .cap_kitty_graphics => { @@ -239,6 +251,7 @@ pub fn process_input(self: *Self, input_: []const u8) !void { } }, .cap_rgb => { + self.logger.print("rgb capability detected", .{}); self.vx.caps.rgb = true; }, } @@ -251,70 +264,55 @@ fn fmtmsg(self: *Self, value: anytype) ![]const u8 { return self.event_buffer.items; } -const OSC = "\x1B]"; // Operating System Command -const ST = "\x1B\\"; // String Terminator -const BEL = "\x07"; -const OSC0_title = OSC ++ "0;"; -const OSC52_clipboard = OSC ++ "52;c;"; -const OSC52_clipboard_paste = OSC ++ "52;p;"; -const OSC22_cursor = OSC ++ "22;"; -const OSC22_cursor_reply = OSC ++ "22:"; - -const CSI = "\x1B["; // Control Sequence Introducer -const CSI_bracketed_paste_enable = CSI ++ "?2004h"; -const CSI_bracketed_paste_disable = CSI ++ "?2004h"; -const CIS_bracketed_paste_begin = CSI ++ "200~"; -const CIS_bracketed_paste_end = CSI ++ "201~"; - -pub fn set_terminal_title(text: []const u8) void { - var writer = std.io.getStdOut().writer(); - var buf: [std.posix.PATH_MAX]u8 = undefined; - const term_cmd = std.fmt.bufPrint(&buf, OSC0_title ++ "{s}" ++ BEL, .{text}) catch return; - _ = writer.write(term_cmd) catch return; +fn handle_bracketed_paste_input(self: *Self, cbor_msg: []const u8) !bool { + var keypress: u32 = undefined; + var egc_: u32 = undefined; + if (try cbor.match(cbor_msg, .{ "I", cbor.number, cbor.extract(&keypress), cbor.extract(&egc_), cbor.string, 0 })) { + switch (keypress) { + key.ENTER => try self.bracketed_paste_buffer.appendSlice("\n"), + else => if (!key.synthesized_p(keypress)) { + var buf: [6]u8 = undefined; + const bytes = try ucs32_to_utf8(&[_]u32{egc_}, &buf); + try self.bracketed_paste_buffer.appendSlice(buf[0..bytes]); + } else { + try self.handle_bracketed_paste_end(); + return false; + }, + } + return true; + } + return false; } -pub fn copy_to_system_clipboard(tmp_a: std.mem.Allocator, text: []const u8) void { - copy_to_system_clipboard_with_errors(tmp_a, text) catch |e| log.logger(log_name).err("copy_to_system_clipboard", e); +fn handle_bracketed_paste_end(self: *Self) !void { + defer self.bracketed_paste_buffer.clearAndFree(); + if (!self.bracketed_paste) return; + self.bracketed_paste = false; + if (self.dispatch_event) |f| f(self.handler_ctx, try self.fmtmsg(.{ "system_clipboard", self.bracketed_paste_buffer.items })); } -fn copy_to_system_clipboard_with_errors(tmp_a: std.mem.Allocator, text: []const u8) !void { - var writer = std.io.getStdOut().writer(); - const encoder = std.base64.standard.Encoder; - const size = OSC52_clipboard.len + encoder.calcSize(text.len) + ST.len; - const buf = try tmp_a.alloc(u8, size); - defer tmp_a.free(buf); - @memcpy(buf[0..OSC52_clipboard.len], OSC52_clipboard); - const b64 = encoder.encode(buf[OSC52_clipboard.len..], text); - @memcpy(buf[OSC52_clipboard.len + b64.len ..], ST); - _ = try writer.write(buf); +pub fn set_terminal_title(self: *Self, text: []const u8) void { + self.vx.setTitle(text) catch {}; } -pub fn request_system_clipboard() void { - write_stdout(OSC52_clipboard ++ "?" ++ ST); +pub fn copy_to_system_clipboard(self: *Self, text: []const u8) void { + self.vx.copyToSystemClipboard(text, self.a) catch |e| log.logger(log_name).err("copy_to_system_clipboard", e); } -pub fn request_mouse_cursor_text(push_or_pop: bool) void { - if (push_or_pop) mouse_cursor_push("text") else mouse_cursor_pop(); +pub fn request_system_clipboard(self: *Self) void { + self.vx.requestSystemClipboard() catch |e| log.logger(log_name).err("request_system_clipboard", e); } -pub fn request_mouse_cursor_pointer(push_or_pop: bool) void { - if (push_or_pop) mouse_cursor_push("pointer") else mouse_cursor_pop(); +pub fn request_mouse_cursor_text(self: *Self, push_or_pop: bool) void { + if (push_or_pop) self.vx.setMouseShape(.text) else self.vx.setMouseShape(.default); } -pub fn request_mouse_cursor_default(push_or_pop: bool) void { - if (push_or_pop) mouse_cursor_push("default") else mouse_cursor_pop(); +pub fn request_mouse_cursor_pointer(self: *Self, push_or_pop: bool) void { + if (push_or_pop) self.vx.setMouseShape(.pointer) else self.vx.setMouseShape(.default); } -fn mouse_cursor_push(comptime name: []const u8) void { - write_stdout(OSC22_cursor ++ name ++ ST); -} - -fn mouse_cursor_pop() void { - write_stdout(OSC22_cursor ++ "default" ++ ST); -} - -fn write_stdout(bytes: []const u8) void { - _ = std.io.getStdOut().writer().write(bytes) catch |e| log.logger(log_name).err("stdout", e); +pub fn request_mouse_cursor_default(self: *Self, push_or_pop: bool) void { + if (push_or_pop) self.vx.setMouseShape(.default) else self.vx.setMouseShape(.default); } pub fn cursor_enable(self: *Self, y: c_int, x: c_int) !void { diff --git a/src/tui/Button.zig b/src/tui/Button.zig index a5ead47..317c22b 100644 --- a/src/tui/Button.zig +++ b/src/tui/Button.zig @@ -105,7 +105,7 @@ pub fn State(ctx_type: type) type { tui.need_render(); return true; } else if (try m.match(.{ "H", tp.extract(&self.hover) })) { - tui.renderer.request_mouse_cursor_pointer(self.hover); + tui.current().rdr.request_mouse_cursor_pointer(self.hover); tui.need_render(); return true; } diff --git a/src/tui/InputBox.zig b/src/tui/InputBox.zig index ad5ba1a..264a899 100644 --- a/src/tui/InputBox.zig +++ b/src/tui/InputBox.zig @@ -110,7 +110,7 @@ pub fn State(ctx_type: type) type { tui.need_render(); return true; } else if (try m.match(.{ "H", tp.extract(&self.hover) })) { - tui.renderer.request_mouse_cursor_pointer(self.hover); + tui.current().rdr.request_mouse_cursor_pointer(self.hover); tui.need_render(); return true; } diff --git a/src/tui/editor.zig b/src/tui/editor.zig index 4db85b8..58bc335 100644 --- a/src/tui/editor.zig +++ b/src/tui/editor.zig @@ -1860,7 +1860,7 @@ pub const Editor = struct { if (self.clipboard) |old| self.a.free(old); self.clipboard = text; - tui.renderer.copy_to_system_clipboard(self.a, text); + tui.current().rdr.copy_to_system_clipboard(text); } fn copy_selection(root: Buffer.Root, sel: Selection, text_a: Allocator, plane: Plane) ![]const u8 { @@ -1967,6 +1967,7 @@ pub const Editor = struct { if (!try ctx.args.match(.{tp.extract(&text)})) { if (self.clipboard) |text_| text = text_ else return; } + self.logger.print("paste: {d} bytes", .{text.len}); const b = self.buf_for_update() catch |e| return tp.exit_error(e); var root = b.root; if (self.cursels.items.len == 1) { @@ -1992,10 +1993,11 @@ pub const Editor = struct { } self.update_buf(root) catch |e| return tp.exit_error(e); self.clamp(); + self.need_render(); } pub fn system_paste(_: *Self, _: command.Context) tp.result { - tui.renderer.request_system_clipboard(); + tui.current().rdr.request_system_clipboard(); } pub fn delete_forward(self: *Self, _: command.Context) tp.result { @@ -2867,12 +2869,12 @@ pub const Editor = struct { pub fn enable_jump_mode(self: *Self, _: command.Context) tp.result { self.jump_mode = true; - tui.renderer.request_mouse_cursor_pointer(true); + tui.current().rdr.request_mouse_cursor_pointer(true); } pub fn disable_jump_mode(self: *Self, _: command.Context) tp.result { self.jump_mode = false; - tui.renderer.request_mouse_cursor_text(true); + tui.current().rdr.request_mouse_cursor_text(true); } fn update_syntax(self: *Self) !void { @@ -3684,9 +3686,9 @@ pub const EditorWidget = struct { self.editor.add_match(m) catch {}; } else if (try m.match(.{ "H", tp.extract(&self.hover) })) { if (self.editor.jump_mode) - tui.renderer.request_mouse_cursor_pointer(self.hover) + tui.current().rdr.request_mouse_cursor_pointer(self.hover) else - tui.renderer.request_mouse_cursor_text(self.hover); + tui.current().rdr.request_mouse_cursor_text(self.hover); } else if (try m.match(.{ "show_whitespace", tp.extract(&self.editor.show_whitespace) })) { _ = ""; } else { diff --git a/src/tui/home.zig b/src/tui/home.zig index 8593d06..dfc5b3e 100644 --- a/src/tui/home.zig +++ b/src/tui/home.zig @@ -67,7 +67,7 @@ pub fn walk(self: *Self, walk_ctx: *anyopaque, f: Widget.WalkFn, w: *Widget) boo pub fn receive(_: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool { var hover: bool = false; if (try m.match(.{ "H", tp.extract(&hover) })) { - tui.renderer.request_mouse_cursor_default(hover); + tui.current().rdr.request_mouse_cursor_default(hover); tui.need_render(); return true; } diff --git a/src/tui/status/filestate.zig b/src/tui/status/filestate.zig index 1c3d123..f5d0569 100644 --- a/src/tui/status/filestate.zig +++ b/src/tui/status/filestate.zig @@ -155,7 +155,7 @@ fn render_terminal_title(self: *Self) void { if (std.mem.eql(u8, self.title, new_title)) return; @memcpy(self.title_buf[0..new_title.len], new_title); self.title = self.title_buf[0..new_title.len]; - tui.renderer.set_terminal_title(self.title); + tui.current().rdr.set_terminal_title(self.title); } pub fn receive(self: *Self, _: *Button.State(Self), _: tp.pid_ref, m: tp.message) error{Exit}!bool { diff --git a/src/tui/status/keystate.zig b/src/tui/status/keystate.zig index 1f209e2..bbd74cd 100644 --- a/src/tui/status/keystate.zig +++ b/src/tui/status/keystate.zig @@ -180,7 +180,7 @@ pub fn receive(self: *Self, _: tp.pid_ref, m: tp.message) error{Exit}!bool { return true; } if (try m.match(.{ "H", tp.extract(&self.hover) })) { - tui.renderer.request_mouse_cursor_pointer(self.hover); + tui.current().rdr.request_mouse_cursor_pointer(self.hover); return true; } diff --git a/src/tui/tui.zig b/src/tui/tui.zig index b245220..9be2c30 100644 --- a/src/tui/tui.zig +++ b/src/tui/tui.zig @@ -287,6 +287,12 @@ fn receive_safe(self: *Self, from: tp.pid_ref, m: tp.message) tp.result { return; } + if (try m.match(.{"focus_in"})) + return; + + if (try m.match(.{"focus_out"})) + return; + if (try self.send_widgets(from, m)) return;