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
This commit is contained in:
1 parent
5b8c92b98c
commit
e03432241b
21 files changed
+13413
-1
No files matched your search
@@ -0,0 +1,148 @@
|
||||
//! This module is a help generator for keybind actions documentation.
|
||||
//! It can generate documentation in different formats (plaintext for CLI,
|
||||
//! markdown for website) while maintaining consistent content.
|
||||
//! (ZIG/src/input/helpgen_actions.zig)
|
||||
//!
|
||||
//! The docs themselves live in [`ActionKey::docs`] (the Zig version reads
|
||||
//! them from the build-time generated `help_strings`).
|
||||
|
||||
use crate::binding::ActionKey;
|
||||
|
||||
/// Format options for generating keybind actions documentation
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Format {
|
||||
/// Plain text output with indentation
|
||||
Plaintext,
|
||||
/// Markdown formatted output
|
||||
Markdown,
|
||||
}
|
||||
|
||||
impl Format {
|
||||
fn format_field_name(self, w: &mut String, field_name: &str) {
|
||||
match self {
|
||||
Format::Plaintext => {
|
||||
w.push_str(field_name);
|
||||
w.push_str(":\n");
|
||||
}
|
||||
Format::Markdown => {
|
||||
w.push_str("## `");
|
||||
w.push_str(field_name);
|
||||
w.push_str("`\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_doc_line(self, w: &mut String, line: &str) {
|
||||
match self {
|
||||
Format::Plaintext => {
|
||||
w.push_str(" ");
|
||||
w.push_str(line);
|
||||
w.push('\n');
|
||||
}
|
||||
Format::Markdown => {
|
||||
w.push_str(line);
|
||||
w.push('\n');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn header(self) -> Option<&'static str> {
|
||||
match self {
|
||||
Format::Plaintext => None,
|
||||
Format::Markdown => Some(
|
||||
"---\n\
|
||||
title: Keybinding Action Reference\n\
|
||||
description: Reference of all Ghostty keybinding actions.\n\
|
||||
editOnGithubLink: https://github.com/ghostty-org/ghostty/edit/main/src/input/Binding.zig\n\
|
||||
---\n\
|
||||
\n\
|
||||
This is a reference of all Ghostty keybinding actions.\n\
|
||||
\n\
|
||||
\n",
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate keybind actions documentation with the specified format
|
||||
pub fn generate(writer: &mut String, format: Format, show_docs: bool) {
|
||||
if let Some(header) = format.header() {
|
||||
writer.push_str(header);
|
||||
}
|
||||
|
||||
let mut stream = String::new();
|
||||
|
||||
for &key in ActionKey::ALL {
|
||||
let name = key.name();
|
||||
if name.starts_with('_') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Write previously stored doc comment below all related actions
|
||||
// (every action has a doc comment).
|
||||
if show_docs {
|
||||
writer.push_str(&stream);
|
||||
writer.push('\n');
|
||||
stream.clear();
|
||||
}
|
||||
|
||||
if show_docs {
|
||||
format.format_field_name(writer, name);
|
||||
} else {
|
||||
writer.push_str(name);
|
||||
writer.push('\n');
|
||||
}
|
||||
|
||||
if show_docs {
|
||||
let mut iter = key.docs().split('\n').peekable();
|
||||
while let Some(s) = iter.next() {
|
||||
// If it is the last line and empty, then skip it.
|
||||
if iter.peek().is_none() && s.is_empty() {
|
||||
continue;
|
||||
}
|
||||
format.format_doc_line(&mut stream, s);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write any remaining buffered documentation
|
||||
if !stream.is_empty() {
|
||||
writer.push_str(&stream);
|
||||
}
|
||||
}
|
||||
|
||||
/// Convenience: generate into a fresh string.
|
||||
pub fn generate_string(format: Format, show_docs: bool) -> String {
|
||||
let mut s = String::new();
|
||||
generate(&mut s, format, show_docs);
|
||||
s
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn plaintext_with_docs_lists_all_actions() {
|
||||
let s = generate_string(Format::Plaintext, true);
|
||||
assert!(s.starts_with("\nignore:\n"));
|
||||
assert!(s.contains("ignore:\n Ignore this key combination.\n"));
|
||||
for &k in ActionKey::ALL {
|
||||
assert!(s.contains(&format!("{}:\n", k.name())));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn names_only() {
|
||||
let s = generate_string(Format::Plaintext, false);
|
||||
assert_eq!(s.lines().count(), ActionKey::ALL.len());
|
||||
assert_eq!(s.lines().next(), Some("ignore"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn markdown_header_and_field() {
|
||||
let s = generate_string(Format::Markdown, true);
|
||||
assert!(s.starts_with("---\ntitle: Keybinding Action Reference\n"));
|
||||
assert!(s.contains("## `unbind`\n"));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user