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
b65bfc575c
commit
b192fdb046
56 files changed
+12614
-16
No files matched your search
@@ -16,7 +16,7 @@ const CSS: &[&str] = &[
|
||||
"style-hc.css",
|
||||
"style-hc-dark.css",
|
||||
];
|
||||
// (major, minor, name). The inspector / imgui blueprints are deferred.
|
||||
// (major, minor, name).
|
||||
const BLUEPRINTS: &[(u32, u32, &str)] = &[
|
||||
(1, 0, "clipboard-confirmation-dialog"),
|
||||
(1, 4, "clipboard-confirmation-dialog"),
|
||||
@@ -36,9 +36,31 @@ const BLUEPRINTS: &[(u32, u32, &str)] = &[
|
||||
(1, 5, "title-dialog"),
|
||||
(1, 5, "window"),
|
||||
(1, 5, "command-palette"),
|
||||
(1, 5, "imgui-widget"),
|
||||
(1, 5, "inspector-widget"),
|
||||
(1, 5, "inspector-window"),
|
||||
];
|
||||
|
||||
/// Emits `GHOSTTY_<name>_VERSION` with the version of a library as reported
|
||||
/// by pkg-config (the headers we compile against; Zig reads the same values
|
||||
/// from the C headers). Falls back to the feature level we require.
|
||||
fn emit_pkg_version(env_name: &str, pkg: &str, fallback: &str) {
|
||||
println!("cargo:rerun-if-env-changed=PKG_CONFIG_PATH");
|
||||
let version = Command::new("pkg-config")
|
||||
.args(["--modversion", pkg])
|
||||
.output()
|
||||
.ok()
|
||||
.filter(|o| o.status.success())
|
||||
.and_then(|o| String::from_utf8(o.stdout).ok())
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty())
|
||||
.unwrap_or_else(|| fallback.to_string());
|
||||
println!("cargo:rustc-env={env_name}={version}");
|
||||
}
|
||||
|
||||
fn main() {
|
||||
emit_pkg_version("GHOSTTY_GTK_VERSION", "gtk4", "4.14.0");
|
||||
emit_pkg_version("GHOSTTY_ADW_VERSION", "libadwaita-1", "1.5.0");
|
||||
let manifest = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
|
||||
let out = PathBuf::from(std::env::var("OUT_DIR").unwrap());
|
||||
let res = manifest.join("resources");
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
|
||||
use crate::gtk_version::Version;
|
||||
|
||||
/// The libadwaita feature level this crate is compiled against (`v1_5`).
|
||||
pub const COMPTIME_VERSION: Version = (1, 5, 0);
|
||||
/// The libadwaita version this crate is compiled against (pkg-config
|
||||
/// `libadwaita-1`, see `build.rs`).
|
||||
pub const COMPTIME_VERSION: Version =
|
||||
crate::gtk_version::parse_version(env!("GHOSTTY_ADW_VERSION"));
|
||||
|
||||
/// Returns the runtime libadwaita version.
|
||||
#[allow(unsafe_code)]
|
||||
|
||||
@@ -1,15 +1,35 @@
|
||||
//! GTK version helpers (port of `apprt/gtk/gtk_version.zig`).
|
||||
//!
|
||||
//! Zig reads the compile-time version from the headers; here it is the
|
||||
//! version of the gtk4-rs `v4_*` feature level this crate is compiled against
|
||||
//! (see `Cargo.toml`) combined with the system library detected at runtime.
|
||||
//! version of the GTK development files reported by pkg-config at build time
|
||||
//! (see `build.rs`) combined with the system library detected at runtime.
|
||||
|
||||
/// A (major, minor, micro) version triple.
|
||||
pub type Version = (u32, u32, u32);
|
||||
|
||||
/// The minimum GTK version this crate is compiled against (feature level
|
||||
/// `v4_14`). Newer symbols must be guarded with [`at_least`]/feature flags.
|
||||
pub const COMPTIME_VERSION: Version = (4, 14, 0);
|
||||
/// The GTK version this crate is compiled against (pkg-config `gtk4`).
|
||||
/// Newer symbols must still be guarded with [`at_least`]/feature flags.
|
||||
pub const COMPTIME_VERSION: Version = parse_version(env!("GHOSTTY_GTK_VERSION"));
|
||||
|
||||
/// Parses a `major.minor[.micro]` string at compile time.
|
||||
pub(crate) const fn parse_version(s: &str) -> (u32, u32, u32) {
|
||||
let b = s.as_bytes();
|
||||
let mut parts = [0u32; 3];
|
||||
let mut idx = 0;
|
||||
let mut i = 0;
|
||||
while i < b.len() && idx < 3 {
|
||||
let c = b[i];
|
||||
if c == b'.' {
|
||||
idx += 1;
|
||||
} else if c.is_ascii_digit() {
|
||||
parts[idx] = parts[idx] * 10 + (c - b'0') as u32;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
(parts[0], parts[1], parts[2])
|
||||
}
|
||||
|
||||
/// Returns the runtime GTK version.
|
||||
pub fn get_runtime_version() -> Version {
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
[package]
|
||||
name = "ghostty-build-data"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
description = "Generates build data (completions, editor syntax, man page markdown, terminfo) like Ghostty's main_build_data.zig"
|
||||
|
||||
[lib]
|
||||
path = "src/lib.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "ghostty-build-data"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
ghostty-cli.workspace = true
|
||||
ghostty-config.workspace = true
|
||||
ghostty-input.workspace = true
|
||||
ghostty-os.workspace = true
|
||||
ghostty-termio.workspace = true
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1,129 @@
|
||||
//! Bash completions (port of `extra/bash.zig`).
|
||||
|
||||
use std::fmt::Write as _;
|
||||
|
||||
use ghostty_config::cli_args::FieldKind;
|
||||
|
||||
use crate::chunks_bash::{C0, C1, C2, C3, C4, C5, C6};
|
||||
use crate::{actions, config_fields, join_names, join_packed};
|
||||
|
||||
const COMPGEN_PREFIX: &str = "_compreply compgen -W \"";
|
||||
const COMPGEN_SUFFIX: &str = "\" -- \"$cur\"; _add_spaces ;;";
|
||||
|
||||
fn is_flag(kind: FieldKind) -> bool {
|
||||
matches!(kind, FieldKind::Bool | FieldKind::OptionalBool)
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn completions() -> String {
|
||||
let pad1 = " ";
|
||||
let pad2 = " ";
|
||||
let pad3 = " ";
|
||||
let pad4 = " ";
|
||||
let pad5 = " ";
|
||||
let mut w = String::new();
|
||||
let fields = config_fields();
|
||||
|
||||
w.push_str(C0);
|
||||
for f in &fields {
|
||||
if is_flag(f.kind) {
|
||||
let _ = writeln!(w, "{pad2}config+=\" '--{} '\"", f.name);
|
||||
} else {
|
||||
let _ = writeln!(w, "{pad2}config+=\" --{}=\"", f.name);
|
||||
}
|
||||
}
|
||||
|
||||
w.push_str(C1);
|
||||
for f in &fields {
|
||||
let _ = write!(w, "{pad3}--{}) ", f.name);
|
||||
if f.name.starts_with("font-family") {
|
||||
w.push_str("_fonts ;;");
|
||||
} else if f.name == "theme" {
|
||||
w.push_str("_themes ;;");
|
||||
} else if f.name == "working-directory" {
|
||||
w.push_str("_dirs ;;");
|
||||
} else if f.kind == FieldKind::RepeatablePath {
|
||||
w.push_str("_files ;;");
|
||||
} else {
|
||||
match f.kind {
|
||||
FieldKind::Enum(names) => {
|
||||
let _ = write!(w, "{COMPGEN_PREFIX}{}{COMPGEN_SUFFIX}", join_names(names));
|
||||
}
|
||||
FieldKind::Packed(names) => {
|
||||
let _ = write!(w, "{COMPGEN_PREFIX}{}{COMPGEN_SUFFIX}", join_packed(names));
|
||||
}
|
||||
_ => w.push_str("return ;;"),
|
||||
}
|
||||
}
|
||||
w.push('\n');
|
||||
}
|
||||
|
||||
w.push_str(C2);
|
||||
let acts = actions();
|
||||
for a in acts {
|
||||
let opts = a.option_fields();
|
||||
if opts.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let bash_name = a.name().replace('-', "_");
|
||||
let _ = write!(w, "{pad2}local {bash_name}=\"");
|
||||
for (count, (name, kind)) in opts.iter().enumerate() {
|
||||
if count > 0 {
|
||||
w.push(' ');
|
||||
}
|
||||
if is_flag(*kind) {
|
||||
let _ = write!(w, "'--{name} '");
|
||||
} else {
|
||||
let _ = write!(w, "--{name}=");
|
||||
}
|
||||
}
|
||||
w.push_str(" --help\"\n");
|
||||
}
|
||||
|
||||
w.push_str(C3);
|
||||
for a in acts {
|
||||
let opts = a.option_fields();
|
||||
if opts.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let bash_name = a.name().replace('-', "_");
|
||||
let _ = writeln!(w, "{pad3}+{})", a.name());
|
||||
let _ = writeln!(w, "{pad4}case $prev in");
|
||||
for (name, kind) in &opts {
|
||||
let _ = write!(w, "{pad5}--{name}) ");
|
||||
match kind {
|
||||
FieldKind::Bool => w.push_str("return ;;"),
|
||||
FieldKind::Enum(names) | FieldKind::OptionalEnum(names) => {
|
||||
let _ = write!(w, "{COMPGEN_PREFIX}{}{COMPGEN_SUFFIX}", join_names(names));
|
||||
}
|
||||
FieldKind::OptionalBool | FieldKind::OptionalOther => {
|
||||
if *name == "config-file" {
|
||||
w.push_str("return ;;");
|
||||
} else {
|
||||
w.push_str("return;;");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if *name == "config-file" {
|
||||
w.push_str("_files ;;");
|
||||
} else {
|
||||
w.push_str("return;;");
|
||||
}
|
||||
}
|
||||
}
|
||||
w.push('\n');
|
||||
}
|
||||
let _ = writeln!(
|
||||
w,
|
||||
"{pad5}*) _compreply compgen -W \"${bash_name}\" -- \"$cur\" ;;"
|
||||
);
|
||||
w.push_str(C4);
|
||||
}
|
||||
|
||||
w.push_str(C5);
|
||||
for a in acts {
|
||||
let _ = writeln!(w, "{pad1}topLevel+=\" +{}\"", a.name());
|
||||
}
|
||||
w.push_str(C6);
|
||||
w
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
// Generated from the Zig multiline strings of extra/bash.zig. Do not edit.
|
||||
pub const C0: &str = r####"_ghostty() {
|
||||
|
||||
# compat: mapfile -t COMPREPLY < <( "$@" )
|
||||
_compreply() {
|
||||
COMPREPLY=()
|
||||
while IFS='' read -r line; do COMPREPLY+=("$line"); done < <( "$@" )
|
||||
}
|
||||
|
||||
# -o nospace requires we add back a space when a completion is finished
|
||||
# and not part of a --key= completion
|
||||
_add_spaces() {
|
||||
for idx in "${!COMPREPLY[@]}"; do
|
||||
[ -n "${COMPREPLY[idx]}" ] && COMPREPLY[idx]="${COMPREPLY[idx]} ";
|
||||
done
|
||||
}
|
||||
|
||||
_fonts() {
|
||||
local IFS=$'\n'
|
||||
COMPREPLY=()
|
||||
while read -r line; do COMPREPLY+=("$line"); done < <( compgen -P '"' -S '"' -W "$($ghostty +list-fonts | grep '^[A-Z]' )" -- "$cur")
|
||||
}
|
||||
|
||||
_themes() {
|
||||
local IFS=$'\n'
|
||||
COMPREPLY=()
|
||||
while read -r line; do COMPREPLY+=("$line"); done < <( compgen -P '"' -S '"' -W "$($ghostty +list-themes | sed -E 's/^(.*) \(.*$/\1/')" -- "$cur")
|
||||
}
|
||||
|
||||
_files() {
|
||||
_compreply compgen -o filenames -f -- "$cur"
|
||||
for i in "${!COMPREPLY[@]}"; do
|
||||
if [[ -d "${COMPREPLY[i]}" ]]; then
|
||||
COMPREPLY[i]="${COMPREPLY[i]}/";
|
||||
fi
|
||||
if [[ -f "${COMPREPLY[i]}" ]]; then
|
||||
COMPREPLY[i]="${COMPREPLY[i]} ";
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
_dirs() {
|
||||
_compreply compgen -o dirnames -d -- "$cur"
|
||||
for i in "${!COMPREPLY[@]}"; do
|
||||
if [[ -d "${COMPREPLY[i]}" ]]; then
|
||||
COMPREPLY[i]="${COMPREPLY[i]}/";
|
||||
fi
|
||||
done
|
||||
if [[ "${#COMPREPLY[@]}" == 0 && -d "$cur" ]]; then
|
||||
COMPREPLY=( "$cur " )
|
||||
fi
|
||||
}
|
||||
|
||||
_handle_config() {
|
||||
local config="--help"
|
||||
config+=" --version"
|
||||
"####;
|
||||
pub const C1: &str = r####"
|
||||
case "$prev" in
|
||||
"####;
|
||||
pub const C2: &str = r####" *) _compreply compgen -W "$config" -- "$cur" ;;
|
||||
esac
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
_handle_actions() {
|
||||
"####;
|
||||
pub const C3: &str = r####"
|
||||
case "${COMP_WORDS[1]}" in
|
||||
"####;
|
||||
pub const C4: &str = r####" esac
|
||||
;;
|
||||
"####;
|
||||
pub const C5: &str = r####" *) _compreply compgen -W "--help" -- "$cur" ;;
|
||||
esac
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
# begin main logic
|
||||
local topLevel="-e"
|
||||
topLevel+=" --help"
|
||||
topLevel+=" --version"
|
||||
"####;
|
||||
pub const C6: &str = r####"
|
||||
local cur=""; local prev=""; local prevWasEq=false; COMPREPLY=()
|
||||
local ghostty="$1"
|
||||
|
||||
# script assumes default COMP_WORDBREAKS of roughly $' \t\n"\'><=;|&(:'
|
||||
# if = is missing this script will degrade to matching on keys only.
|
||||
# eg: --key=
|
||||
# this can be improved if needed see: https://github.com/ghostty-org/ghostty/discussions/2994
|
||||
|
||||
if [ "$2" = "=" ]; then cur=""
|
||||
else cur="$2"
|
||||
fi
|
||||
|
||||
if [ "$3" = "=" ]; then prev="${COMP_WORDS[COMP_CWORD-2]}"; prevWasEq=true;
|
||||
else prev="${COMP_WORDS[COMP_CWORD-1]}"
|
||||
fi
|
||||
|
||||
# current completion is double quoted add a space so the cursor progresses
|
||||
if [[ "$2" == \"*\" ]]; then
|
||||
COMPREPLY=( "$cur " );
|
||||
return;
|
||||
fi
|
||||
|
||||
case "$COMP_CWORD" in
|
||||
1)
|
||||
case "${COMP_WORDS[1]}" in
|
||||
-e | --help | --version) return 0 ;;
|
||||
--*) _handle_config ;;
|
||||
*) _compreply compgen -W "${topLevel}" -- "$cur"; _add_spaces ;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
case "$prev" in
|
||||
-e | --help | --version) return 0 ;;
|
||||
*)
|
||||
if [[ "=" != "${COMP_WORDS[COMP_CWORD]}" && $prevWasEq != true ]]; then
|
||||
# must be completing with a space after the key eg: '--<key> '
|
||||
# clear out prev so we don't run any of the key specific completions
|
||||
prev=""
|
||||
fi
|
||||
|
||||
case "${COMP_WORDS[1]}" in
|
||||
--*) _handle_config ;;
|
||||
+*) _handle_actions ;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
esac
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
complete -o nospace -o bashdefault -F _ghostty ghostty
|
||||
"####;
|
||||
@@ -0,0 +1 @@
|
||||
// Generated from the Zig multiline strings of extra/fish.zig. Do not edit.
|
||||
@@ -0,0 +1,20 @@
|
||||
// Generated from the Zig multiline strings of extra/sublime.zig. Do not edit.
|
||||
pub const C0: &str = r####"%YAML 1.2
|
||||
---
|
||||
# See http://www.sublimetext.com/docs/syntax.html
|
||||
name: Ghostty Config
|
||||
file_extensions:
|
||||
- ghostty
|
||||
scope: source.ghostty
|
||||
|
||||
contexts:
|
||||
main:
|
||||
# Comments
|
||||
- match: '^\s*#.*$'
|
||||
scope: comment.line.number-sign.ghostty
|
||||
|
||||
# Keywords
|
||||
- match: '\b("####;
|
||||
pub const C1: &str = r####")\b'
|
||||
scope: keyword.other.ghostty
|
||||
"####;
|
||||
@@ -0,0 +1,74 @@
|
||||
// Generated from the Zig multiline strings of extra/vim.zig. Do not edit.
|
||||
pub const C0: &str = r####"" Vim filetype detect file
|
||||
" Language: Ghostty config file
|
||||
" Maintainer: Ghostty <https://github.com/ghostty-org/ghostty>
|
||||
"
|
||||
" THIS FILE IS AUTO-GENERATED
|
||||
|
||||
au BufRead,BufNewFile */ghostty/config,*/*.ghostty/config,*/ghostty/themes/*,*.ghostty setf ghostty
|
||||
"####;
|
||||
pub const C1: &str = r####"" Vim filetype plugin file
|
||||
" Language: Ghostty config file
|
||||
" Maintainer: Ghostty <https://github.com/ghostty-org/ghostty>
|
||||
"
|
||||
" THIS FILE IS AUTO-GENERATED
|
||||
|
||||
if exists('b:did_ftplugin')
|
||||
finish
|
||||
endif
|
||||
let b:did_ftplugin = 1
|
||||
|
||||
setlocal commentstring=#\ %s
|
||||
setlocal iskeyword+=-
|
||||
|
||||
" Use syntax keywords for completion
|
||||
setlocal omnifunc=syntaxcomplete#Complete
|
||||
|
||||
let b:undo_ftplugin = 'setl cms< isk< ofu<'
|
||||
|
||||
if !exists('current_compiler')
|
||||
compiler ghostty
|
||||
let b:undo_ftplugin .= " makeprg< errorformat<"
|
||||
endif
|
||||
"####;
|
||||
pub const C2: &str = r####"" Vim compiler file
|
||||
" Language: Ghostty config file
|
||||
" Maintainer: Ghostty <https://github.com/ghostty-org/ghostty>
|
||||
"
|
||||
" THIS FILE IS AUTO-GENERATED
|
||||
|
||||
if exists("current_compiler")
|
||||
finish
|
||||
endif
|
||||
let current_compiler = "ghostty"
|
||||
|
||||
CompilerSet makeprg=ghostty\ +validate-config\ --config-file=%:S
|
||||
CompilerSet errorformat=%f:%l:%m,%m
|
||||
"####;
|
||||
pub const C3: &str = r####"" Vim syntax file
|
||||
" Language: Ghostty config file
|
||||
" Maintainer: Ghostty <https://github.com/ghostty-org/ghostty>
|
||||
"
|
||||
" THIS FILE IS AUTO-GENERATED
|
||||
|
||||
if exists('b:current_syntax')
|
||||
finish
|
||||
endif
|
||||
|
||||
let b:current_syntax = 'ghostty'
|
||||
|
||||
let s:cpo_save = &cpo
|
||||
set cpo&vim
|
||||
|
||||
syn iskeyword @,48-57,-
|
||||
syn keyword ghosttyConfigKeyword"####;
|
||||
pub const C4: &str = r####"
|
||||
|
||||
syn match ghosttyConfigComment /^\s*#.*/ contains=@Spell
|
||||
|
||||
hi def link ghosttyConfigComment Comment
|
||||
hi def link ghosttyConfigKeyword Keyword
|
||||
|
||||
let &cpo = s:cpo_save
|
||||
unlet s:cpo_save
|
||||
"####;
|
||||
@@ -0,0 +1,58 @@
|
||||
// Generated from the Zig multiline strings of extra/zsh.zig. Do not edit.
|
||||
pub const C0: &str = r####"#compdef ghostty
|
||||
|
||||
_fonts () {
|
||||
local font_list=$(ghostty +list-fonts | grep -Z '^[A-Z]')
|
||||
local fonts=(${(f)font_list})
|
||||
_describe -t fonts 'fonts' fonts
|
||||
}
|
||||
|
||||
_themes() {
|
||||
local theme_list=$(ghostty +list-themes | sed -E 's/^(.*) \(.*$/\1/')
|
||||
local themes=(${(f)theme_list})
|
||||
_describe -t themes 'themes' themes
|
||||
}
|
||||
"####;
|
||||
pub const C1: &str = r####"_ghostty() {
|
||||
typeset -A opt_args
|
||||
local context state line
|
||||
local opt=('-e' '--help' '--version')
|
||||
|
||||
_arguments -C \
|
||||
'1:actions:->actions' \
|
||||
'*:: :->rest' \
|
||||
|
||||
if [[ "$line[1]" == "--help" || "$line[1]" == "--version" || "$line[1]" == "-e" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$line[1]" == -* ]]; then
|
||||
_config
|
||||
return
|
||||
fi
|
||||
|
||||
case "$state" in
|
||||
(actions)
|
||||
local actions; actions=(
|
||||
"####;
|
||||
pub const C2: &str = r####" )
|
||||
_describe '' opt
|
||||
_describe -t action 'action' actions
|
||||
;;
|
||||
(rest)
|
||||
if [[ "$line[2]" == "--help" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
local help=('--help')
|
||||
_describe '' help
|
||||
|
||||
case $line[1] in
|
||||
"####;
|
||||
pub const C3: &str = r####" esac
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
_ghostty "$@"
|
||||
"####;
|
||||
@@ -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."
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,200 @@
|
||||
//! Build-data generators (port of `src/main_build_data.zig`, `src/extra/*.zig`
|
||||
//! and `src/build/mdgen/*`).
|
||||
//!
|
||||
//! The binary prints the requested artifact to stdout; the `just install`
|
||||
//! recipe (mirroring `GhosttyResources.zig` / `GhosttyDocs.zig`) captures it.
|
||||
|
||||
#![allow(clippy::too_many_lines)]
|
||||
#![cfg_attr(test, allow(clippy::expect_used))]
|
||||
|
||||
use ghostty_cli::Action;
|
||||
use ghostty_config::cli_args::FieldKind;
|
||||
use ghostty_config::config::Config;
|
||||
use ghostty_config::macros::ConfigStruct;
|
||||
|
||||
pub mod bash;
|
||||
pub mod fish;
|
||||
pub mod mdgen;
|
||||
pub mod sublime;
|
||||
pub mod vim;
|
||||
pub mod zsh;
|
||||
|
||||
#[allow(clippy::needless_raw_string_hashes)]
|
||||
mod chunks_bash;
|
||||
#[allow(clippy::needless_raw_string_hashes)]
|
||||
mod chunks_sublime;
|
||||
#[allow(clippy::needless_raw_string_hashes)]
|
||||
mod chunks_vim;
|
||||
#[allow(clippy::needless_raw_string_hashes)]
|
||||
mod chunks_zsh;
|
||||
|
||||
/// The modes of the generator (Zig: `main_build_data.Action`, plus the two
|
||||
/// `mdgen` executables which are separate binaries upstream).
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum Mode {
|
||||
Bash,
|
||||
Fish,
|
||||
Zsh,
|
||||
Sublime,
|
||||
VimSyntax,
|
||||
VimFtdetect,
|
||||
VimFtplugin,
|
||||
VimCompiler,
|
||||
Terminfo,
|
||||
/// `ghostty(1)` man page markdown (Zig: `mdgen_ghostty_1`).
|
||||
Man1,
|
||||
/// `ghostty(5)` man page markdown (Zig: `mdgen_ghostty_5`).
|
||||
Man5,
|
||||
}
|
||||
|
||||
impl Mode {
|
||||
pub const ALL: &'static [(&'static str, Mode)] = &[
|
||||
("bash", Mode::Bash),
|
||||
("fish", Mode::Fish),
|
||||
("zsh", Mode::Zsh),
|
||||
("sublime", Mode::Sublime),
|
||||
("vim-syntax", Mode::VimSyntax),
|
||||
("vim-ftdetect", Mode::VimFtdetect),
|
||||
("vim-ftplugin", Mode::VimFtplugin),
|
||||
("vim-compiler", Mode::VimCompiler),
|
||||
("terminfo", Mode::Terminfo),
|
||||
("mdgen-ghostty-1", Mode::Man1),
|
||||
("mdgen-ghostty-5", Mode::Man5),
|
||||
];
|
||||
|
||||
/// Parse `+name` (Zig action syntax) or a bare `name`.
|
||||
#[must_use]
|
||||
pub fn parse(arg: &str) -> Option<Mode> {
|
||||
let name = arg.strip_prefix('+').unwrap_or(arg);
|
||||
Mode::ALL.iter().find(|(n, _)| *n == name).map(|(_, m)| *m)
|
||||
}
|
||||
|
||||
/// Generate the output of this mode.
|
||||
#[must_use]
|
||||
pub fn generate(self) -> String {
|
||||
match self {
|
||||
Mode::Bash => bash::completions(),
|
||||
Mode::Fish => fish::completions(),
|
||||
Mode::Zsh => zsh::completions(),
|
||||
Mode::Sublime => sublime::syntax(),
|
||||
Mode::VimSyntax => vim::syntax(),
|
||||
Mode::VimFtdetect => vim::FTDETECT.to_string(),
|
||||
Mode::VimFtplugin => vim::FTPLUGIN.to_string(),
|
||||
Mode::VimCompiler => vim::COMPILER.to_string(),
|
||||
Mode::Terminfo => ghostty_termio::terminfo::ghostty.encode_to_string(),
|
||||
Mode::Man1 => mdgen::ghostty_1(),
|
||||
Mode::Man5 => mdgen::ghostty_5(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A public (non-`_`) config field.
|
||||
pub(crate) struct CfgField {
|
||||
pub name: &'static str,
|
||||
pub kind: FieldKind,
|
||||
pub help: String,
|
||||
}
|
||||
|
||||
/// The non-internal fields of `Config` in declaration order.
|
||||
pub(crate) fn config_fields() -> Vec<CfgField> {
|
||||
Config::FIELDS
|
||||
.iter()
|
||||
.filter(|f| !f.name.starts_with('_'))
|
||||
.map(|f| CfgField {
|
||||
name: f.name,
|
||||
kind: f.kind,
|
||||
help: f.help(),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Space separated enum/packed names for a completion word list.
|
||||
pub(crate) fn join_names(names: &[&str]) -> String {
|
||||
names.join(" ")
|
||||
}
|
||||
|
||||
/// Space separated `name no-name` pairs (packed structs).
|
||||
pub(crate) fn join_packed(names: &[&str]) -> String {
|
||||
names
|
||||
.iter()
|
||||
.map(|n| format!("{n} no-{n}"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
}
|
||||
|
||||
/// All CLI actions in declaration order.
|
||||
pub(crate) fn actions() -> &'static [Action] {
|
||||
Action::ALL
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn every_mode_generates_output() {
|
||||
for (name, mode) in Mode::ALL {
|
||||
assert!(!mode.generate().is_empty(), "{name}");
|
||||
}
|
||||
assert_eq!(Mode::parse("+bash"), Some(Mode::Bash));
|
||||
assert_eq!(Mode::parse("vim-syntax"), Some(Mode::VimSyntax));
|
||||
assert_eq!(Mode::parse("nope"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn completions_cover_config_and_actions() {
|
||||
let bash = bash::completions();
|
||||
assert!(bash.starts_with("_ghostty() {"));
|
||||
assert!(bash.contains("--font-family) _fonts ;;"));
|
||||
assert!(bash.contains("config+=\" '--maximize '\""));
|
||||
assert!(bash.contains("local list_themes=\""));
|
||||
assert!(bash.contains("topLevel+=\" +new-window\""));
|
||||
let zsh = zsh::completions();
|
||||
assert!(zsh.starts_with("#compdef ghostty"));
|
||||
assert!(zsh.contains("\"--working-directory=-:::{_files -/}\""));
|
||||
let fish = fish::completions();
|
||||
assert!(fish.starts_with("set -l commands \"+version +help"));
|
||||
assert!(fish.contains(" -l theme -r -f -a \"(ghostty +list-themes"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn editor_syntax_lists_config_keys() {
|
||||
assert!(vim::syntax().contains("\n\t\\ font-family"));
|
||||
assert!(sublime::syntax().contains("|font-family|"));
|
||||
assert!(!sublime::syntax().contains("|_"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn man_pages_have_sections() {
|
||||
let one = mdgen::ghostty_1();
|
||||
assert!(one.starts_with("% GHOSTTY(1) Version "));
|
||||
assert!(!one.contains("@@VERSION@@"));
|
||||
assert!(one.contains("**`+list-fonts`**\n\n: "));
|
||||
assert!(one.contains("**`--font-family`**"));
|
||||
let five = mdgen::ghostty_5();
|
||||
assert!(five.contains("# KEYBIND ACTIONS"));
|
||||
assert!(five.contains("**`new_split`**"));
|
||||
}
|
||||
|
||||
/// Every supported locale has a `.po` catalog and vice versa
|
||||
/// (Zig: `GhosttyI18n.zig` iterates `os/i18n_locales.zig`).
|
||||
#[test]
|
||||
fn po_catalogs_match_locales() {
|
||||
let dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../../po");
|
||||
let mut found: Vec<String> = std::fs::read_dir(dir)
|
||||
.expect("po dir")
|
||||
.filter_map(Result::ok)
|
||||
.filter_map(|e| {
|
||||
let n = e.file_name().into_string().ok()?;
|
||||
n.strip_suffix(".po").map(str::to_owned)
|
||||
})
|
||||
.collect();
|
||||
found.sort();
|
||||
let mut want: Vec<String> = ghostty_os::i18n_locales::LOCALES
|
||||
.iter()
|
||||
.map(|s| (*s).to_owned())
|
||||
.collect();
|
||||
want.sort();
|
||||
assert_eq!(found, want);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
//! `ghostty-build-data`: prints generated build data to stdout
|
||||
//! (port of `src/main_build_data.zig`).
|
||||
//!
|
||||
//! Usage: `ghostty-build-data +bash|+zsh|+fish|+sublime|+vim-syntax|
|
||||
//! +vim-ftdetect|+vim-ftplugin|+vim-compiler|+terminfo|+mdgen-ghostty-1|
|
||||
//! +mdgen-ghostty-5`
|
||||
|
||||
use std::io::Write;
|
||||
use std::process::ExitCode;
|
||||
|
||||
use ghostty_build_data::Mode;
|
||||
|
||||
fn main() -> ExitCode {
|
||||
let Some(mode) = std::env::args().nth(1).and_then(|a| Mode::parse(&a)) else {
|
||||
let names: Vec<_> = Mode::ALL.iter().map(|(n, _)| format!("+{n}")).collect();
|
||||
eprintln!("usage: ghostty-build-data <{}>", names.join("|"));
|
||||
return ExitCode::FAILURE;
|
||||
};
|
||||
let out = mode.generate();
|
||||
let mut stdout = std::io::stdout().lock();
|
||||
if stdout
|
||||
.write_all(out.as_bytes())
|
||||
.and_then(|()| stdout.flush())
|
||||
.is_err()
|
||||
{
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
ExitCode::SUCCESS
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
//! Man page markdown generator (port of `build/mdgen/{mdgen,main_ghostty_1,
|
||||
//! main_ghostty_5}.zig`). The markdown is converted to roff/html with pandoc
|
||||
//! by `just docs` / `just install` (Zig: `GhosttyDocs.zig`).
|
||||
|
||||
use ghostty_cli::build_info;
|
||||
use ghostty_input::binding::ActionKey;
|
||||
|
||||
use crate::config_fields;
|
||||
|
||||
const GHOSTTY_1_HEADER: &str = include_str!("mdgen/ghostty_1_header.md");
|
||||
const GHOSTTY_1_FOOTER: &str = include_str!("mdgen/ghostty_1_footer.md");
|
||||
const GHOSTTY_5_HEADER: &str = include_str!("mdgen/ghostty_5_header.md");
|
||||
const GHOSTTY_5_FOOTER: &str = include_str!("mdgen/ghostty_5_footer.md");
|
||||
|
||||
/// Replaces `@@VERSION@@` with the build's version string.
|
||||
#[must_use]
|
||||
pub fn substitute(input: &str) -> String {
|
||||
input.replace("@@VERSION@@", &build_info::get().version_string)
|
||||
}
|
||||
|
||||
/// Write a definition-list body: first line `: `, others indented.
|
||||
fn write_help(w: &mut String, help: &str) {
|
||||
for (i, line) in help.split('\n').enumerate() {
|
||||
w.push_str(if i == 0 { ": " } else { " " });
|
||||
w.push_str(line);
|
||||
w.push('\n');
|
||||
}
|
||||
w.push_str("\n\n");
|
||||
}
|
||||
|
||||
pub fn gen_config(w: &mut String, cli: bool) {
|
||||
w.push_str("\n# CONFIGURATION OPTIONS\n\n");
|
||||
for f in config_fields() {
|
||||
w.push_str("**`");
|
||||
if cli {
|
||||
w.push_str("--");
|
||||
}
|
||||
w.push_str(f.name);
|
||||
w.push_str("`**\n\n");
|
||||
if !f.help.is_empty() {
|
||||
write_help(w, &f.help);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_actions(w: &mut String) {
|
||||
w.push_str("\n# COMMAND LINE ACTIONS\n\n");
|
||||
for &action in ghostty_cli::Action::ALL {
|
||||
match action {
|
||||
ghostty_cli::Action::Help => w.push_str("**`--help`**\n\n"),
|
||||
ghostty_cli::Action::Version => w.push_str("**`--version`**\n\n"),
|
||||
_ => {
|
||||
w.push_str("**`+");
|
||||
w.push_str(action.name());
|
||||
w.push_str("`**\n\n");
|
||||
}
|
||||
}
|
||||
write_help(w, action.help_text());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn gen_keybind_actions(w: &mut String) {
|
||||
w.push_str("\n# KEYBIND ACTIONS\n\n");
|
||||
for &key in ActionKey::ALL {
|
||||
let name = key.name();
|
||||
if name.starts_with('_') {
|
||||
continue;
|
||||
}
|
||||
w.push_str("**`");
|
||||
w.push_str(name);
|
||||
w.push_str("`**\n\n");
|
||||
write_help(w, key.help_string());
|
||||
}
|
||||
}
|
||||
|
||||
/// `ghostty(1)` markdown.
|
||||
#[must_use]
|
||||
pub fn ghostty_1() -> String {
|
||||
let mut w = substitute(GHOSTTY_1_HEADER);
|
||||
gen_actions(&mut w);
|
||||
gen_config(&mut w, true);
|
||||
w.push_str(&substitute(GHOSTTY_1_FOOTER));
|
||||
w
|
||||
}
|
||||
|
||||
/// `ghostty(5)` markdown.
|
||||
#[must_use]
|
||||
pub fn ghostty_5() -> String {
|
||||
let mut w = substitute(GHOSTTY_5_HEADER);
|
||||
gen_config(&mut w, false);
|
||||
gen_keybind_actions(&mut w);
|
||||
w.push_str(&substitute(GHOSTTY_5_FOOTER));
|
||||
w
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
# FILES
|
||||
|
||||
_\$XDG_CONFIG_HOME/ghostty/config.ghostty_
|
||||
|
||||
: Location of the default configuration file.
|
||||
|
||||
_\$HOME/Library/Application Support/com.mitchellh.ghostty/config.ghostty_
|
||||
|
||||
: **On macOS**, location of the default configuration file. This location takes
|
||||
precedence over the XDG environment locations.
|
||||
|
||||
_\$LOCALAPPDATA/ghostty/config.ghostty_
|
||||
|
||||
: **On Windows**, if _\$XDG_CONFIG_HOME_ is not set, _\$LOCALAPPDATA_ will be searched
|
||||
for configuration files.
|
||||
|
||||
# ENVIRONMENT
|
||||
|
||||
**TERM**
|
||||
|
||||
: Defaults to `xterm-ghostty`. Can be configured with the `term` configuration option.
|
||||
|
||||
**GHOSTTY_RESOURCES_DIR**
|
||||
|
||||
: Where the Ghostty resources can be found.
|
||||
|
||||
**XDG_CONFIG_HOME**
|
||||
|
||||
: Default location for configuration files.
|
||||
|
||||
**$HOME/Library/Application Support/com.mitchellh.ghostty**
|
||||
|
||||
: **MACOS ONLY** default location for configuration files. This location takes
|
||||
precedence over the XDG environment locations.
|
||||
|
||||
**LOCALAPPDATA**
|
||||
|
||||
: **WINDOWS ONLY:** alternate location to search for configuration files.
|
||||
|
||||
**GHOSTTY_LOG**
|
||||
|
||||
: The `GHOSTTY_LOG` environment variable can be used to control which
|
||||
destinations receive logs. Ghostty currently defines two destinations:
|
||||
|
||||
: - `stderr` - logging to `stderr`.
|
||||
: - `macos` - logging to macOS's unified log (has no effect on non-macOS platforms).
|
||||
|
||||
: Combine values with a comma to enable multiple destinations. Prefix a
|
||||
destination with `no-` to disable it. Enabling and disabling destinations
|
||||
can be done at the same time. Setting `GHOSTTY_LOG` to `true` will enable all
|
||||
destinations. Setting `GHOSTTY_LOG` to `false` will disable all destinations.
|
||||
|
||||
# BUGS
|
||||
|
||||
See GitHub issues: <https://github.com/ghostty-org/ghostty/issues>
|
||||
|
||||
# AUTHOR
|
||||
|
||||
Mitchell Hashimoto <m@mitchellh.com>
|
||||
Ghostty contributors <https://github.com/ghostty-org/ghostty/graphs/contributors>
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
**ghostty(5)**
|
||||
@@ -0,0 +1,23 @@
|
||||
% GHOSTTY(1) Version @@VERSION@@ | Ghostty terminal emulator
|
||||
|
||||
# NAME
|
||||
|
||||
**ghostty** - Ghostty terminal emulator
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
Ghostty is a cross-platform, GPU-accelerated terminal emulator that aims to push
|
||||
the boundaries of what is possible with a terminal emulator by exposing modern,
|
||||
opt-in features that enable CLI tool developers to build more feature rich,
|
||||
interactive applications.
|
||||
|
||||
There are a number of excellent terminal emulator options that exist today.
|
||||
The unique goal of Ghostty is to have a platform for experimenting with modern,
|
||||
optional, non-standards-compliant features to enhance the capabilities of CLI
|
||||
applications. We aim to be the best in this category, and competitive in the
|
||||
rest.
|
||||
|
||||
While aiming for this ambitious goal, Ghostty is a fully standards compliant
|
||||
terminal emulator that aims to remain compatible with all existing shells and
|
||||
software. You can use this as a drop-in replacement for your existing terminal
|
||||
emulator.
|
||||
@@ -0,0 +1,43 @@
|
||||
# FILES
|
||||
|
||||
_\$XDG_CONFIG_HOME/ghostty/config.ghostty_
|
||||
|
||||
: Location of the default configuration file.
|
||||
|
||||
_\$HOME/Library/Application Support/com.mitchellh.ghostty/config.ghostty_
|
||||
|
||||
: **On macOS**, location of the default configuration file. This location takes
|
||||
precedence over the XDG environment locations.
|
||||
|
||||
_\$LOCALAPPDATA/ghostty/config.ghostty_
|
||||
|
||||
: **On Windows**, if _\$XDG_CONFIG_HOME_ is not set, _\$LOCALAPPDATA_ will be searched
|
||||
for configuration files.
|
||||
|
||||
# ENVIRONMENT
|
||||
|
||||
**XDG_CONFIG_HOME**
|
||||
|
||||
: Default location for configuration files.
|
||||
|
||||
**$HOME/Library/Application Support/com.mitchellh.ghostty**
|
||||
|
||||
: **MACOS ONLY** default location for configuration files. This location takes
|
||||
precedence over the XDG environment locations.
|
||||
|
||||
**LOCALAPPDATA**
|
||||
|
||||
: **WINDOWS ONLY:** alternate location to search for configuration files.
|
||||
|
||||
# BUGS
|
||||
|
||||
See GitHub issues: <https://github.com/ghostty-org/ghostty/issues>
|
||||
|
||||
# AUTHOR
|
||||
|
||||
Mitchell Hashimoto <m@mitchellh.com>
|
||||
Ghostty contributors <https://github.com/ghostty-org/ghostty/graphs/contributors>
|
||||
|
||||
# SEE ALSO
|
||||
|
||||
**ghostty(1)**
|
||||
@@ -0,0 +1,122 @@
|
||||
% GHOSTTY(5) Version @@VERSION@@ | Ghostty terminal emulator configuration file
|
||||
|
||||
# NAME
|
||||
|
||||
**ghostty** - Ghostty terminal emulator configuration file
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
To configure Ghostty, you must use a configuration file. GUI-based configuration
|
||||
is on the roadmap but not yet supported. The configuration file must be placed
|
||||
at `$XDG_CONFIG_HOME/ghostty/config.ghostty`, which defaults to `~/.config/ghostty/config.ghostty`
|
||||
if the [XDG environment is not set](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html).
|
||||
|
||||
**If you are using macOS, the configuration file can also be placed at
|
||||
`$HOME/Library/Application Support/com.mitchellh.ghostty/config.ghostty`.** This is the
|
||||
default configuration location for macOS. It will be searched before any of the
|
||||
XDG environment locations listed above.
|
||||
|
||||
The file format is documented below as an example:
|
||||
|
||||
# The syntax is "key = value". The whitespace around the equals doesn't matter.
|
||||
background = 282c34
|
||||
foreground= ffffff
|
||||
|
||||
# Blank lines are ignored!
|
||||
|
||||
keybind = ctrl+z=close_surface
|
||||
keybind = ctrl+d=new_split:right
|
||||
|
||||
# Colors can be changed by setting the 16 colors of `palette`, which each color
|
||||
# being defined as regular and bold.
|
||||
#
|
||||
# black
|
||||
palette = 0=#1d2021
|
||||
palette = 8=#7c6f64
|
||||
# red
|
||||
palette = 1=#cc241d
|
||||
palette = 9=#fb4934
|
||||
# green
|
||||
palette = 2=#98971a
|
||||
palette = 10=#b8bb26
|
||||
# yellow
|
||||
palette = 3=#d79921
|
||||
palette = 11=#fabd2f
|
||||
# blue
|
||||
palette = 4=#458588
|
||||
palette = 12=#83a598
|
||||
# purple
|
||||
palette = 5=#b16286
|
||||
palette = 13=#d3869b
|
||||
# aqua
|
||||
palette = 6=#689d6a
|
||||
palette = 14=#8ec07c
|
||||
# white
|
||||
palette = 7=#a89984
|
||||
palette = 15=#fbf1c7
|
||||
|
||||
You can view all available configuration options and their documentation by
|
||||
executing the command `ghostty +show-config --default --docs`. Note that this will
|
||||
output the full default configuration with docs to stdout, so you may want to
|
||||
pipe that through a pager, an editor, etc.
|
||||
|
||||
Note: You'll see a lot of weird blank configurations like `font-family =`. This
|
||||
is a valid syntax to specify the default behavior (no value). The `+show-config`
|
||||
outputs it so it's clear that key is defaulting and also to have something to
|
||||
attach the doc comment to.
|
||||
|
||||
You can also see and read all available configuration options in the source
|
||||
Config structure. The available keys are the keys verbatim, and their possible
|
||||
values are typically documented in the comments. You also can search for
|
||||
the public config files of many Ghostty users for examples and inspiration.
|
||||
|
||||
## Configuration Errors
|
||||
|
||||
If your configuration file has any errors, Ghostty does its best to ignore
|
||||
them and move on. Configuration errors will be logged.
|
||||
|
||||
## Debugging Configuration
|
||||
|
||||
You can verify that configuration is being properly loaded by looking at the
|
||||
debug output of Ghostty.
|
||||
|
||||
In the debug output, you should see in the first 20 lines or so messages about
|
||||
loading (or not loading) a configuration file, as well as any errors it may have
|
||||
encountered. Configuration errors are also shown in a dedicated window on both
|
||||
macOS and Linux (GTK). Ghostty does not treat configuration errors as fatal and
|
||||
will fall back to default values for erroneous keys.
|
||||
|
||||
You can also view the full configuration Ghostty is loading using `ghostty
|
||||
+show-config` from the command-line. Use the `--help` flag to additional options
|
||||
for that command.
|
||||
|
||||
## Logging
|
||||
|
||||
Ghostty can write logs to a number of destinations. On all platforms, logging to
|
||||
`stderr` is available. Depending on the platform and how Ghostty was launched,
|
||||
logs sent to `stderr` may be stored by the system and made available for later
|
||||
retrieval.
|
||||
|
||||
On Linux if Ghostty is launched by the default `systemd` user service, you can use
|
||||
`journald` to see Ghostty's logs: `journalctl --user --unit app-com.mitchellh.ghostty.service`.
|
||||
|
||||
On macOS logging to the macOS unified log is available and enabled by default.
|
||||
--Use the system `log` CLI to view Ghostty's logs: `sudo log stream --level debug
|
||||
--predicate 'subsystem=="com.mitchellh.ghostty"'`.
|
||||
|
||||
Ghostty's logging can be configured in two ways. The first is by what
|
||||
optimization level Ghostty is compiled with. If Ghostty is compiled with `Debug`
|
||||
optimizations debug logs will be output to `stderr`. If Ghostty is compiled with
|
||||
any other optimization the debug logs will not be output to `stderr`.
|
||||
|
||||
Ghostty also checks the `GHOSTTY_LOG` environment variable. It can be used
|
||||
to control which destinations receive logs. Ghostty currently defines two
|
||||
destinations:
|
||||
|
||||
- `stderr` - logging to `stderr`.
|
||||
- `macos` - logging to macOS's unified log (has no effect on non-macOS platforms).
|
||||
|
||||
Combine values with a comma to enable multiple destinations. Prefix a
|
||||
destination with `no-` to disable it. Enabling and disabling destinations
|
||||
can be done at the same time. Setting `GHOSTTY_LOG` to `true` will enable all
|
||||
destinations. Setting `GHOSTTY_LOG` to `false` will disable all destinations.
|
||||
@@ -0,0 +1,10 @@
|
||||
//! Sublime syntax (also used by `bat`) (port of `extra/sublime.zig`).
|
||||
|
||||
use crate::chunks_sublime::{C0, C1};
|
||||
use crate::config_fields;
|
||||
|
||||
#[must_use]
|
||||
pub fn syntax() -> String {
|
||||
let keywords: Vec<_> = config_fields().iter().map(|f| f.name).collect();
|
||||
format!("{C0}{}{C1}", keywords.join("|"))
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
//! Vim/Neovim plugin files (port of `extra/vim.zig`).
|
||||
|
||||
use std::fmt::Write as _;
|
||||
|
||||
use crate::chunks_vim::{C0, C1, C2, C3, C4};
|
||||
use crate::config_fields;
|
||||
|
||||
pub const FTDETECT: &str = C0;
|
||||
pub const FTPLUGIN: &str = C1;
|
||||
pub const COMPILER: &str = C2;
|
||||
|
||||
#[must_use]
|
||||
pub fn syntax() -> String {
|
||||
let mut w = String::from(C3);
|
||||
for f in config_fields() {
|
||||
let _ = write!(w, "\n\t\\ {}", f.name);
|
||||
}
|
||||
w.push_str(C4);
|
||||
w
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
//! Zsh completions (port of `extra/zsh.zig`).
|
||||
|
||||
use std::fmt::Write as _;
|
||||
|
||||
use ghostty_config::cli_args::FieldKind;
|
||||
|
||||
use crate::chunks_zsh::{C0, C1, C2, C3};
|
||||
use crate::{actions, config_fields, join_names, join_packed};
|
||||
|
||||
const EQUALS_REQUIRED: &str = "=-:::";
|
||||
|
||||
#[must_use]
|
||||
pub fn completions() -> String {
|
||||
let mut w = String::new();
|
||||
w.push_str(C0);
|
||||
w.push_str("_config() {\n");
|
||||
w.push_str(" _arguments \\\n");
|
||||
w.push_str(" \"--help\" \\\n");
|
||||
w.push_str(" \"--version\" \\\n");
|
||||
for f in config_fields() {
|
||||
let _ = write!(w, " \"--{}", f.name);
|
||||
if f.name.starts_with("font-family") {
|
||||
let _ = write!(w, "{EQUALS_REQUIRED}_fonts");
|
||||
} else if f.name == "theme" {
|
||||
let _ = write!(w, "{EQUALS_REQUIRED}_themes");
|
||||
} else if f.name == "working-directory" {
|
||||
let _ = write!(w, "{EQUALS_REQUIRED}{{_files -/}}");
|
||||
} else if f.kind == FieldKind::RepeatablePath {
|
||||
let _ = write!(w, "{EQUALS_REQUIRED}_files");
|
||||
} else {
|
||||
match f.kind {
|
||||
FieldKind::Bool => {}
|
||||
FieldKind::Enum(names) => {
|
||||
let _ = write!(w, "{EQUALS_REQUIRED}({})", join_names(names));
|
||||
}
|
||||
FieldKind::Packed(names) => {
|
||||
let _ = write!(w, "{EQUALS_REQUIRED}({})", join_packed(names));
|
||||
}
|
||||
_ => {
|
||||
let _ = write!(w, "{EQUALS_REQUIRED}( )");
|
||||
}
|
||||
}
|
||||
}
|
||||
w.push_str("\" \\\n");
|
||||
}
|
||||
w.push_str("\n}\n\n");
|
||||
|
||||
w.push_str(C1);
|
||||
let padding = " ";
|
||||
for a in actions() {
|
||||
let _ = writeln!(w, "{padding}'+{}'", a.name());
|
||||
}
|
||||
w.push_str(C2);
|
||||
|
||||
for a in actions() {
|
||||
let opts = a.option_fields();
|
||||
if opts.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let _ = writeln!(w, "{padding}(+{})", a.name());
|
||||
let _ = writeln!(w, "{padding} _arguments \\");
|
||||
for (name, kind) in &opts {
|
||||
let _ = write!(w, "{padding} '--{name}");
|
||||
match kind {
|
||||
FieldKind::Bool => {}
|
||||
FieldKind::Enum(names) => {
|
||||
let _ = write!(w, "{EQUALS_REQUIRED}({})", join_names(names));
|
||||
}
|
||||
FieldKind::OptionalEnum(names) => {
|
||||
let _ = write!(w, "{EQUALS_REQUIRED}({})", join_names(names));
|
||||
}
|
||||
FieldKind::OptionalBool | FieldKind::OptionalOther => {
|
||||
w.push_str(EQUALS_REQUIRED);
|
||||
if *name == "config-file" {
|
||||
w.push_str("_files");
|
||||
} else {
|
||||
w.push_str("( )");
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
w.push_str(EQUALS_REQUIRED);
|
||||
if *name == "config-file" {
|
||||
w.push_str("_files");
|
||||
} else {
|
||||
w.push_str("( )");
|
||||
}
|
||||
}
|
||||
}
|
||||
w.push_str("' \\\n");
|
||||
}
|
||||
let _ = writeln!(w, "{padding};;");
|
||||
}
|
||||
w.push_str(C3);
|
||||
w
|
||||
}
|
||||
+138
-1
@@ -1,7 +1,128 @@
|
||||
//! Captures the compiler version for `+version` (Zig: `builtin.zig_version_string`).
|
||||
//! Build metadata for `+version` and the generated docs.
|
||||
//!
|
||||
//! * the compiler version (Zig: `builtin.zig_version_string`)
|
||||
//! * the app version, release channel and commit (Zig: `build/Config.zig`
|
||||
//! version detection + `build/GitVersion.zig`)
|
||||
//! * the build mode (Zig: `builtin.mode`)
|
||||
//!
|
||||
//! The version can be overridden with `GHOSTTY_VERSION_STRING` (Zig:
|
||||
//! `-Dversion-string`), which must be a semantic version.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
fn run(cmd: &str, args: &[&str], dir: &Path) -> Option<String> {
|
||||
let out = Command::new(cmd)
|
||||
.args(args)
|
||||
.current_dir(dir)
|
||||
.output()
|
||||
.ok()?;
|
||||
out.status
|
||||
.success()
|
||||
.then(|| String::from_utf8_lossy(&out.stdout).trim_end().to_string())
|
||||
}
|
||||
|
||||
/// The `app-version` from `[workspace.metadata.ghostty]` of the workspace
|
||||
/// manifest (two levels above this crate).
|
||||
fn app_version(manifest_dir: &Path) -> (u64, u64, u64) {
|
||||
let root = manifest_dir.join("../../Cargo.toml");
|
||||
println!("cargo:rerun-if-changed={}", root.display());
|
||||
let text = std::fs::read_to_string(root).unwrap_or_default();
|
||||
let v = text
|
||||
.lines()
|
||||
.find_map(|l| l.trim().strip_prefix("app-version"))
|
||||
.and_then(|l| l.split('"').nth(1))
|
||||
.unwrap_or("1.3.1");
|
||||
let mut it = v.split('.').map(|p| p.parse::<u64>().unwrap_or(0));
|
||||
(
|
||||
it.next().unwrap_or(1),
|
||||
it.next().unwrap_or(3),
|
||||
it.next().unwrap_or(1),
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns `(version_string, commit)` mirroring `Config.zig`.
|
||||
fn detect_version(manifest_dir: &Path) -> (String, Option<String>) {
|
||||
println!("cargo:rerun-if-env-changed=GHOSTTY_VERSION_STRING");
|
||||
if let Ok(v) = std::env::var("GHOSTTY_VERSION_STRING") {
|
||||
let commit = v.split_once('+').map(|(_, b)| b.to_string());
|
||||
return (v, commit);
|
||||
}
|
||||
|
||||
let (maj, min, pat) = app_version(manifest_dir);
|
||||
let base = format!("{maj}.{min}.{pat}");
|
||||
|
||||
// Git unavailable or not a repository: unknown dev version.
|
||||
let Some(branch) = run("git", &["rev-parse", "--abbrev-ref", "HEAD"], manifest_dir) else {
|
||||
return (format!("{base}-dev+0000000"), Some("0000000".into()));
|
||||
};
|
||||
if let Some(git_dir) = run("git", &["rev-parse", "--absolute-git-dir"], manifest_dir) {
|
||||
let git_dir = PathBuf::from(git_dir);
|
||||
println!("cargo:rerun-if-changed={}", git_dir.join("HEAD").display());
|
||||
println!("cargo:rerun-if-changed={}", git_dir.join("refs").display());
|
||||
println!(
|
||||
"cargo:rerun-if-changed={}",
|
||||
git_dir.join("packed-refs").display()
|
||||
);
|
||||
}
|
||||
let hash = run(
|
||||
"git",
|
||||
&[
|
||||
"-c",
|
||||
"log.showSignature=false",
|
||||
"log",
|
||||
"--pretty=format:%h",
|
||||
"-n",
|
||||
"1",
|
||||
],
|
||||
manifest_dir,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
let tag = run(
|
||||
"git",
|
||||
&["describe", "--exact-match", "--tags"],
|
||||
manifest_dir,
|
||||
)
|
||||
.filter(|t| !t.is_empty());
|
||||
|
||||
if let Some(tag) = tag {
|
||||
// Tip releases behave just like any other pre-release so we skip.
|
||||
if tag != "tip" {
|
||||
assert!(
|
||||
tag == format!("v{base}"),
|
||||
"tagged releases must be in vX.Y.Z format matching the workspace app-version"
|
||||
);
|
||||
return (base, None);
|
||||
}
|
||||
}
|
||||
|
||||
// Slashes in branch names are replaced like in `GitVersion.zig`.
|
||||
let branch = branch.replace('/', "-");
|
||||
(format!("{base}-{branch}+{hash}"), Some(hash))
|
||||
}
|
||||
|
||||
/// The Zig optimize-mode name for the Cargo profile being built.
|
||||
fn build_mode() -> &'static str {
|
||||
// The profile name is the directory below `target/` that holds OUT_DIR.
|
||||
let out = std::env::var("OUT_DIR").unwrap_or_default();
|
||||
let profile = Path::new(&out)
|
||||
.ancestors()
|
||||
.skip_while(|p| p.file_name().is_none_or(|n| n != "build"))
|
||||
.nth(1)
|
||||
.and_then(|p| p.file_name())
|
||||
.map(|n| n.to_string_lossy().into_owned())
|
||||
.unwrap_or_default();
|
||||
let debug_assertions = std::env::var_os("CARGO_CFG_DEBUG_ASSERTIONS").is_some();
|
||||
let opt = std::env::var("OPT_LEVEL").unwrap_or_default();
|
||||
if profile == "production" || !debug_assertions {
|
||||
".ReleaseFast"
|
||||
} else if opt == "0" {
|
||||
".Debug"
|
||||
} else {
|
||||
".ReleaseSafe"
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rerun-if-env-changed=RUSTC");
|
||||
@@ -15,4 +136,20 @@ fn main() {
|
||||
.and_then(|s| s.split_whitespace().nth(1).map(str::to_owned))
|
||||
.unwrap_or_else(|| "unknown".into());
|
||||
println!("cargo:rustc-env=GHOSTTY_RUSTC_VERSION={version}");
|
||||
|
||||
let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap_or_default());
|
||||
let (version_string, commit) = detect_version(&manifest_dir);
|
||||
// Release channel: stable unless the version has a pre-release part.
|
||||
let channel = if version_string.contains('-') {
|
||||
"tip"
|
||||
} else {
|
||||
"stable"
|
||||
};
|
||||
println!("cargo:rustc-env=GHOSTTY_VERSION_STRING={version_string}");
|
||||
println!(
|
||||
"cargo:rustc-env=GHOSTTY_COMMIT={}",
|
||||
commit.unwrap_or_default()
|
||||
);
|
||||
println!("cargo:rustc-env=GHOSTTY_RELEASE_CHANNEL={channel}");
|
||||
println!("cargo:rustc-env=GHOSTTY_BUILD_MODE={}", build_mode());
|
||||
}
|
||||
Reference in new issue
Block a user