terminal: parse and handle set modify key format (ESC[>{a};{b}m)

This commit is contained in:
Mitchell Hashimoto
2023-08-12 22:27:46 -07:00
parent d94474463b
commit 66aa1d9be3
6 changed files with 92 additions and 18 deletions

View File

@ -1115,6 +1115,7 @@ pub fn keyCallback(
self.renderer_state.mutex.lock(); self.renderer_state.mutex.lock();
const cursor_key_application = self.io.terminal.modes.cursor_keys; const cursor_key_application = self.io.terminal.modes.cursor_keys;
const keypad_key_application = self.io.terminal.modes.keypad_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(); self.renderer_state.mutex.unlock();
// Check if we're processing a function key. // Check if we're processing a function key.
@ -1133,10 +1134,8 @@ pub fn keyCallback(
switch (entry.modify_other_keys) { switch (entry.modify_other_keys) {
.any => {}, .any => {},
.set => if (modify_other_keys) continue,
// TODO .set_other => if (!modify_other_keys) continue,
.set => {},
.set_other => continue,
} }
const mods_int: u8 = @bitCast(binding_mods); const mods_int: u8 = @bitCast(binding_mods);

View File

@ -99,6 +99,10 @@ modes: packed struct {
bracketed_paste: bool = false, // 2004 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. // 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 // 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, // the prompt and that when we resize, if the cursor is at a prompt,

View File

@ -192,3 +192,12 @@ pub const StatusDisplay = enum(u16) {
// Non-exhaustive so that @intToEnum never fails for unsupported values. // 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 },
};

View File

@ -21,6 +21,7 @@ pub const CursorStyle = ansi.CursorStyle;
pub const DeviceAttributeReq = ansi.DeviceAttributeReq; pub const DeviceAttributeReq = ansi.DeviceAttributeReq;
pub const DeviceStatusReq = ansi.DeviceStatusReq; pub const DeviceStatusReq = ansi.DeviceStatusReq;
pub const Mode = ansi.Mode; pub const Mode = ansi.Mode;
pub const ModifyKeyFormat = ansi.ModifyKeyFormat;
pub const StatusLineType = ansi.StatusLineType; pub const StatusLineType = ansi.StatusLineType;
pub const StatusDisplay = ansi.StatusDisplay; pub const StatusDisplay = ansi.StatusDisplay;
pub const EraseDisplay = csi.EraseDisplay; pub const EraseDisplay = csi.EraseDisplay;

View File

@ -437,25 +437,75 @@ pub fn Stream(comptime Handler: type) type {
} else log.warn("unimplemented CSI callback: {}", .{action}), } else log.warn("unimplemented CSI callback: {}", .{action}),
// SGR - Select Graphic Rendition // SGR - Select Graphic Rendition
'm' => if (action.intermediates.len == 0) { 'm' => switch (action.intermediates.len) {
if (@hasDecl(T, "setAttribute")) { 0 => if (@hasDecl(T, "setAttribute")) {
var p: sgr.Parser = .{ .params = action.params, .colon = action.sep == .colon }; var p: sgr.Parser = .{ .params = action.params, .colon = action.sep == .colon };
while (p.next()) |attr| { while (p.next()) |attr| {
// log.info("SGR attribute: {}", .{attr}); // log.info("SGR attribute: {}", .{attr});
try self.handler.setAttribute(attr); try self.handler.setAttribute(attr);
} }
} else log.warn("unimplemented CSI callback: {}", .{action}); } else log.warn("unimplemented CSI callback: {}", .{action}),
} else {
// Nothing, but I wanted a place to put this comment: 1 => switch (action.intermediates[0]) {
// there are others forms of CSI m that have intermediates. '>' => if (@hasDecl(T, "setModifyKeyFormat")) blk: {
// `vim --clean` uses `CSI ? 4 m` and I don't know what if (action.params.len == 0) {
// that means. And there is also `CSI > m` which is used // Reset
// to control modifier key reporting formats that we don't try self.handler.setModifyKeyFormat(.{ .legacy = {} });
// support yet. break :blk;
log.debug( }
"ignoring unimplemented CSI m with intermediates: {s}",
.{action.intermediates}, 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 // CPR - Request Cursor Position Report

View File

@ -1183,6 +1183,17 @@ const StreamHandler = struct {
self.terminal.setScrollingRegion(top, bot); 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 { pub fn setMode(self: *StreamHandler, mode: terminal.Mode, enabled: bool) !void {
// Note: this function doesn't need to grab the render state or // Note: this function doesn't need to grab the render state or
// terminal locks because it is only called from process() which // terminal locks because it is only called from process() which