From 66aa1d9be3887ecf80ec50bdddc54553ce36a9ff Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Sat, 12 Aug 2023 22:27:46 -0700 Subject: [PATCH] terminal: parse and handle set modify key format (ESC[>{a};{b}m) --- src/Surface.zig | 7 ++-- src/terminal/Terminal.zig | 4 ++ src/terminal/ansi.zig | 9 +++++ src/terminal/main.zig | 1 + src/terminal/stream.zig | 78 ++++++++++++++++++++++++++++++++------- src/termio/Exec.zig | 11 ++++++ 6 files changed, 92 insertions(+), 18 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index dca59c154..0d08901d7 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -1115,6 +1115,7 @@ pub fn keyCallback( self.renderer_state.mutex.lock(); const cursor_key_application = self.io.terminal.modes.cursor_keys; const keypad_key_application = self.io.terminal.modes.keypad_keys; + const modify_other_keys = self.io.terminal.modes.modify_other_keys; self.renderer_state.mutex.unlock(); // Check if we're processing a function key. @@ -1133,10 +1134,8 @@ pub fn keyCallback( switch (entry.modify_other_keys) { .any => {}, - - // TODO - .set => {}, - .set_other => continue, + .set => if (modify_other_keys) continue, + .set_other => if (!modify_other_keys) continue, } const mods_int: u8 = @bitCast(binding_mods); diff --git a/src/terminal/Terminal.zig b/src/terminal/Terminal.zig index 713bcb5dd..7972d696b 100644 --- a/src/terminal/Terminal.zig +++ b/src/terminal/Terminal.zig @@ -99,6 +99,10 @@ modes: packed struct { bracketed_paste: bool = false, // 2004 + // This is set via ESC[4;2m. Any other modify key mode just sets + // this to false. + modify_other_keys: bool = false, + // This isn't a mode, this is set by OSC 133 using the "A" event. // If this is true, it tells us that the shell supports redrawing // the prompt and that when we resize, if the cursor is at a prompt, diff --git a/src/terminal/ansi.zig b/src/terminal/ansi.zig index 34b3afd4d..2429210e0 100644 --- a/src/terminal/ansi.zig +++ b/src/terminal/ansi.zig @@ -192,3 +192,12 @@ pub const StatusDisplay = enum(u16) { // Non-exhaustive so that @intToEnum never fails for unsupported values. _, }; + +/// The possible modify key formats to ESC[>{a};{b}m +/// Note: this is not complete, we should add more as we support more +pub const ModifyKeyFormat = union(enum) { + legacy: void, + cursor_keys: void, + function_keys: void, + other_keys: enum { none, numeric_except, numeric }, +}; diff --git a/src/terminal/main.zig b/src/terminal/main.zig index fcb39f5d4..296089b68 100644 --- a/src/terminal/main.zig +++ b/src/terminal/main.zig @@ -21,6 +21,7 @@ pub const CursorStyle = ansi.CursorStyle; pub const DeviceAttributeReq = ansi.DeviceAttributeReq; pub const DeviceStatusReq = ansi.DeviceStatusReq; pub const Mode = ansi.Mode; +pub const ModifyKeyFormat = ansi.ModifyKeyFormat; pub const StatusLineType = ansi.StatusLineType; pub const StatusDisplay = ansi.StatusDisplay; pub const EraseDisplay = csi.EraseDisplay; diff --git a/src/terminal/stream.zig b/src/terminal/stream.zig index 5502e6c30..4fe6bed32 100644 --- a/src/terminal/stream.zig +++ b/src/terminal/stream.zig @@ -437,25 +437,75 @@ pub fn Stream(comptime Handler: type) type { } else log.warn("unimplemented CSI callback: {}", .{action}), // SGR - Select Graphic Rendition - 'm' => if (action.intermediates.len == 0) { - if (@hasDecl(T, "setAttribute")) { + 'm' => switch (action.intermediates.len) { + 0 => if (@hasDecl(T, "setAttribute")) { var p: sgr.Parser = .{ .params = action.params, .colon = action.sep == .colon }; while (p.next()) |attr| { // log.info("SGR attribute: {}", .{attr}); try self.handler.setAttribute(attr); } - } else log.warn("unimplemented CSI callback: {}", .{action}); - } else { - // Nothing, but I wanted a place to put this comment: - // there are others forms of CSI m that have intermediates. - // `vim --clean` uses `CSI ? 4 m` and I don't know what - // that means. And there is also `CSI > m` which is used - // to control modifier key reporting formats that we don't - // support yet. - log.debug( - "ignoring unimplemented CSI m with intermediates: {s}", - .{action.intermediates}, - ); + } else log.warn("unimplemented CSI callback: {}", .{action}), + + 1 => switch (action.intermediates[0]) { + '>' => if (@hasDecl(T, "setModifyKeyFormat")) blk: { + if (action.params.len == 0) { + // Reset + try self.handler.setModifyKeyFormat(.{ .legacy = {} }); + break :blk; + } + + var format: ansi.ModifyKeyFormat = switch (action.params[0]) { + 0 => .{ .legacy = {} }, + 1 => .{ .cursor_keys = {} }, + 2 => .{ .function_keys = {} }, + 4 => .{ .other_keys = .none }, + else => { + log.warn("invalid setModifyKeyFormat: {}", .{action}); + break :blk; + }, + }; + + if (action.params.len > 2) { + log.warn("invalid setModifyKeyFormat: {}", .{action}); + break :blk; + } + + if (action.params.len == 2) { + switch (format) { + // We don't support any of the subparams yet for these. + .legacy => {}, + .cursor_keys => {}, + .function_keys => {}, + + // We only support the numeric form. + .other_keys => |*v| switch (action.params[1]) { + 2 => v.* = .numeric, + else => v.* = .none, + }, + } + } + + try self.handler.setModifyKeyFormat(format); + } else log.warn("unimplemented setModifyKeyFormat: {}", .{action}), + + else => log.warn( + "unknown CSI m with intermediate: {}", + .{action.intermediates[0]}, + ), + }, + + else => { + // Nothing, but I wanted a place to put this comment: + // there are others forms of CSI m that have intermediates. + // `vim --clean` uses `CSI ? 4 m` and I don't know what + // that means. And there is also `CSI > m` which is used + // to control modifier key reporting formats that we don't + // support yet. + log.warn( + "ignoring unimplemented CSI m with intermediates: {s}", + .{action.intermediates}, + ); + }, }, // CPR - Request Cursor Position Report diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 599f7a8f4..8f7d05568 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -1183,6 +1183,17 @@ const StreamHandler = struct { self.terminal.setScrollingRegion(top, bot); } + pub fn setModifyKeyFormat(self: *StreamHandler, format: terminal.ModifyKeyFormat) !void { + self.terminal.modes.modify_other_keys = false; + switch (format) { + .other_keys => |v| switch (v) { + .numeric => self.terminal.modes.modify_other_keys = true, + else => {}, + }, + else => {}, + } + } + pub fn setMode(self: *StreamHandler, mode: terminal.Mode, enabled: bool) !void { // Note: this function doesn't need to grab the render state or // terminal locks because it is only called from process() which