diff --git a/src/lib/main.zig b/src/lib/main.zig index cdddade09..5a626b1e8 100644 --- a/src/lib/main.zig +++ b/src/lib/main.zig @@ -1,9 +1,11 @@ const std = @import("std"); const enumpkg = @import("enum.zig"); +const types = @import("types.zig"); const unionpkg = @import("union.zig"); pub const allocator = @import("allocator.zig"); pub const Enum = enumpkg.Enum; +pub const String = types.String; pub const Struct = @import("struct.zig").Struct; pub const Target = @import("target.zig").Target; pub const TaggedUnion = unionpkg.TaggedUnion; diff --git a/src/lib/types.zig b/src/lib/types.zig new file mode 100644 index 000000000..758540d12 --- /dev/null +++ b/src/lib/types.zig @@ -0,0 +1,13 @@ +pub const String = extern struct { + ptr: [*]const u8, + len: usize, + + pub fn init(zig: anytype) String { + return switch (@TypeOf(zig)) { + []u8, []const u8 => .{ + .ptr = zig.ptr, + .len = zig.len, + }, + }; + } +}; diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index 7442fb21c..025e995c1 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -113,6 +113,16 @@ pub const Action = union(Key) { end_hyperlink, active_status_display: ansi.StatusDisplay, decaln, + window_title: WindowTitle, + report_pwd: ReportPwd, + show_desktop_notification: ShowDesktopNotification, + progress_report: osc.Command.ProgressReport, + start_hyperlink: StartHyperlink, + clipboard_contents: ClipboardContents, + prompt_start: PromptStart, + prompt_continuation: PromptContinuation, + end_of_command: EndOfCommand, + mouse_shape: MouseShape, pub const Key = lib.Enum( lib_target, @@ -200,6 +210,16 @@ pub const Action = union(Key) { "end_hyperlink", "active_status_display", "decaln", + "window_title", + "report_pwd", + "show_desktop_notification", + "progress_report", + "start_hyperlink", + "clipboard_contents", + "prompt_start", + "prompt_continuation", + "end_of_command", + "mouse_shape", }, ); @@ -288,6 +308,118 @@ pub const Action = union(Key) { return @intCast(self.flags.int()); } }; + + pub const WindowTitle = struct { + title: []const u8, + + pub const C = lib.String; + + pub fn cval(self: WindowTitle) WindowTitle.C { + return .init(self.title); + } + }; + + pub const ReportPwd = struct { + url: []const u8, + + pub const C = lib.String; + + pub fn cval(self: ReportPwd) ReportPwd.C { + return .init(self.url); + } + }; + + pub const ShowDesktopNotification = struct { + title: []const u8, + body: []const u8, + + pub const C = extern struct { + title: lib.String, + body: lib.String, + }; + + pub fn cval(self: ShowDesktopNotification) ShowDesktopNotification.C { + return .{ + .title = .init(self.title), + .body = .init(self.body), + }; + } + }; + + pub const StartHyperlink = struct { + uri: []const u8, + id: ?[]const u8, + + pub const C = extern struct { + uri: lib.String, + id: lib.String, + }; + + pub fn cval(self: StartHyperlink) StartHyperlink.C { + return .{ + .uri = .init(self.uri), + .id = .init(self.id orelse ""), + }; + } + }; + + pub const ClipboardContents = struct { + kind: u8, + data: []const u8, + + pub const C = extern struct { + kind: u8, + data: lib.String, + }; + + pub fn cval(self: ClipboardContents) ClipboardContents.C { + return .{ + .kind = self.kind, + .data = .init(self.data), + }; + } + }; + + pub const PromptStart = struct { + aid: ?[]const u8, + redraw: bool, + + pub const C = extern struct { + aid: lib.String, + redraw: bool, + }; + + pub fn cval(self: PromptStart) PromptStart.C { + return .{ + .aid = .init(self.aid orelse ""), + .redraw = self.redraw, + }; + } + }; + + pub const PromptContinuation = struct { + aid: ?[]const u8, + + pub const C = lib.String; + + pub fn cval(self: PromptContinuation) PromptContinuation.C { + return .init(self.aid orelse ""); + } + }; + + pub const EndOfCommand = struct { + exit_code: ?u8, + + pub const C = extern struct { + exit_code: i16, + }; + + pub fn cval(self: EndOfCommand) EndOfCommand.C { + return .{ + .exit_code = if (self.exit_code) |code| @intCast(code) else -1, + }; + } + }; }; /// Returns a type that can process a stream of tty control characters. @@ -1710,15 +1842,12 @@ pub fn Stream(comptime Handler: type) type { inline fn oscDispatch(self: *Self, cmd: osc.Command) !void { switch (cmd) { .change_window_title => |title| { - if (@hasDecl(T, "changeWindowTitle")) { - if (!std.unicode.utf8ValidateSlice(title)) { - log.warn("change title request: invalid utf-8, ignoring request", .{}); - return; - } - - try self.handler.changeWindowTitle(title); + if (!std.unicode.utf8ValidateSlice(title)) { + log.warn("change title request: invalid utf-8, ignoring request", .{}); return; - } else log.warn("unimplemented OSC callback: {}", .{cmd}); + } + + try self.handler.vt(.window_title, .{ .title = title }); }, .change_window_icon => |icon| { @@ -1726,54 +1855,43 @@ pub fn Stream(comptime Handler: type) type { }, .clipboard_contents => |clip| { - if (@hasDecl(T, "clipboardContents")) { - try self.handler.clipboardContents(clip.kind, clip.data); - return; - } else log.warn("unimplemented OSC callback: {}", .{cmd}); + try self.handler.vt(.clipboard_contents, .{ + .kind = clip.kind, + .data = clip.data, + }); }, .prompt_start => |v| { - if (@hasDecl(T, "promptStart")) { - switch (v.kind) { - .primary, .right => try self.handler.promptStart(v.aid, v.redraw), - .continuation, .secondary => try self.handler.promptContinuation(v.aid), - } - return; - } else log.warn("unimplemented OSC callback: {}", .{cmd}); + switch (v.kind) { + .primary, .right => try self.handler.vt(.prompt_start, .{ + .aid = v.aid, + .redraw = v.redraw, + }), + .continuation, .secondary => try self.handler.vt(.prompt_continuation, .{ + .aid = v.aid, + }), + } }, - .prompt_end => { - try self.handler.vt(.prompt_end, {}); - }, + .prompt_end => try self.handler.vt(.prompt_end, {}), - .end_of_input => { - try self.handler.vt(.end_of_input, {}); - }, + .end_of_input => try self.handler.vt(.end_of_input, {}), .end_of_command => |end| { - if (@hasDecl(T, "endOfCommand")) { - try self.handler.endOfCommand(end.exit_code); - return; - } else log.warn("unimplemented OSC callback: {}", .{cmd}); + try self.handler.vt(.end_of_command, .{ .exit_code = end.exit_code }); }, .report_pwd => |v| { - if (@hasDecl(T, "reportPwd")) { - try self.handler.reportPwd(v.value); - return; - } else log.warn("unimplemented OSC callback: {}", .{cmd}); + try self.handler.vt(.report_pwd, .{ .url = v.value }); }, .mouse_shape => |v| { - if (@hasDecl(T, "setMouseShape")) { - const shape = MouseShape.fromString(v.value) orelse { - log.warn("unknown cursor shape: {s}", .{v.value}); - return; - }; - - try self.handler.setMouseShape(shape); + const shape = MouseShape.fromString(v.value) orelse { + log.warn("unknown cursor shape: {s}", .{v.value}); return; - } else log.warn("unimplemented OSC callback: {}", .{cmd}); + }; + + try self.handler.vt(.mouse_shape, shape); }, .color_operation => |v| { @@ -1795,17 +1913,17 @@ pub fn Stream(comptime Handler: type) type { }, .show_desktop_notification => |v| { - if (@hasDecl(T, "showDesktopNotification")) { - try self.handler.showDesktopNotification(v.title, v.body); - return; - } else log.warn("unimplemented OSC callback: {}", .{cmd}); + try self.handler.vt(.show_desktop_notification, .{ + .title = v.title, + .body = v.body, + }); }, .hyperlink_start => |v| { - if (@hasDecl(T, "startHyperlink")) { - try self.handler.startHyperlink(v.uri, v.id); - return; - } else log.warn("unimplemented OSC callback: {}", .{cmd}); + try self.handler.vt(.start_hyperlink, .{ + .uri = v.uri, + .id = v.id, + }); }, .hyperlink_end => { @@ -1813,10 +1931,7 @@ pub fn Stream(comptime Handler: type) type { }, .conemu_progress_report => |v| { - if (@hasDecl(T, "handleProgressReport")) { - try self.handler.handleProgressReport(v); - return; - } else log.warn("unimplemented OSC callback: {}", .{cmd}); + try self.handler.vt(.progress_report, v); }, .conemu_sleep, @@ -2643,20 +2758,16 @@ test "stream: change window title with invalid utf-8" { const H = struct { seen: bool = false, - pub fn changeWindowTitle(self: *@This(), title: []const u8) !void { - _ = title; - - self.seen = true; - } - pub fn vt( self: *@This(), comptime action: anytype, value: anytype, ) !void { - _ = self; - _ = action; _ = value; + switch (action) { + .window_title => self.seen = true, + else => {}, + } } }; diff --git a/src/termio/stream_handler.zig b/src/termio/stream_handler.zig index 32696c096..d23e7606e 100644 --- a/src/termio/stream_handler.zig +++ b/src/termio/stream_handler.zig @@ -306,6 +306,16 @@ pub const StreamHandler = struct { .end_hyperlink => try self.endHyperlink(), .active_status_display => self.terminal.status_display = value, .decaln => try self.decaln(), + .window_title => try self.windowTitle(value.title), + .report_pwd => try self.reportPwd(value.url), + .show_desktop_notification => try self.showDesktopNotification(value.title, value.body), + .progress_report => self.progressReport(value), + .start_hyperlink => try self.startHyperlink(value.uri, value.id), + .clipboard_contents => try self.clipboardContents(value.kind, value.data), + .prompt_start => self.promptStart(value.aid, value.redraw), + .prompt_continuation => self.promptContinuation(value.aid), + .end_of_command => self.endOfCommand(value.exit_code), + .mouse_shape => try self.setMouseShape(value), .dcs_hook => try self.dcsHook(value), .dcs_put => try self.dcsPut(value), .dcs_unhook => try self.dcsUnhook(), @@ -714,7 +724,7 @@ pub const StreamHandler = struct { } } - pub inline fn startHyperlink(self: *StreamHandler, uri: []const u8, id: ?[]const u8) !void { + inline fn startHyperlink(self: *StreamHandler, uri: []const u8, id: ?[]const u8) !void { try self.terminal.screen.startHyperlink(uri, id); } @@ -902,7 +912,7 @@ pub const StreamHandler = struct { //------------------------------------------------------------------------- // OSC - pub fn changeWindowTitle(self: *StreamHandler, title: []const u8) !void { + fn windowTitle(self: *StreamHandler, title: []const u8) !void { var buf: [256]u8 = undefined; if (title.len >= buf.len) { log.warn("change title requested larger than our buffer size, ignoring", .{}); @@ -933,7 +943,7 @@ pub const StreamHandler = struct { self.surfaceMessageWriter(.{ .set_title = buf }); } - pub inline fn setMouseShape( + inline fn setMouseShape( self: *StreamHandler, shape: terminal.MouseShape, ) !void { @@ -945,7 +955,7 @@ pub const StreamHandler = struct { self.surfaceMessageWriter(.{ .set_mouse_shape = shape }); } - pub fn clipboardContents(self: *StreamHandler, kind: u8, data: []const u8) !void { + fn clipboardContents(self: *StreamHandler, kind: u8, data: []const u8) !void { // Note: we ignore the "kind" field and always use the standard clipboard. // iTerm also appears to do this but other terminals seem to only allow // certain. Let's investigate more. @@ -975,13 +985,13 @@ pub const StreamHandler = struct { }); } - pub inline fn promptStart(self: *StreamHandler, aid: ?[]const u8, redraw: bool) !void { + inline fn promptStart(self: *StreamHandler, aid: ?[]const u8, redraw: bool) void { _ = aid; self.terminal.markSemanticPrompt(.prompt); self.terminal.flags.shell_redraws_prompt = redraw; } - pub inline fn promptContinuation(self: *StreamHandler, aid: ?[]const u8) !void { + inline fn promptContinuation(self: *StreamHandler, aid: ?[]const u8) void { _ = aid; self.terminal.markSemanticPrompt(.prompt_continuation); } @@ -995,11 +1005,11 @@ pub const StreamHandler = struct { self.surfaceMessageWriter(.start_command); } - pub inline fn endOfCommand(self: *StreamHandler, exit_code: ?u8) !void { + inline fn endOfCommand(self: *StreamHandler, exit_code: ?u8) void { self.surfaceMessageWriter(.{ .stop_command = exit_code }); } - pub fn reportPwd(self: *StreamHandler, url: []const u8) !void { + fn reportPwd(self: *StreamHandler, url: []const u8) !void { // Special handling for the empty URL. We treat the empty URL // as resetting the pwd as if we never saw a pwd. I can't find any // other terminal that does this but it seems like a reasonable @@ -1013,7 +1023,7 @@ pub const StreamHandler = struct { // If we haven't seen a title, we're using the pwd as our title. // Set it to blank which will reset our title behavior. if (!self.seen_title) { - try self.changeWindowTitle(""); + try self.windowTitle(""); assert(!self.seen_title); } @@ -1093,7 +1103,7 @@ pub const StreamHandler = struct { // If we haven't seen a title, use our pwd as the title. if (!self.seen_title) { - try self.changeWindowTitle(path); + try self.windowTitle(path); self.seen_title = false; } } @@ -1347,7 +1357,7 @@ pub const StreamHandler = struct { } } - pub fn showDesktopNotification( + fn showDesktopNotification( self: *StreamHandler, title: []const u8, body: []const u8, @@ -1500,7 +1510,7 @@ pub const StreamHandler = struct { } /// Display a GUI progress report. - pub fn handleProgressReport(self: *StreamHandler, report: terminal.osc.Command.ProgressReport) error{}!void { + fn progressReport(self: *StreamHandler, report: terminal.osc.Command.ProgressReport) void { self.surfaceMessageWriter(.{ .progress_report = report }); } };