feat(cli): port ghostty +action CLI commands
Ports ZIG/src/cli into ghostty-cli: action detection/dispatch with per-action --help, and the actions help, version, list-actions, list-colors, list-keybinds, list-themes (plain listing plus an interactive ratatui browser mirroring Ghostty's libvaxis UI), show-config, validate-config, edit-config, crash-report (listing), ssh-cache and boo (235 embedded frames). Verified byte-for-byte against the installed Ghostty 1.3.1 for stdout, stderr and exit codes of most actions, incl. tty output in a pty at widths 20-200. Known differences: +list-colors --plain prints all 782 colors (Ghostty truncates at 617 because it never flushes), +version has placeholder build info until build_info::set is wired up. Deferred: list-fonts, show-face (need font discovery integration) and new-window (needs GTK IPC); they return NotYetAvailable for now. Test Plan: - cargo test -p ghostty-cli (86 tests) - cargo run -p ghostty-cli --example cli -- +list-keybinds --plain and diff against `ghostty +list-keybinds --plain`
This commit is contained in:
1 parent
a97ae04f1d
commit
6286e02f07
46 files changed
+7135
-6
No files matched your search
@@ -0,0 +1,108 @@
|
||||
//! `+list-actions` (ZIG/src/cli/list_actions.zig).
|
||||
|
||||
use std::io::Write;
|
||||
|
||||
use ghostty_config::config_struct;
|
||||
use ghostty_input::binding::ActionKey;
|
||||
|
||||
use crate::error::Error;
|
||||
|
||||
config_struct! {
|
||||
pub struct Options {
|
||||
/// If `true`, print out documentation about the action associated with the
|
||||
/// keybinds.
|
||||
"docs" => docs: bool = false,
|
||||
}
|
||||
}
|
||||
cli_options!(Options, help);
|
||||
|
||||
/// The `list-actions` command is used to list all the available keybind
|
||||
/// actions for Ghostty. These are distinct from the CLI Actions which can
|
||||
/// be listed via `+help`
|
||||
///
|
||||
/// Flags:
|
||||
///
|
||||
/// * `--docs`: will print out the documentation for each action.
|
||||
pub fn run(args: &[String]) -> Result<u8, Error> {
|
||||
let mut opts = Options::default();
|
||||
crate::parse_options(&mut opts, args)?;
|
||||
|
||||
let out = generate_plaintext(opts.docs);
|
||||
let mut stdout = std::io::stdout().lock();
|
||||
stdout.write_all(out.as_bytes())?;
|
||||
stdout.flush()?;
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
/// `helpgen_actions.generate(.plaintext, show_docs)`.
|
||||
///
|
||||
/// TODO(integration): this is a local copy of
|
||||
/// `ghostty_input::helpgen_actions::generate`. The embedded docs of a few
|
||||
/// actions in ghostty-input (`adjust_selection`, ...) end with an extra blank
|
||||
/// line compared to Ghostty's `help_strings`, which adds a stray `" "` line
|
||||
/// to `--docs` output. Trailing newlines are normalised here; once the data
|
||||
/// is fixed in ghostty-input this should call `helpgen_actions::generate`.
|
||||
#[must_use]
|
||||
pub fn generate_plaintext(show_docs: bool) -> String {
|
||||
let mut writer = String::new();
|
||||
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
|
||||
if show_docs {
|
||||
writer.push_str(&stream);
|
||||
writer.push('\n');
|
||||
stream.clear();
|
||||
writer.push_str(name);
|
||||
writer.push_str(":\n");
|
||||
for line in key.docs().trim_end_matches('\n').split('\n') {
|
||||
stream.push_str(" ");
|
||||
stream.push_str(line);
|
||||
stream.push('\n');
|
||||
}
|
||||
} else {
|
||||
writer.push_str(name);
|
||||
writer.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
// Write any remaining buffered documentation
|
||||
writer.push_str(&stream);
|
||||
writer
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#![allow(clippy::unwrap_used)]
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn names_only() {
|
||||
let s = generate_plaintext(false);
|
||||
assert!(s.starts_with("ignore\nunbind\ncsi\n"));
|
||||
assert!(!s.contains(':'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn docs_have_no_stray_blank_lines_between_actions() {
|
||||
let s = generate_plaintext(true);
|
||||
assert!(s.starts_with("\nignore:\n Ignore this key combination.\n"));
|
||||
assert!(s.contains(" respectively.\n\njump_to_prompt:\n"));
|
||||
assert!(s.ends_with('\n'));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matches_input_crate_when_docs_are_clean() {
|
||||
// The first actions have well-formed docs.
|
||||
let ours = generate_plaintext(true);
|
||||
let theirs = ghostty_input::helpgen_actions::generate_string(
|
||||
ghostty_input::helpgen_actions::Format::Plaintext,
|
||||
true,
|
||||
);
|
||||
assert!(ours.lines().take(100).eq(theirs.lines().take(100)));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user