Files
ghostty-rs/crates/ghostty-input/src/function_keys.rs
T
ddidderr e03432241b feat(input): port keyboard, keybinding and mouse input handling
Ports ZIG/src/input (except macOS keymaps) into ghostty-input:
- key, key_mods (incl. RemapSet), the physical keycode table
- key_encode: legacy, modifyOtherKeys and kitty keyboard protocol
  encoders, function key tables
- Binding: all keybinding actions with their user-visible docs (for
  +list-actions), triggers, sets with sequences, chains, leader keys and
  key tables, and formatting back to config strings
- command palette defaults, mouse, paste safety checks, kitty helpers,
  links, helpgen action docs

Deviations (docs/rewrite/notes/ghostty-input.md): Mods.super is named
super_; case folding uses char::to_lowercase plus Ghostty's special
cases instead of uucode tables; chain parents are trigger paths instead
of pointers; a Zig bug where chained leaves inside sequences corrupted the
shared prefix when formatting is fixed (covered by a test).

Integration left: building encode/paste Options from terminal modes,
and implementing the EntryFormatter trait in ghostty-config.

Test Plan:
- cargo test -p ghostty-input (220 tests; 8 macOS-only skipped)
- cargo clippy -p ghostty-input --all-targets -- -D warnings
2026-10-11 21:56:07 +02:00

617 lines
19 KiB
Rust

//! This is the list of "PC style function keys" that xterm supports for
//! the legacy keyboard protocols. These always take priority since at the
//! time of writing this, even the most modern keyboard protocols still
//! are backwards compatible with regards to these sequences.
//!
//! This is based on a variety of sources cross-referenced but mostly
//! based on foot's keymap.h: https://codeberg.org/dnkl/foot/src/branch/master/keymap.h
//! (ZIG/src/input/function_keys.zig)
use std::sync::LazyLock;
use crate::key::{Key, Mods};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CursorMode {
Any,
Normal,
Application,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KeypadMode {
Any,
Normal,
Application,
}
/// A bit confusing so I'll document this one: this is the "modify other keys"
/// setting. We only change behavior for "set_other" which is ESC [ > 4; 2 m.
/// So this can be "any" which means we don't care what's going on. Or it
/// can be "set" which means modify keys must be set EXCEPT FOR "other keys"
/// mode, and "set_other" which means modify keys must be set to "other keys"
/// mode.
///
/// See: https://invisible-island.net/xterm/modified-keys.html
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ModifyKeys {
Any,
Set,
SetOther,
}
/// A single entry in the table of keys.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Entry {
/// The exact set of modifiers that must be active for this entry to match.
/// If mods_empty_is_any is true then empty mods means any set of mods can
/// match. Otherwise, empty mods means no mods must be active.
pub mods: Mods,
pub mods_empty_is_any: bool,
/// The state required for cursor/keypad mode.
pub cursor: CursorMode,
pub keypad: KeypadMode,
/// Whether or not this entry should be used
pub modify_other_keys: ModifyKeys,
/// The sequence to send to the pty if this entry matches.
pub sequence: String,
}
impl Entry {
fn new(sequence: impl Into<String>) -> Entry {
Entry {
mods: Mods::default(),
mods_empty_is_any: true,
cursor: CursorMode::Any,
keypad: KeypadMode::Any,
modify_other_keys: ModifyKeys::Any,
sequence: sequence.into(),
}
}
}
const fn m(shift: bool, ctrl: bool, alt: bool, super_: bool) -> Mods {
Mods {
shift,
ctrl,
alt,
super_,
caps_lock: false,
num_lock: false,
sides: crate::key_mods::Side {
shift: crate::key_mods::ModSide::Left,
ctrl: crate::key_mods::ModSide::Left,
alt: crate::key_mods::ModSide::Left,
super_: crate::key_mods::ModSide::Left,
},
}
}
/// The list of modifier combinations for modify other key sequences.
/// The mode value is index + 2.
pub const MODIFIERS: &[Mods] = &[
m(true, false, false, false),
m(false, false, true, false),
m(true, false, true, false),
m(false, true, false, false),
m(true, true, false, false),
m(false, true, true, false),
m(true, true, true, false),
m(false, false, false, true),
m(true, false, false, true),
m(false, false, true, true),
m(true, false, true, true),
m(false, true, false, true),
m(true, true, false, true),
m(false, true, true, true),
m(true, true, true, true),
];
fn ent(mods: Mods, modify_other_keys: ModifyKeys, sequence: &str) -> Entry {
Entry {
mods,
modify_other_keys,
..Entry::new(sequence)
}
}
/// Returns the entries for a key. Not every key has any entries. It is
/// expected that you'll just use a for loop through the entry values, there
/// are never more than a couple dozen or so.
pub fn keys(k: Key) -> &'static [Entry] {
&KEYS[k as usize]
}
/// Iterate over the whole table (all keys, in `Key` order).
pub fn all_keys() -> impl Iterator<Item = &'static [Entry]> {
KEYS.iter().map(Vec::as_slice)
}
static KEYS: LazyLock<Vec<Vec<Entry>>> = LazyLock::new(build_keys);
fn cat(mut a: Vec<Entry>, b: Vec<Entry>) -> Vec<Entry> {
a.extend(b);
a
}
fn one(seq: &str) -> Vec<Entry> {
vec![Entry::new(seq)]
}
fn build_keys() -> Vec<Vec<Entry>> {
let mut result: Vec<Vec<Entry>> = vec![Vec::new(); Key::ALL.len()];
let mut set = |k: Key, v: Vec<Entry>| result[k as usize] = v;
set(
Key::ArrowUp,
cat(pc_style("\x1b[1;{}A"), cursor_key("\x1b[A", "\x1bOA")),
);
set(
Key::ArrowDown,
cat(pc_style("\x1b[1;{}B"), cursor_key("\x1b[B", "\x1bOB")),
);
set(
Key::ArrowRight,
cat(pc_style("\x1b[1;{}C"), cursor_key("\x1b[C", "\x1bOC")),
);
set(
Key::ArrowLeft,
cat(pc_style("\x1b[1;{}D"), cursor_key("\x1b[D", "\x1bOD")),
);
set(
Key::Home,
cat(pc_style("\x1b[1;{}H"), cursor_key("\x1b[H", "\x1bOH")),
);
set(
Key::End,
cat(pc_style("\x1b[1;{}F"), cursor_key("\x1b[F", "\x1bOF")),
);
set(Key::Insert, cat(pc_style("\x1b[2;{}~"), one("\x1B[2~")));
set(Key::Delete, cat(pc_style("\x1b[3;{}~"), one("\x1B[3~")));
set(Key::PageUp, cat(pc_style("\x1b[5;{}~"), one("\x1B[5~")));
set(Key::PageDown, cat(pc_style("\x1b[6;{}~"), one("\x1B[6~")));
// Function Keys. todo: f13-f35 but we need to add to input.Key
set(Key::F1, cat(pc_style("\x1b[1;{}P"), one("\x1BOP")));
set(Key::F2, cat(pc_style("\x1b[1;{}Q"), one("\x1BOQ")));
set(Key::F3, cat(pc_style("\x1b[13;{}~"), one("\x1BOR")));
set(Key::F4, cat(pc_style("\x1b[1;{}S"), one("\x1BOS")));
set(Key::F5, cat(pc_style("\x1b[15;{}~"), one("\x1B[15~")));
set(Key::F6, cat(pc_style("\x1b[17;{}~"), one("\x1B[17~")));
set(Key::F7, cat(pc_style("\x1b[18;{}~"), one("\x1B[18~")));
set(Key::F8, cat(pc_style("\x1b[19;{}~"), one("\x1B[19~")));
set(Key::F9, cat(pc_style("\x1b[20;{}~"), one("\x1B[20~")));
set(Key::F10, cat(pc_style("\x1b[21;{}~"), one("\x1B[21~")));
set(Key::F11, cat(pc_style("\x1b[23;{}~"), one("\x1B[23~")));
set(Key::F12, cat(pc_style("\x1b[24;{}~"), one("\x1B[24~")));
// Keypad keys
set(Key::Numpad0, kp_keys("p"));
set(Key::Numpad1, kp_keys("q"));
set(Key::Numpad2, kp_keys("r"));
set(Key::Numpad3, kp_keys("s"));
set(Key::Numpad4, kp_keys("t"));
set(Key::Numpad5, kp_keys("u"));
set(Key::Numpad6, kp_keys("v"));
set(Key::Numpad7, kp_keys("w"));
set(Key::Numpad8, kp_keys("x"));
set(Key::Numpad9, kp_keys("y"));
set(Key::NumpadDecimal, kp_keys("n"));
set(Key::NumpadDivide, kp_keys("o"));
set(Key::NumpadMultiply, kp_keys("j"));
set(Key::NumpadSubtract, kp_keys("m"));
set(Key::NumpadAdd, kp_keys("k"));
set(Key::NumpadEnter, cat(kp_keys("M"), one("\r")));
set(
Key::NumpadUp,
cat(pc_style("\x1b[1;{}A"), cursor_key("\x1b[A", "\x1bOA")),
);
set(
Key::NumpadDown,
cat(pc_style("\x1b[1;{}B"), cursor_key("\x1b[B", "\x1bOB")),
);
set(
Key::NumpadRight,
cat(pc_style("\x1b[1;{}C"), cursor_key("\x1b[C", "\x1bOC")),
);
set(
Key::NumpadLeft,
cat(pc_style("\x1b[1;{}D"), cursor_key("\x1b[D", "\x1bOD")),
);
set(
Key::NumpadBegin,
cat(pc_style("\x1b[1;{}E"), cursor_key("\x1b[E", "\x1bOE")),
);
set(
Key::NumpadHome,
cat(pc_style("\x1b[1;{}H"), cursor_key("\x1b[H", "\x1bOH")),
);
set(
Key::NumpadEnd,
cat(pc_style("\x1b[1;{}F"), cursor_key("\x1b[F", "\x1bOF")),
);
set(
Key::NumpadInsert,
cat(pc_style("\x1b[2;{}~"), one("\x1B[2~")),
);
set(
Key::NumpadDelete,
cat(pc_style("\x1b[3;{}~"), one("\x1B[3~")),
);
set(
Key::NumpadPageUp,
cat(pc_style("\x1b[5;{}~"), one("\x1B[5~")),
);
set(
Key::NumpadPageDown,
cat(pc_style("\x1b[6;{}~"), one("\x1B[6~")),
);
set(
Key::Backspace,
vec![
// Modify Keys Normal / Other
ent(m(true, false, false, false), ModifyKeys::Set, "\x7f"),
ent(m(false, false, true, false), ModifyKeys::Set, "\x1b\x7f"),
ent(m(true, false, true, false), ModifyKeys::Set, "\x1b\x7f"),
ent(m(true, true, false, false), ModifyKeys::Set, "\x08"),
ent(m(false, true, true, false), ModifyKeys::Set, "\x1b\x08"),
ent(m(false, false, false, true), ModifyKeys::Set, "\x7f"),
ent(m(true, false, false, true), ModifyKeys::Set, "\x7f"),
ent(m(false, false, true, true), ModifyKeys::Set, "\x1b\x7f"),
ent(m(true, false, true, true), ModifyKeys::Set, "\x1b\x7f"),
ent(m(false, true, false, true), ModifyKeys::Set, "\x08"),
ent(m(true, true, false, true), ModifyKeys::Set, "\x08"),
ent(m(false, true, true, true), ModifyKeys::Set, "\x1b\x08"),
ent(m(true, true, true, true), ModifyKeys::Set, "\x1b\x08"),
ent(
m(true, false, false, false),
ModifyKeys::SetOther,
"\x1b[27;2;127~",
),
ent(
m(false, false, true, false),
ModifyKeys::SetOther,
"\x1b[27;3;127~",
),
ent(
m(true, false, true, false),
ModifyKeys::SetOther,
"\x1b[27;4;127~",
),
ent(
m(true, true, false, false),
ModifyKeys::SetOther,
"\x1b[27;6;127~",
),
ent(
m(false, true, true, false),
ModifyKeys::SetOther,
"\x1b[27;7;127~",
),
ent(
m(true, true, true, false),
ModifyKeys::SetOther,
"\x1b[27;8;127~",
),
ent(
m(false, false, false, true),
ModifyKeys::SetOther,
"\x1b[27;9;127~",
),
ent(
m(true, false, false, true),
ModifyKeys::SetOther,
"\x1b[27;10;127~",
),
ent(
m(false, false, true, true),
ModifyKeys::SetOther,
"\x1b[27;11;127~",
),
ent(
m(true, false, true, true),
ModifyKeys::SetOther,
"\x1b[27;12;127~",
),
ent(
m(false, true, false, true),
ModifyKeys::SetOther,
"\x1b[27;13;127~",
),
ent(
m(true, true, false, true),
ModifyKeys::SetOther,
"\x1b[27;14;127~",
),
ent(
m(false, true, true, true),
ModifyKeys::SetOther,
"\x1b[27;15;127~",
),
ent(
m(true, true, true, true),
ModifyKeys::SetOther,
"\x1b[27;16;127~",
),
ent(m(false, true, false, false), ModifyKeys::Any, "\x08"),
ent(m(false, false, false, false), ModifyKeys::Any, "\x7f"),
ent(m(false, true, false, false), ModifyKeys::Any, "\x08"),
ent(m(false, false, false, false), ModifyKeys::Any, "\x7f"),
],
);
set(
Key::Tab,
vec![
ent(m(true, false, false, false), ModifyKeys::Set, "\x1b[Z"),
ent(m(false, false, true, false), ModifyKeys::Set, "\x1b\t"),
ent(
m(true, false, false, false),
ModifyKeys::SetOther,
"\x1b[27;2;9~",
),
ent(
m(false, false, true, false),
ModifyKeys::SetOther,
"\x1b[27;3;9~",
),
ent(m(true, false, true, false), ModifyKeys::Any, "\x1b[27;4;9~"),
ent(
m(false, true, false, false),
ModifyKeys::Any,
"\x1b[27;5;9~",
),
ent(m(true, true, false, false), ModifyKeys::Any, "\x1b[27;6;9~"),
ent(m(false, true, true, false), ModifyKeys::Any, "\x1b[27;7;9~"),
ent(m(true, true, true, false), ModifyKeys::Any, "\x1b[27;8;9~"),
ent(
m(false, false, false, true),
ModifyKeys::Any,
"\x1b[27;9;9~",
),
ent(
m(true, false, false, true),
ModifyKeys::Any,
"\x1b[27;10;9~",
),
ent(
m(false, false, true, true),
ModifyKeys::Any,
"\x1b[27;11;9~",
),
ent(m(true, false, true, true), ModifyKeys::Any, "\x1b[27;12;9~"),
ent(
m(false, true, false, true),
ModifyKeys::Any,
"\x1b[27;13;9~",
),
ent(m(true, true, false, true), ModifyKeys::Any, "\x1b[27;14;9~"),
ent(m(false, true, true, true), ModifyKeys::Any, "\x1b[27;15;9~"),
ent(m(true, true, true, true), ModifyKeys::Any, "\x1b[27;16;9~"),
ent(m(false, false, false, false), ModifyKeys::Any, "\t"),
ent(m(false, false, false, false), ModifyKeys::Any, "\t"),
],
);
set(
Key::Enter,
vec![
ent(
m(true, false, false, false),
ModifyKeys::Any,
"\x1b[27;2;13~",
),
ent(m(false, false, true, false), ModifyKeys::Set, "\x1b\r"),
ent(
m(false, false, true, false),
ModifyKeys::SetOther,
"\x1b[27;3;13~",
),
ent(
m(true, false, true, false),
ModifyKeys::Any,
"\x1b[27;4;13~",
),
ent(
m(false, true, false, false),
ModifyKeys::Any,
"\x1b[27;5;13~",
),
ent(
m(true, true, false, false),
ModifyKeys::Any,
"\x1b[27;6;13~",
),
ent(
m(false, true, true, false),
ModifyKeys::Any,
"\x1b[27;7;13~",
),
ent(m(true, true, true, false), ModifyKeys::Any, "\x1b[27;8;13~"),
ent(
m(false, false, false, true),
ModifyKeys::Any,
"\x1b[27;9;13~",
),
ent(
m(true, false, false, true),
ModifyKeys::Any,
"\x1b[27;10;13~",
),
ent(
m(false, false, true, true),
ModifyKeys::Any,
"\x1b[27;11;13~",
),
ent(
m(true, false, true, true),
ModifyKeys::Any,
"\x1b[27;12;13~",
),
ent(
m(false, true, false, true),
ModifyKeys::Any,
"\x1b[27;13;13~",
),
ent(
m(true, true, false, true),
ModifyKeys::Any,
"\x1b[27;14;13~",
),
ent(
m(false, true, true, true),
ModifyKeys::Any,
"\x1b[27;15;13~",
),
ent(m(true, true, true, true), ModifyKeys::Any, "\x1b[27;16;13~"),
ent(m(false, false, false, false), ModifyKeys::Any, "\r"),
ent(m(false, false, false, false), ModifyKeys::Any, "\r"),
],
);
set(
Key::Escape,
vec![
ent(
m(true, false, false, false),
ModifyKeys::Any,
"\x1b[27;2;27~",
),
ent(m(false, false, true, false), ModifyKeys::Any, "\x1b\x1b"),
ent(
m(true, false, true, false),
ModifyKeys::Any,
"\x1b[27;4;27~",
),
ent(
m(false, true, false, false),
ModifyKeys::Any,
"\x1b[27;5;27~",
),
ent(
m(true, true, false, false),
ModifyKeys::Any,
"\x1b[27;6;27~",
),
ent(
m(false, true, true, false),
ModifyKeys::Any,
"\x1b[27;7;27~",
),
ent(m(true, true, true, false), ModifyKeys::Any, "\x1b[27;8;27~"),
ent(
m(false, false, false, true),
ModifyKeys::Any,
"\x1b[27;9;27~",
),
ent(
m(true, false, false, true),
ModifyKeys::Any,
"\x1b[27;10;27~",
),
ent(
m(false, false, true, true),
ModifyKeys::Any,
"\x1b[27;11;27~",
),
ent(
m(true, false, true, true),
ModifyKeys::Any,
"\x1b[27;12;27~",
),
ent(
m(false, true, false, true),
ModifyKeys::Any,
"\x1b[27;13;27~",
),
ent(
m(true, true, false, true),
ModifyKeys::Any,
"\x1b[27;14;27~",
),
ent(
m(false, true, true, true),
ModifyKeys::Any,
"\x1b[27;15;27~",
),
ent(m(true, true, true, true), ModifyKeys::Any, "\x1b[27;16;27~"),
ent(m(false, false, false, false), ModifyKeys::Any, "\x1b"),
ent(m(false, false, false, false), ModifyKeys::Any, "\x1b"),
],
);
result
}
/// Returns the default keypad application mode entry.
fn kp_default(suffix: &str) -> Vec<Entry> {
vec![Entry {
mods_empty_is_any: false,
keypad: KeypadMode::Application,
..Entry::new(format!("\x1bO{suffix}"))
}]
}
/// Returns the entries for a keypad key. The suffix is the final character
/// of the sent sequence, such as "r" for kp_2.
fn kp_keys(suffix: &str) -> Vec<Entry> {
let mut pc = pc_style(&format!("\x1bO{{}}{suffix}"));
for entry in &mut pc {
entry.keypad = KeypadMode::Application;
}
cat(kp_default(suffix), pc)
}
/// Returns entries that are dependent on cursor key settings.
fn cursor_key(normal: &str, application: &str) -> Vec<Entry> {
vec![
Entry {
cursor: CursorMode::Normal,
..Entry::new(normal)
},
Entry {
cursor: CursorMode::Application,
..Entry::new(application)
},
]
}
/// Constructs a set of pcStyle function keys using the given format. The
/// format should have exactly one "hole" (`{}`) for the mods code.
/// Example: "\x1b[11;{}~" for F1.
fn pc_style(fmt: &str) -> Vec<Entry> {
MODIFIERS
.iter()
.zip(2..)
.map(|(mods, code)| Entry {
mods: *mods,
..Entry::new(fmt.replace("{}", &code.to_string()))
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn keys_table() {
// All key sequences must fit into a termio array.
// TODO(integration): termio's `Message.WriteReq.Small.Max` isn't
// available here; use its documented limit (38 bytes).
const MAX: usize = 38;
let mut n = 0;
for entries in all_keys() {
for entry in entries {
assert!(entry.sequence.len() <= MAX, "{:?}", entry.sequence);
n += 1;
}
}
assert!(n > 0);
}
}