mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +03:00
bind sequences for PC style function keys from xterm
Fixes #256 This makes a whole lot more sequences work, such as `ctrl+left`, `ctrl+shift+f1`, etc. We were just missing these completely. This also found an issue where if you split a sequence across two `write()` syscalls, then `/bin/sh` (I didn't test other shells) treats it as two literals rather than parsing as a single sequence. Great.
This commit is contained in:
@ -1906,12 +1906,15 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !void
|
|||||||
},
|
},
|
||||||
|
|
||||||
.csi => |data| {
|
.csi => |data| {
|
||||||
_ = self.io_thread.mailbox.push(.{
|
// We need to send the CSI sequence as a single write request.
|
||||||
.write_stable = "\x1B[",
|
// If you split it across two then the shell can interpret it
|
||||||
}, .{ .forever = {} });
|
// as two literals.
|
||||||
_ = self.io_thread.mailbox.push(.{
|
var buf: [128]u8 = undefined;
|
||||||
.write_stable = data,
|
const full_data = try std.fmt.bufPrint(&buf, "\x1b[{s}", .{data});
|
||||||
}, .{ .forever = {} });
|
_ = self.io_thread.mailbox.push(try termio.Message.writeReq(
|
||||||
|
self.alloc,
|
||||||
|
full_data,
|
||||||
|
), .{ .forever = {} });
|
||||||
try self.io_thread.wakeup.notify();
|
try self.io_thread.wakeup.notify();
|
||||||
|
|
||||||
// CSI triggers a scroll.
|
// CSI triggers a scroll.
|
||||||
|
175
src/config.zig
175
src/config.zig
@ -297,6 +297,9 @@ pub const Config = struct {
|
|||||||
errdefer result.deinit();
|
errdefer result.deinit();
|
||||||
const alloc = result._arena.?.allocator();
|
const alloc = result._arena.?.allocator();
|
||||||
|
|
||||||
|
// Add the PC style function keys first so that we can override any later.
|
||||||
|
try result.defaultPCStyleFunctionKeys(alloc);
|
||||||
|
|
||||||
// Add our default keybindings
|
// Add our default keybindings
|
||||||
try result.keybind.set.put(
|
try result.keybind.set.put(
|
||||||
alloc,
|
alloc,
|
||||||
@ -324,55 +327,6 @@ pub const Config = struct {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some control keys
|
|
||||||
try result.keybind.set.put(alloc, .{ .key = .up }, .{ .cursor_key = .{
|
|
||||||
.normal = "\x1b[A",
|
|
||||||
.application = "\x1bOA",
|
|
||||||
} });
|
|
||||||
try result.keybind.set.put(alloc, .{ .key = .down }, .{ .cursor_key = .{
|
|
||||||
.normal = "\x1b[B",
|
|
||||||
.application = "\x1bOB",
|
|
||||||
} });
|
|
||||||
try result.keybind.set.put(alloc, .{ .key = .right }, .{ .cursor_key = .{
|
|
||||||
.normal = "\x1b[C",
|
|
||||||
.application = "\x1bOC",
|
|
||||||
} });
|
|
||||||
try result.keybind.set.put(alloc, .{ .key = .left }, .{ .cursor_key = .{
|
|
||||||
.normal = "\x1b[D",
|
|
||||||
.application = "\x1bOD",
|
|
||||||
} });
|
|
||||||
try result.keybind.set.put(alloc, .{ .key = .home }, .{ .cursor_key = .{
|
|
||||||
.normal = "\x1b[H",
|
|
||||||
.application = "\x1bOH",
|
|
||||||
} });
|
|
||||||
try result.keybind.set.put(alloc, .{ .key = .end }, .{ .cursor_key = .{
|
|
||||||
.normal = "\x1b[F",
|
|
||||||
.application = "\x1bOF",
|
|
||||||
} });
|
|
||||||
|
|
||||||
try result.keybind.set.put(alloc, .{ .key = .page_up }, .{ .csi = "5~" });
|
|
||||||
try result.keybind.set.put(alloc, .{ .key = .page_down }, .{ .csi = "6~" });
|
|
||||||
|
|
||||||
// From xterm:
|
|
||||||
// Note that F1 through F4 are prefixed with SS3 , while the other keys are
|
|
||||||
// prefixed with CSI . Older versions of xterm implement different escape
|
|
||||||
// sequences for F1 through F4, with a CSI prefix. These can be activated
|
|
||||||
// by setting the oldXtermFKeys resource. However, since they do not
|
|
||||||
// correspond to any hardware terminal, they have been deprecated. (The
|
|
||||||
// DEC VT220 reserves F1 through F5 for local functions such as Setup).
|
|
||||||
try result.keybind.set.put(alloc, .{ .key = .f1 }, .{ .csi = "11~" });
|
|
||||||
try result.keybind.set.put(alloc, .{ .key = .f2 }, .{ .csi = "12~" });
|
|
||||||
try result.keybind.set.put(alloc, .{ .key = .f3 }, .{ .csi = "13~" });
|
|
||||||
try result.keybind.set.put(alloc, .{ .key = .f4 }, .{ .csi = "14~" });
|
|
||||||
try result.keybind.set.put(alloc, .{ .key = .f5 }, .{ .csi = "15~" });
|
|
||||||
try result.keybind.set.put(alloc, .{ .key = .f6 }, .{ .csi = "17~" });
|
|
||||||
try result.keybind.set.put(alloc, .{ .key = .f7 }, .{ .csi = "18~" });
|
|
||||||
try result.keybind.set.put(alloc, .{ .key = .f8 }, .{ .csi = "19~" });
|
|
||||||
try result.keybind.set.put(alloc, .{ .key = .f9 }, .{ .csi = "20~" });
|
|
||||||
try result.keybind.set.put(alloc, .{ .key = .f10 }, .{ .csi = "21~" });
|
|
||||||
try result.keybind.set.put(alloc, .{ .key = .f11 }, .{ .csi = "23~" });
|
|
||||||
try result.keybind.set.put(alloc, .{ .key = .f12 }, .{ .csi = "24~" });
|
|
||||||
|
|
||||||
// Fonts
|
// Fonts
|
||||||
try result.keybind.set.put(
|
try result.keybind.set.put(
|
||||||
alloc,
|
alloc,
|
||||||
@ -623,6 +577,129 @@ pub const Config = struct {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn defaultPCStyleFunctionKeys(result: *Config, alloc: Allocator) !void {
|
||||||
|
// Some control keys
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .up }, .{ .cursor_key = .{
|
||||||
|
.normal = "\x1b[A",
|
||||||
|
.application = "\x1bOA",
|
||||||
|
} });
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .down }, .{ .cursor_key = .{
|
||||||
|
.normal = "\x1b[B",
|
||||||
|
.application = "\x1bOB",
|
||||||
|
} });
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .right }, .{ .cursor_key = .{
|
||||||
|
.normal = "\x1b[C",
|
||||||
|
.application = "\x1bOC",
|
||||||
|
} });
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .left }, .{ .cursor_key = .{
|
||||||
|
.normal = "\x1b[D",
|
||||||
|
.application = "\x1bOD",
|
||||||
|
} });
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .home }, .{ .cursor_key = .{
|
||||||
|
.normal = "\x1b[H",
|
||||||
|
.application = "\x1bOH",
|
||||||
|
} });
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .end }, .{ .cursor_key = .{
|
||||||
|
.normal = "\x1b[F",
|
||||||
|
.application = "\x1bOF",
|
||||||
|
} });
|
||||||
|
|
||||||
|
try result.keybind.set.put(
|
||||||
|
alloc,
|
||||||
|
.{ .key = .tab, .mods = .{ .shift = true } },
|
||||||
|
.{ .csi = "Z" },
|
||||||
|
);
|
||||||
|
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .insert }, .{ .csi = "2~" });
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .delete }, .{ .csi = "3~" });
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .page_up }, .{ .csi = "5~" });
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .page_down }, .{ .csi = "6~" });
|
||||||
|
|
||||||
|
// From xterm:
|
||||||
|
// Note that F1 through F4 are prefixed with SS3 , while the other keys are
|
||||||
|
// prefixed with CSI . Older versions of xterm implement different escape
|
||||||
|
// sequences for F1 through F4, with a CSI prefix. These can be activated
|
||||||
|
// by setting the oldXtermFKeys resource. However, since they do not
|
||||||
|
// correspond to any hardware terminal, they have been deprecated. (The
|
||||||
|
// DEC VT220 reserves F1 through F5 for local functions such as Setup).
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .f1 }, .{ .csi = "11~" });
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .f2 }, .{ .csi = "12~" });
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .f3 }, .{ .csi = "13~" });
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .f4 }, .{ .csi = "14~" });
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .f5 }, .{ .csi = "15~" });
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .f6 }, .{ .csi = "17~" });
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .f7 }, .{ .csi = "18~" });
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .f8 }, .{ .csi = "19~" });
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .f9 }, .{ .csi = "20~" });
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .f10 }, .{ .csi = "21~" });
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .f11 }, .{ .csi = "23~" });
|
||||||
|
try result.keybind.set.put(alloc, .{ .key = .f12 }, .{ .csi = "24~" });
|
||||||
|
|
||||||
|
// From: https://invisible-island.net/xterm/ctlseqs/ctlseqs.html
|
||||||
|
//
|
||||||
|
// In normal mode, i.e., a Sun/PC keyboard when the sunKeyboard resource is
|
||||||
|
// false (and none of the other keyboard resources such as oldXtermFKeys
|
||||||
|
// resource is set), xterm encodes function key modifiers as parameters
|
||||||
|
// appended before the final character of the control sequence. As a
|
||||||
|
// special case, the SS3 sent before F1 through F4 is altered to CSI when
|
||||||
|
// sending a function key modifier as a parameter.
|
||||||
|
//
|
||||||
|
// Code Modifiers
|
||||||
|
// ---------+---------------------------
|
||||||
|
// 2 | Shift
|
||||||
|
// 3 | Alt
|
||||||
|
// 4 | Shift + Alt
|
||||||
|
// 5 | Control
|
||||||
|
// 6 | Shift + Control
|
||||||
|
// 7 | Alt + Control
|
||||||
|
// 8 | Shift + Alt + Control
|
||||||
|
// 9 | Meta
|
||||||
|
// 10 | Meta + Shift
|
||||||
|
// 11 | Meta + Alt
|
||||||
|
// 12 | Meta + Alt + Shift
|
||||||
|
// 13 | Meta + Ctrl
|
||||||
|
// 14 | Meta + Ctrl + Shift
|
||||||
|
// 15 | Meta + Ctrl + Alt
|
||||||
|
// 16 | Meta + Ctrl + Alt + Shift
|
||||||
|
// ---------+---------------------------
|
||||||
|
const modifiers: []const inputpkg.Mods = &.{
|
||||||
|
.{ .shift = true },
|
||||||
|
.{ .alt = true },
|
||||||
|
.{ .shift = true, .alt = true },
|
||||||
|
.{ .ctrl = true },
|
||||||
|
.{ .shift = true, .ctrl = true },
|
||||||
|
.{ .alt = true, .ctrl = true },
|
||||||
|
.{ .shift = true, .alt = true, .ctrl = true },
|
||||||
|
// todo: do we do meta or not?
|
||||||
|
};
|
||||||
|
inline for (modifiers, 2..) |mods, code| {
|
||||||
|
const m: []const u8 = &.{code + 48};
|
||||||
|
const set = &result.keybind.set;
|
||||||
|
try set.put(alloc, .{ .key = .end, .mods = mods }, .{ .csi = "1;" ++ m ++ "F" });
|
||||||
|
try set.put(alloc, .{ .key = .home, .mods = mods }, .{ .csi = "1;" ++ m ++ "H" });
|
||||||
|
try set.put(alloc, .{ .key = .insert, .mods = mods }, .{ .csi = "2;" ++ m ++ "~" });
|
||||||
|
try set.put(alloc, .{ .key = .delete, .mods = mods }, .{ .csi = "3;" ++ m ++ "~" });
|
||||||
|
try set.put(alloc, .{ .key = .page_up, .mods = mods }, .{ .csi = "5;" ++ m ++ "~" });
|
||||||
|
try set.put(alloc, .{ .key = .page_down, .mods = mods }, .{ .csi = "6;" ++ m ++ "~" });
|
||||||
|
try set.put(alloc, .{ .key = .up, .mods = mods }, .{ .csi = "1;" ++ m ++ "A" });
|
||||||
|
try set.put(alloc, .{ .key = .down, .mods = mods }, .{ .csi = "1;" ++ m ++ "B" });
|
||||||
|
try set.put(alloc, .{ .key = .right, .mods = mods }, .{ .csi = "1;" ++ m ++ "C" });
|
||||||
|
try set.put(alloc, .{ .key = .left, .mods = mods }, .{ .csi = "1;" ++ m ++ "D" });
|
||||||
|
try set.put(alloc, .{ .key = .f1, .mods = mods }, .{ .csi = "11;" ++ m ++ "~" });
|
||||||
|
try set.put(alloc, .{ .key = .f2, .mods = mods }, .{ .csi = "12;" ++ m ++ "~" });
|
||||||
|
try set.put(alloc, .{ .key = .f3, .mods = mods }, .{ .csi = "13;" ++ m ++ "~" });
|
||||||
|
try set.put(alloc, .{ .key = .f4, .mods = mods }, .{ .csi = "14;" ++ m ++ "~" });
|
||||||
|
try set.put(alloc, .{ .key = .f5, .mods = mods }, .{ .csi = "15;" ++ m ++ "~" });
|
||||||
|
try set.put(alloc, .{ .key = .f6, .mods = mods }, .{ .csi = "17;" ++ m ++ "~" });
|
||||||
|
try set.put(alloc, .{ .key = .f7, .mods = mods }, .{ .csi = "18;" ++ m ++ "~" });
|
||||||
|
try set.put(alloc, .{ .key = .f8, .mods = mods }, .{ .csi = "19;" ++ m ++ "~" });
|
||||||
|
try set.put(alloc, .{ .key = .f9, .mods = mods }, .{ .csi = "20;" ++ m ++ "~" });
|
||||||
|
try set.put(alloc, .{ .key = .f10, .mods = mods }, .{ .csi = "21;" ++ m ++ "~" });
|
||||||
|
try set.put(alloc, .{ .key = .f11, .mods = mods }, .{ .csi = "23;" ++ m ++ "~" });
|
||||||
|
try set.put(alloc, .{ .key = .f12, .mods = mods }, .{ .csi = "24;" ++ m ++ "~" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// This sets either "ctrl" or "super" to true (but not both)
|
/// This sets either "ctrl" or "super" to true (but not both)
|
||||||
/// on mods depending on if the build target is Mac or not. On
|
/// on mods depending on if the build target is Mac or not. On
|
||||||
/// Mac, we default to super (i.e. super+c for copy) and on
|
/// Mac, we default to super (i.e. super+c for copy) and on
|
||||||
|
Reference in New Issue
Block a user