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
149 lines
4.3 KiB
Rust
149 lines
4.3 KiB
Rust
//! 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"));
|
|
}
|
|
}
|