feat(dist): generate completions, editor syntax, man pages, translations
Ports Ghostty's build-data helpers (main_build_data.zig, src/extra/*, build/mdgen) as the ghostty-build-data binary: bash/zsh/fish completions, Vim syntax/ftdetect/ftplugin/compiler, Sublime syntax (also used by bat), terminfo source and ghostty(1)/ghostty(5) markdown. With the version set to the installed 1.3.1-arch2, all generated files are byte-identical to the installed Ghostty's. - `just docs` renders man pages and HTML with pandoc. - po/: Ghostty's 29 translation catalogs; `just locales` compiles them into the com.mitchellh.ghostty gettext domain. - `just install PREFIX FLATPAK SYSTEM_PACKAGE` installs everything incl. terminfo text/termcap outputs and mirrors GhosttyResources.zig install modes (Flatpak D-Bus template without systemd; lib/systemd/user for system packages). - Build metadata (version, tip/stable channel, commit, build mode) is derived at build time like build_config.zig; GTK/libadwaita compile-time versions come from pkg-config instead of constants. Test Plan: - cargo run -p ghostty-build-data -- +bash | diff - \ /usr/share/bash-completion/completions/ghostty (same for zsh/fish/vim) - just install /tmp/prefix
This commit is contained in:
1 parent
79486601b5
commit
2dc1c6028d
56 files changed
+12614
-16
No files matched your search
@@ -0,0 +1,146 @@
|
||||
//! Fish completions (port of `extra/fish.zig`).
|
||||
|
||||
use std::fmt::Write as _;
|
||||
|
||||
use ghostty_cli::Action;
|
||||
use ghostty_config::cli_args::FieldKind;
|
||||
|
||||
use crate::{actions, config_fields, join_names, join_packed};
|
||||
|
||||
#[must_use]
|
||||
pub fn completions() -> String {
|
||||
let mut w = String::new();
|
||||
let acts = actions();
|
||||
let plus = |sep: &str| {
|
||||
acts.iter()
|
||||
.map(|a| format!("+{}", a.name()))
|
||||
.collect::<Vec<_>>()
|
||||
.join(sep)
|
||||
};
|
||||
|
||||
let _ = writeln!(w, "set -l commands \"{}\"", plus(" "));
|
||||
w.push_str("complete -c ghostty -f\n");
|
||||
w.push_str("complete -c ghostty -s e -l help -f\n");
|
||||
w.push_str(
|
||||
"complete -c ghostty -n \"not __fish_seen_subcommand_from $commands\" -l version -f\n",
|
||||
);
|
||||
|
||||
for f in config_fields() {
|
||||
w.push_str("complete -c ghostty -n \"not __fish_seen_subcommand_from $commands\" -l ");
|
||||
w.push_str(f.name);
|
||||
w.push_str(if f.kind == FieldKind::Bool {
|
||||
" "
|
||||
} else {
|
||||
" -r"
|
||||
});
|
||||
if f.name.starts_with("font-family") {
|
||||
w.push_str(" -f -a \"(ghostty +list-fonts | grep '^[A-Z]')\"");
|
||||
} else if f.name == "theme" {
|
||||
w.push_str(" -f -a \"(ghostty +list-themes | sed -E 's/^(.*) \\(.*\\$/\\1/')\"");
|
||||
} else if f.name == "working-directory" {
|
||||
w.push_str(" -f -k -a \"(__fish_complete_directories)\"");
|
||||
} else {
|
||||
w.push_str(if f.kind == FieldKind::RepeatablePath {
|
||||
" -F"
|
||||
} else {
|
||||
" -f"
|
||||
});
|
||||
match f.kind {
|
||||
FieldKind::Enum(names) => {
|
||||
let _ = write!(w, " -a \"{}\"", join_names(names));
|
||||
}
|
||||
FieldKind::Packed(names) => {
|
||||
let _ = write!(w, " -a \"{}\"", join_packed(names));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
if !f.help.is_empty() {
|
||||
let _ = write!(w, " -d \"{}\"", get_description(&f.help));
|
||||
}
|
||||
w.push('\n');
|
||||
}
|
||||
|
||||
let _ = writeln!(
|
||||
w,
|
||||
"complete -c ghostty -n \"string match -q -- '+*' (commandline -pt)\" -f -a \"{}\"",
|
||||
plus(" ")
|
||||
);
|
||||
|
||||
for a in acts {
|
||||
if matches!(a, Action::Help | Action::Version) {
|
||||
continue;
|
||||
}
|
||||
for (name, kind) in a.option_fields() {
|
||||
let _ = write!(
|
||||
w,
|
||||
"complete -c ghostty -n \"__fish_seen_subcommand_from +{}\" -l {name}",
|
||||
a.name()
|
||||
);
|
||||
if kind != FieldKind::Bool {
|
||||
w.push_str(" -r");
|
||||
}
|
||||
w.push_str(if name == "config-file" { " -F" } else { " -f" });
|
||||
match kind {
|
||||
FieldKind::Enum(names) | FieldKind::OptionalEnum(names) => {
|
||||
let _ = write!(w, " -a \"{}\"", join_names(names));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
w.push('\n');
|
||||
}
|
||||
}
|
||||
w
|
||||
}
|
||||
|
||||
/// First sentence of the help text on one line, with quotes escaped
|
||||
/// (Zig: `getDescription`).
|
||||
#[must_use]
|
||||
pub fn get_description(help: &str) -> String {
|
||||
let b = help.as_bytes();
|
||||
let mut out = Vec::with_capacity(b.len() * 2);
|
||||
let mut prev_was_space = false;
|
||||
for (i, &c) in b.iter().enumerate() {
|
||||
match c {
|
||||
b'.' => {
|
||||
out.push(b'.');
|
||||
if i + 1 >= b.len() {
|
||||
break;
|
||||
}
|
||||
let next = b[i + 1];
|
||||
if next == b' ' || next == b'\n' {
|
||||
break;
|
||||
}
|
||||
}
|
||||
b'\n' => {
|
||||
if !prev_was_space && !out.is_empty() {
|
||||
out.push(b' ');
|
||||
prev_was_space = true;
|
||||
}
|
||||
}
|
||||
b'"' => {
|
||||
out.extend_from_slice(b"\\\"");
|
||||
prev_was_space = false;
|
||||
}
|
||||
_ => {
|
||||
out.push(c);
|
||||
prev_was_space = c == b' ';
|
||||
}
|
||||
}
|
||||
}
|
||||
String::from_utf8_lossy(&out).into_owned()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn get_description_test() {
|
||||
let input = "First sentence with \"quotes\"\nand newlines. Second sentence.";
|
||||
assert_eq!(
|
||||
get_description(input),
|
||||
"First sentence with \\\"quotes\\\" and newlines."
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user