mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-05-24 05:40:15 +00:00
core: implement backarrow key mode (DECBKM) - mode 67
This mode allows programs to modify the code that the `backspace` key (backarrow key in DEC parlance) sends. If this mode is `off`/`false`/`reset` (the default, the same as before this PR), we send the byte `0x7f`. If this mode is `on`/`true`/`set` we send the byte `0x08`.
This commit is contained in:
@@ -70,6 +70,7 @@ extern "C" {
|
||||
#define GHOSTTY_MODE_REVERSE_WRAP (ghostty_mode_new(45, false)) /**< Reverse wrap */
|
||||
#define GHOSTTY_MODE_ALT_SCREEN_LEGACY (ghostty_mode_new(47, false)) /**< Alternate screen (legacy) */
|
||||
#define GHOSTTY_MODE_KEYPAD_KEYS (ghostty_mode_new(66, false)) /**< Application keypad */
|
||||
#define GHOSTTY_MODE_BACKARROW_KEY_MODE (ghostty_mode_new(67, false)) /**< Backarrow key mode (DECBKM) */
|
||||
#define GHOSTTY_MODE_LEFT_RIGHT_MARGIN (ghostty_mode_new(69, false)) /**< Left/right margin mode */
|
||||
#define GHOSTTY_MODE_NORMAL_MOUSE (ghostty_mode_new(1000, false)) /**< Normal mouse tracking */
|
||||
#define GHOSTTY_MODE_BUTTON_MOUSE (ghostty_mode_new(1002, false)) /**< Button-event mouse tracking */
|
||||
|
||||
@@ -18,6 +18,12 @@ pub const Options = struct {
|
||||
/// Terminal DEC mode 66
|
||||
keypad_key_application: bool = false,
|
||||
|
||||
// DEC Backarrow Key Mode (DECBKM)
|
||||
// See https://vt100.net/dec/ek-vt3xx-tp-002.pdf page 170
|
||||
// If `false` (the default), `backspace` emits 0x7f
|
||||
// If `true`, `backspace` emits 0x08
|
||||
backarrow_key_mode: bool = false,
|
||||
|
||||
/// Terminal DEC mode 1035
|
||||
ignore_keypad_with_numlock: bool = false,
|
||||
|
||||
@@ -55,6 +61,7 @@ pub const Options = struct {
|
||||
.alt_esc_prefix = t.modes.get(.alt_esc_prefix),
|
||||
.cursor_key_application = t.modes.get(.cursor_keys),
|
||||
.keypad_key_application = t.modes.get(.keypad_keys),
|
||||
.backarrow_key_mode = t.modes.get(.backarrow_key_mode),
|
||||
.ignore_keypad_with_numlock = t.modes.get(.ignore_keypad_with_numlock),
|
||||
.modify_other_keys_state_2 = t.flags.modify_other_keys_2,
|
||||
.kitty_flags = t.screens.active.kitty_keyboard.current(),
|
||||
@@ -182,7 +189,9 @@ fn kitty(
|
||||
switch (event.key) {
|
||||
.enter => return try writer.writeByte('\r'),
|
||||
.tab => return try writer.writeByte('\t'),
|
||||
.backspace => return try writer.writeByte(0x7F),
|
||||
.backspace => return try writer.writeByte(
|
||||
if (opts.backarrow_key_mode) 0x08 else 0x7F,
|
||||
),
|
||||
else => {},
|
||||
}
|
||||
}
|
||||
@@ -338,6 +347,7 @@ fn legacy(
|
||||
opts.keypad_key_application,
|
||||
opts.ignore_keypad_with_numlock,
|
||||
opts.modify_other_keys_state_2,
|
||||
opts.backarrow_key_mode,
|
||||
)) |sequence| pc_style: {
|
||||
// If we have UTF-8 text, then we never emit PC style function
|
||||
// keys. Many function keys (escape, enter, backspace) have
|
||||
@@ -601,6 +611,7 @@ fn pcStyleFunctionKey(
|
||||
keypad_key_application_req: bool,
|
||||
ignore_keypad_with_numlock: bool,
|
||||
modify_other_keys: bool, // True if state 2
|
||||
backarrow_key_mode: bool,
|
||||
) ?[]const u8 {
|
||||
// We only want binding-sensitive mods because lock keys
|
||||
// and directional modifiers (left/right) don't matter for
|
||||
@@ -653,6 +664,8 @@ fn pcStyleFunctionKey(
|
||||
continue;
|
||||
}
|
||||
|
||||
if (keyval == .backspace and backarrow_key_mode) return "\x08";
|
||||
|
||||
return entry.sequence;
|
||||
}
|
||||
|
||||
@@ -1245,12 +1258,23 @@ test "kitty: enter, backspace, tab" {
|
||||
try testing.expectEqualStrings("\r", writer.buffered());
|
||||
}
|
||||
{
|
||||
// DECBKM reset
|
||||
var writer: std.Io.Writer = .fixed(&buf);
|
||||
try kitty(&writer, .{ .key = .backspace, .mods = .{}, .utf8 = "" }, .{
|
||||
.kitty_flags = .{ .disambiguate = true },
|
||||
.backarrow_key_mode = false,
|
||||
});
|
||||
try testing.expectEqualStrings("\x7f", writer.buffered());
|
||||
}
|
||||
{
|
||||
// DECBKM set
|
||||
var writer: std.Io.Writer = .fixed(&buf);
|
||||
try kitty(&writer, .{ .key = .backspace, .mods = .{}, .utf8 = "" }, .{
|
||||
.kitty_flags = .{ .disambiguate = true },
|
||||
.backarrow_key_mode = true,
|
||||
});
|
||||
try testing.expectEqualStrings("\x08", writer.buffered());
|
||||
}
|
||||
{
|
||||
var writer: std.Io.Writer = .fixed(&buf);
|
||||
try kitty(&writer, .{ .key = .tab, .mods = .{}, .utf8 = "" }, .{
|
||||
@@ -1888,6 +1912,24 @@ test "legacy: backspace with utf8 (dead key state)" {
|
||||
try testing.expectEqualStrings("", writer.buffered());
|
||||
}
|
||||
|
||||
test "kitty: backspace (DECBKM set) (report_all: true)" {
|
||||
var buf: [128]u8 = undefined;
|
||||
var writer: std.Io.Writer = .fixed(&buf);
|
||||
try kitty(&writer, .{
|
||||
.key = .backspace,
|
||||
}, .{
|
||||
.kitty_flags = .{
|
||||
.disambiguate = true,
|
||||
.report_events = true,
|
||||
.report_alternates = true,
|
||||
.report_all = true,
|
||||
.report_associated = true,
|
||||
},
|
||||
.backarrow_key_mode = true,
|
||||
});
|
||||
try testing.expectEqualStrings("\x1b[127u", writer.buffered());
|
||||
}
|
||||
|
||||
test "legacy: enter with utf8 (dead key state)" {
|
||||
var buf: [128]u8 = undefined;
|
||||
var writer: std.Io.Writer = .fixed(&buf);
|
||||
@@ -2039,6 +2081,26 @@ test "legacy: ctrl+shift+backspace" {
|
||||
try testing.expectEqualStrings("\x08", writer.buffered());
|
||||
}
|
||||
|
||||
test "legacy: backspace (DECBKM reset)" {
|
||||
var buf: [128]u8 = undefined;
|
||||
var writer: std.Io.Writer = .fixed(&buf);
|
||||
try legacy(&writer, .{
|
||||
.key = .backspace,
|
||||
.mods = .{},
|
||||
}, .{ .backarrow_key_mode = false });
|
||||
try testing.expectEqualStrings("\x7f", writer.buffered());
|
||||
}
|
||||
|
||||
test "legacy: backspace (DECBKM set)" {
|
||||
var buf: [128]u8 = undefined;
|
||||
var writer: std.Io.Writer = .fixed(&buf);
|
||||
try legacy(&writer, .{
|
||||
.key = .backspace,
|
||||
.mods = .{},
|
||||
}, .{ .backarrow_key_mode = true });
|
||||
try testing.expectEqualStrings("\x08", writer.buffered());
|
||||
}
|
||||
|
||||
test "legacy: ctrl+shift+char with modify other state 2" {
|
||||
var buf: [128]u8 = undefined;
|
||||
var writer: std.Io.Writer = .fixed(&buf);
|
||||
@@ -2355,17 +2417,28 @@ test "legacy: super and other mods on macOS with text" {
|
||||
try testing.expectEqualStrings("", writer.buffered());
|
||||
}
|
||||
|
||||
test "legacy: backspace with DEL utf8" {
|
||||
test "legacy: backspace with DEL utf8 (DECBKM reset)" {
|
||||
var buf: [128]u8 = undefined;
|
||||
var writer: std.Io.Writer = .fixed(&buf);
|
||||
try legacy(&writer, .{
|
||||
.key = .backspace,
|
||||
.utf8 = &.{0x7F},
|
||||
.unshifted_codepoint = 0x08,
|
||||
}, .{});
|
||||
}, .{ .backarrow_key_mode = false });
|
||||
try testing.expectEqualStrings("\x7F", writer.buffered());
|
||||
}
|
||||
|
||||
test "legacy: backspace with DEL utf8 (DECBKM set)" {
|
||||
var buf: [128]u8 = undefined;
|
||||
var writer: std.Io.Writer = .fixed(&buf);
|
||||
try legacy(&writer, .{
|
||||
.key = .backspace,
|
||||
.utf8 = &.{0x7F},
|
||||
.unshifted_codepoint = 0x08,
|
||||
}, .{ .backarrow_key_mode = true });
|
||||
try testing.expectEqualStrings("\x08", writer.buffered());
|
||||
}
|
||||
|
||||
test "ctrlseq: normal ctrl c" {
|
||||
const seq = ctrlSeq(.unidentified, "c", 'c', .{ .ctrl = true });
|
||||
try testing.expectEqual(@as(u8, 0x03), seq.?);
|
||||
|
||||
@@ -270,6 +270,11 @@ const entries: []const ModeEntry = &.{
|
||||
.{ .name = "reverse_wrap", .value = 45 },
|
||||
.{ .name = "alt_screen_legacy", .value = 47 },
|
||||
.{ .name = "keypad_keys", .value = 66 },
|
||||
// DEC Backarrow Key Mode (DECBKM)
|
||||
// See https://vt100.net/dec/ek-vt3xx-tp-002.pdf page 170
|
||||
// If `false` (the default), `backspace` emits 0x7f
|
||||
// If `true`, `backspace` emits 0x08
|
||||
.{ .name = "backarrow_key_mode", .value = 67 },
|
||||
.{ .name = "enable_left_and_right_margin", .value = 69 },
|
||||
.{ .name = "mouse_event_normal", .value = 1000 },
|
||||
.{ .name = "mouse_event_button", .value = 1002 },
|
||||
|
||||
Reference in New Issue
Block a user