From 864ef82acb233d144b6fbee1ee1746be79a38ccc Mon Sep 17 00:00:00 2001 From: ddidderr Date: Sun, 11 Oct 2026 21:41:01 +0200 Subject: [PATCH] chore: scaffold Cargo workspace for the Ghostty rewrite Start of the full Rust rewrite of Ghostty 1.3.1 (reference source in ../ghostty). Sets up a workspace whose crates mirror Ghostty's Zig modules (vt, unicode, datastruct, os, input, config, terminal, font, renderer, termio, core, apprt-gtk) so porting work can proceed in parallel per crate. Also adds: - docs/rewrite/{ARCHITECTURE,PLAN,STATUS,AGENT_GUIDE}.md: crate map, core design decisions (safe Rust, slab-based PageList, logical capacity accounting), phased plan, progress tracker, and porting rules. - Workspace clippy config relaxing pedantic lints that are pure noise for low-level terminal code (casts, long functions). - Shared vt types needed by several work packages up front: kitty keyboard flags (kitty/key.rs) and the CSI separator bitset (SepList). Test Plan: - cargo test -p ghostty-vt (3 tests pass) - cargo clippy -p ghostty-vt --all-targets -- -D warnings --- Cargo.lock | 96 +++++++++++++++ Cargo.toml | 66 +++++++++- crates/ghostty-apprt-gtk/Cargo.toml | 13 ++ crates/ghostty-apprt-gtk/src/lib.rs | 1 + crates/ghostty-config/Cargo.toml | 13 ++ crates/ghostty-config/src/lib.rs | 1 + crates/ghostty-core/Cargo.toml | 18 +++ crates/ghostty-core/src/lib.rs | 1 + crates/ghostty-datastruct/Cargo.toml | 11 ++ crates/ghostty-datastruct/src/lib.rs | 1 + crates/ghostty-font/Cargo.toml | 13 ++ crates/ghostty-font/src/lib.rs | 1 + crates/ghostty-input/Cargo.toml | 11 ++ crates/ghostty-input/src/lib.rs | 1 + crates/ghostty-os/Cargo.toml | 11 ++ crates/ghostty-os/src/lib.rs | 1 + crates/ghostty-renderer/Cargo.toml | 13 ++ crates/ghostty-renderer/src/lib.rs | 1 + crates/ghostty-terminal/Cargo.toml | 13 ++ crates/ghostty-terminal/src/lib.rs | 1 + crates/ghostty-termio/Cargo.toml | 13 ++ crates/ghostty-termio/src/lib.rs | 1 + crates/ghostty-unicode/Cargo.toml | 11 ++ crates/ghostty-unicode/src/lib.rs | 1 + crates/ghostty-vt/Cargo.toml | 11 ++ crates/ghostty-vt/src/kitty/key.rs | 173 +++++++++++++++++++++++++++ crates/ghostty-vt/src/kitty/mod.rs | 3 + crates/ghostty-vt/src/lib.rs | 5 + crates/ghostty-vt/src/parser.rs | 52 ++++++++ docs/rewrite/AGENT_GUIDE.md | 56 +++++++++ docs/rewrite/ARCHITECTURE.md | 76 ++++++++++++ docs/rewrite/PLAN.md | 43 +++++++ docs/rewrite/STATUS.md | 17 +++ scripts/test-progress.sh | 14 +++ 34 files changed, 759 insertions(+), 4 deletions(-) create mode 100644 crates/ghostty-apprt-gtk/Cargo.toml create mode 100644 crates/ghostty-apprt-gtk/src/lib.rs create mode 100644 crates/ghostty-config/Cargo.toml create mode 100644 crates/ghostty-config/src/lib.rs create mode 100644 crates/ghostty-core/Cargo.toml create mode 100644 crates/ghostty-core/src/lib.rs create mode 100644 crates/ghostty-datastruct/Cargo.toml create mode 100644 crates/ghostty-datastruct/src/lib.rs create mode 100644 crates/ghostty-font/Cargo.toml create mode 100644 crates/ghostty-font/src/lib.rs create mode 100644 crates/ghostty-input/Cargo.toml create mode 100644 crates/ghostty-input/src/lib.rs create mode 100644 crates/ghostty-os/Cargo.toml create mode 100644 crates/ghostty-os/src/lib.rs create mode 100644 crates/ghostty-renderer/Cargo.toml create mode 100644 crates/ghostty-renderer/src/lib.rs create mode 100644 crates/ghostty-terminal/Cargo.toml create mode 100644 crates/ghostty-terminal/src/lib.rs create mode 100644 crates/ghostty-termio/Cargo.toml create mode 100644 crates/ghostty-termio/src/lib.rs create mode 100644 crates/ghostty-unicode/Cargo.toml create mode 100644 crates/ghostty-unicode/src/lib.rs create mode 100644 crates/ghostty-vt/Cargo.toml create mode 100644 crates/ghostty-vt/src/kitty/key.rs create mode 100644 crates/ghostty-vt/src/kitty/mod.rs create mode 100644 crates/ghostty-vt/src/lib.rs create mode 100644 crates/ghostty-vt/src/parser.rs create mode 100644 docs/rewrite/AGENT_GUIDE.md create mode 100644 docs/rewrite/ARCHITECTURE.md create mode 100644 docs/rewrite/PLAN.md create mode 100644 docs/rewrite/STATUS.md create mode 100755 scripts/test-progress.sh diff --git a/Cargo.lock b/Cargo.lock index 5767671..2a1b859 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,102 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ghostty-apprt-gtk" +version = "0.1.0" +dependencies = [ + "ghostty-config", + "ghostty-core", + "ghostty-input", +] + +[[package]] +name = "ghostty-config" +version = "0.1.0" +dependencies = [ + "ghostty-input", + "ghostty-os", + "ghostty-vt", +] + +[[package]] +name = "ghostty-core" +version = "0.1.0" +dependencies = [ + "ghostty-config", + "ghostty-datastruct", + "ghostty-font", + "ghostty-input", + "ghostty-os", + "ghostty-renderer", + "ghostty-terminal", + "ghostty-termio", +] + +[[package]] +name = "ghostty-datastruct" +version = "0.1.0" + +[[package]] +name = "ghostty-font" +version = "0.1.0" +dependencies = [ + "ghostty-config", + "ghostty-terminal", + "ghostty-unicode", +] + +[[package]] +name = "ghostty-input" +version = "0.1.0" +dependencies = [ + "ghostty-vt", +] + +[[package]] +name = "ghostty-os" +version = "0.1.0" + +[[package]] +name = "ghostty-renderer" +version = "0.1.0" +dependencies = [ + "ghostty-config", + "ghostty-font", + "ghostty-terminal", +] + [[package]] name = "ghostty-rs" version = "0.1.0" +dependencies = [ + "ghostty-apprt-gtk", + "ghostty-config", + "ghostty-core", +] + +[[package]] +name = "ghostty-terminal" +version = "0.1.0" +dependencies = [ + "ghostty-datastruct", + "ghostty-unicode", + "ghostty-vt", +] + +[[package]] +name = "ghostty-termio" +version = "0.1.0" +dependencies = [ + "ghostty-config", + "ghostty-os", + "ghostty-terminal", +] + +[[package]] +name = "ghostty-unicode" +version = "0.1.0" + +[[package]] +name = "ghostty-vt" +version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 1de778d..0822470 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,16 +2,74 @@ name = "ghostty-rs" version = "0.1.0" edition = "2024" +description = "A Rust rewrite of the Ghostty terminal emulator" + +[[bin]] +name = "ghostty" +path = "src/main.rs" [dependencies] +ghostty-core.workspace = true +ghostty-config.workspace = true +ghostty-apprt-gtk.workspace = true -[lints.clippy] +[lints] +workspace = true + +[workspace] +resolver = "3" +members = [".", "crates/*"] + +[workspace.package] +version = "0.1.0" +edition = "2024" +license = "MIT" + +[workspace.dependencies] +ghostty-vt = { path = "crates/ghostty-vt" } +ghostty-unicode = { path = "crates/ghostty-unicode" } +ghostty-terminal = { path = "crates/ghostty-terminal" } +ghostty-input = { path = "crates/ghostty-input" } +ghostty-config = { path = "crates/ghostty-config" } +ghostty-font = { path = "crates/ghostty-font" } +ghostty-renderer = { path = "crates/ghostty-renderer" } +ghostty-termio = { path = "crates/ghostty-termio" } +ghostty-os = { path = "crates/ghostty-os" } +ghostty-datastruct = { path = "crates/ghostty-datastruct" } +ghostty-core = { path = "crates/ghostty-core" } +ghostty-apprt-gtk = { path = "crates/ghostty-apprt-gtk" } + +[workspace.lints.rust] +unsafe_code = "forbid" + +[workspace.lints.clippy] pedantic = { level = "warn", priority = -1 } todo = "warn" unwrap_used = "warn" - -[lints.rust] -unsafe_code = "forbid" +# Porting a low-level terminal emulator involves a lot of intentional +# numeric casts and long state-machine functions; these pedantic lints are +# noise for this codebase. +cast_possible_truncation = "allow" +cast_possible_wrap = "allow" +cast_sign_loss = "allow" +cast_precision_loss = "allow" +cast_lossless = "allow" +module_name_repetitions = "allow" +too_many_lines = "allow" +missing_errors_doc = "allow" +missing_panics_doc = "allow" +similar_names = "allow" +struct_excessive_bools = "allow" +fn_params_excessive_bools = "allow" +match_same_arms = "allow" +unreadable_literal = "allow" +many_single_char_names = "allow" +doc_markdown = "allow" +must_use_candidate = "allow" +return_self_not_must_use = "allow" +used_underscore_binding = "allow" +large_stack_arrays = "allow" +inline_always = "allow" [profile.release] debug = true diff --git a/crates/ghostty-apprt-gtk/Cargo.toml b/crates/ghostty-apprt-gtk/Cargo.toml new file mode 100644 index 0000000..14f8b80 --- /dev/null +++ b/crates/ghostty-apprt-gtk/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "ghostty-apprt-gtk" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +ghostty-core.workspace = true +ghostty-config.workspace = true +ghostty-input.workspace = true + +[lints] +workspace = true diff --git a/crates/ghostty-apprt-gtk/src/lib.rs b/crates/ghostty-apprt-gtk/src/lib.rs new file mode 100644 index 0000000..66e15bd --- /dev/null +++ b/crates/ghostty-apprt-gtk/src/lib.rs @@ -0,0 +1 @@ +//! ghostty-apprt-gtk: see docs/rewrite/ARCHITECTURE.md for the module map. diff --git a/crates/ghostty-config/Cargo.toml b/crates/ghostty-config/Cargo.toml new file mode 100644 index 0000000..bf63bcd --- /dev/null +++ b/crates/ghostty-config/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "ghostty-config" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +ghostty-input.workspace = true +ghostty-os.workspace = true +ghostty-vt.workspace = true + +[lints] +workspace = true diff --git a/crates/ghostty-config/src/lib.rs b/crates/ghostty-config/src/lib.rs new file mode 100644 index 0000000..0fb79b7 --- /dev/null +++ b/crates/ghostty-config/src/lib.rs @@ -0,0 +1 @@ +//! ghostty-config: see docs/rewrite/ARCHITECTURE.md for the module map. diff --git a/crates/ghostty-core/Cargo.toml b/crates/ghostty-core/Cargo.toml new file mode 100644 index 0000000..8b506af --- /dev/null +++ b/crates/ghostty-core/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "ghostty-core" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +ghostty-config.workspace = true +ghostty-terminal.workspace = true +ghostty-input.workspace = true +ghostty-font.workspace = true +ghostty-renderer.workspace = true +ghostty-termio.workspace = true +ghostty-os.workspace = true +ghostty-datastruct.workspace = true + +[lints] +workspace = true diff --git a/crates/ghostty-core/src/lib.rs b/crates/ghostty-core/src/lib.rs new file mode 100644 index 0000000..f4a6c89 --- /dev/null +++ b/crates/ghostty-core/src/lib.rs @@ -0,0 +1 @@ +//! ghostty-core: see docs/rewrite/ARCHITECTURE.md for the module map. diff --git a/crates/ghostty-datastruct/Cargo.toml b/crates/ghostty-datastruct/Cargo.toml new file mode 100644 index 0000000..b4e3737 --- /dev/null +++ b/crates/ghostty-datastruct/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ghostty-datastruct" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] + + +[lints] +workspace = true diff --git a/crates/ghostty-datastruct/src/lib.rs b/crates/ghostty-datastruct/src/lib.rs new file mode 100644 index 0000000..b2092d8 --- /dev/null +++ b/crates/ghostty-datastruct/src/lib.rs @@ -0,0 +1 @@ +//! ghostty-datastruct: see docs/rewrite/ARCHITECTURE.md for the module map. diff --git a/crates/ghostty-font/Cargo.toml b/crates/ghostty-font/Cargo.toml new file mode 100644 index 0000000..4597757 --- /dev/null +++ b/crates/ghostty-font/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "ghostty-font" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +ghostty-config.workspace = true +ghostty-unicode.workspace = true +ghostty-terminal.workspace = true + +[lints] +workspace = true diff --git a/crates/ghostty-font/src/lib.rs b/crates/ghostty-font/src/lib.rs new file mode 100644 index 0000000..fe2d30b --- /dev/null +++ b/crates/ghostty-font/src/lib.rs @@ -0,0 +1 @@ +//! ghostty-font: see docs/rewrite/ARCHITECTURE.md for the module map. diff --git a/crates/ghostty-input/Cargo.toml b/crates/ghostty-input/Cargo.toml new file mode 100644 index 0000000..ffb0c18 --- /dev/null +++ b/crates/ghostty-input/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ghostty-input" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +ghostty-vt.workspace = true + +[lints] +workspace = true diff --git a/crates/ghostty-input/src/lib.rs b/crates/ghostty-input/src/lib.rs new file mode 100644 index 0000000..9cf96bc --- /dev/null +++ b/crates/ghostty-input/src/lib.rs @@ -0,0 +1 @@ +//! ghostty-input: see docs/rewrite/ARCHITECTURE.md for the module map. diff --git a/crates/ghostty-os/Cargo.toml b/crates/ghostty-os/Cargo.toml new file mode 100644 index 0000000..ef24850 --- /dev/null +++ b/crates/ghostty-os/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ghostty-os" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] + + +[lints] +workspace = true diff --git a/crates/ghostty-os/src/lib.rs b/crates/ghostty-os/src/lib.rs new file mode 100644 index 0000000..bcb1e4e --- /dev/null +++ b/crates/ghostty-os/src/lib.rs @@ -0,0 +1 @@ +//! ghostty-os: see docs/rewrite/ARCHITECTURE.md for the module map. diff --git a/crates/ghostty-renderer/Cargo.toml b/crates/ghostty-renderer/Cargo.toml new file mode 100644 index 0000000..2ff64f9 --- /dev/null +++ b/crates/ghostty-renderer/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "ghostty-renderer" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +ghostty-config.workspace = true +ghostty-font.workspace = true +ghostty-terminal.workspace = true + +[lints] +workspace = true diff --git a/crates/ghostty-renderer/src/lib.rs b/crates/ghostty-renderer/src/lib.rs new file mode 100644 index 0000000..6b8e3e8 --- /dev/null +++ b/crates/ghostty-renderer/src/lib.rs @@ -0,0 +1 @@ +//! ghostty-renderer: see docs/rewrite/ARCHITECTURE.md for the module map. diff --git a/crates/ghostty-terminal/Cargo.toml b/crates/ghostty-terminal/Cargo.toml new file mode 100644 index 0000000..621a33e --- /dev/null +++ b/crates/ghostty-terminal/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "ghostty-terminal" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +ghostty-vt.workspace = true +ghostty-unicode.workspace = true +ghostty-datastruct.workspace = true + +[lints] +workspace = true diff --git a/crates/ghostty-terminal/src/lib.rs b/crates/ghostty-terminal/src/lib.rs new file mode 100644 index 0000000..a740c78 --- /dev/null +++ b/crates/ghostty-terminal/src/lib.rs @@ -0,0 +1 @@ +//! ghostty-terminal: see docs/rewrite/ARCHITECTURE.md for the module map. diff --git a/crates/ghostty-termio/Cargo.toml b/crates/ghostty-termio/Cargo.toml new file mode 100644 index 0000000..c73d411 --- /dev/null +++ b/crates/ghostty-termio/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "ghostty-termio" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] +ghostty-config.workspace = true +ghostty-terminal.workspace = true +ghostty-os.workspace = true + +[lints] +workspace = true diff --git a/crates/ghostty-termio/src/lib.rs b/crates/ghostty-termio/src/lib.rs new file mode 100644 index 0000000..5a45ed8 --- /dev/null +++ b/crates/ghostty-termio/src/lib.rs @@ -0,0 +1 @@ +//! ghostty-termio: see docs/rewrite/ARCHITECTURE.md for the module map. diff --git a/crates/ghostty-unicode/Cargo.toml b/crates/ghostty-unicode/Cargo.toml new file mode 100644 index 0000000..d36d8ce --- /dev/null +++ b/crates/ghostty-unicode/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ghostty-unicode" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] + + +[lints] +workspace = true diff --git a/crates/ghostty-unicode/src/lib.rs b/crates/ghostty-unicode/src/lib.rs new file mode 100644 index 0000000..9761611 --- /dev/null +++ b/crates/ghostty-unicode/src/lib.rs @@ -0,0 +1 @@ +//! ghostty-unicode: see docs/rewrite/ARCHITECTURE.md for the module map. diff --git a/crates/ghostty-vt/Cargo.toml b/crates/ghostty-vt/Cargo.toml new file mode 100644 index 0000000..8fcf73d --- /dev/null +++ b/crates/ghostty-vt/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "ghostty-vt" +version.workspace = true +edition.workspace = true +license.workspace = true + +[dependencies] + + +[lints] +workspace = true diff --git a/crates/ghostty-vt/src/kitty/key.rs b/crates/ghostty-vt/src/kitty/key.rs new file mode 100644 index 0000000..1ad94da --- /dev/null +++ b/crates/ghostty-vt/src/kitty/key.rs @@ -0,0 +1,173 @@ +//! Kitty keyboard protocol support (ZIG/src/terminal/kitty/key.zig). + +/// Stack for the key flags. This implements the push/pop behavior +/// of the CSI > u and CSI < u sequences. We implement the stack as +/// fixed size to avoid heap allocation. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] +pub struct FlagStack { + pub flags: [Flags; FlagStack::LEN], + /// Index into `flags`; wraps modulo `LEN` (a `u3` in Zig). + pub idx: u8, +} + +impl FlagStack { + pub const LEN: usize = 8; + + /// Return the current stack value + pub fn current(&self) -> Flags { + self.flags[self.idx as usize] + } + + /// Perform the "set" operation as described in the spec for + /// the CSI = u sequence. + pub fn set(&mut self, mode: SetMode, v: Flags) { + let cur = &mut self.flags[self.idx as usize]; + *cur = match mode { + SetMode::Set => v, + SetMode::Or => Flags::from_int(cur.int() | v.int()), + SetMode::Not => Flags::from_int(cur.int() & !v.int()), + }; + } + + /// Push a new set of flags onto the stack. If the stack is full + /// then the oldest entry is evicted. + pub fn push(&mut self, flags: Flags) { + self.idx = (self.idx + 1) % Self::LEN as u8; + self.flags[self.idx as usize] = flags; + } + + /// Pop `n` entries from the stack. This will just wrap around + /// if `n` is greater than the amount in the stack. + pub fn pop(&mut self, n: usize) { + // If n is more than our length then we just reset the stack. + // This also avoids a DoS vector where a malicious client + // could send a huge number of pop commands to waste cpu. + if n >= Self::LEN { + self.idx = 0; + self.flags = [Flags::DISABLED; Self::LEN]; + return; + } + + for _ in 0..n { + self.flags[self.idx as usize] = Flags::DISABLED; + self.idx = (self.idx + Self::LEN as u8 - 1) % Self::LEN as u8; + } + } +} + +/// The possible flags for the Kitty keyboard protocol. +/// Bit order matches the Zig `packed struct(u5)` (disambiguate = bit 0). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)] +#[allow(clippy::struct_excessive_bools)] +pub struct Flags { + pub disambiguate: bool, + pub report_events: bool, + pub report_alternates: bool, + pub report_all: bool, + pub report_associated: bool, +} + +impl Flags { + /// Kitty keyboard protocol disabled (all flags off). + pub const DISABLED: Flags = Flags { + disambiguate: false, + report_events: false, + report_alternates: false, + report_all: false, + report_associated: false, + }; + + /// Sets all modes on. + pub const TRUE: Flags = Flags { + disambiguate: true, + report_events: true, + report_alternates: true, + report_all: true, + report_associated: true, + }; + + /// The flags as a 5-bit integer. + pub fn int(self) -> u8 { + u8::from(self.disambiguate) + | u8::from(self.report_events) << 1 + | u8::from(self.report_alternates) << 2 + | u8::from(self.report_all) << 3 + | u8::from(self.report_associated) << 4 + } + + /// Build flags from the low 5 bits of `v`. + pub fn from_int(v: u8) -> Flags { + Flags { + disambiguate: v & 1 != 0, + report_events: v & 2 != 0, + report_alternates: v & 4 != 0, + report_all: v & 8 != 0, + report_associated: v & 16 != 0, + } + } +} + +/// The possible modes for setting the Kitty keyboard protocol flags. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[repr(u8)] +pub enum SetMode { + Set = 1, + Or = 2, + Not = 3, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn flag_stack_overflow() { + let mut stack = FlagStack { + idx: (FlagStack::LEN - 1) as u8, + ..FlagStack::default() + }; + stack.push(Flags::DISABLED); + assert_eq!(stack.idx, 0); + stack.pop(1); + assert_eq!(stack.idx as usize, FlagStack::LEN - 1); + } + + #[test] + fn flags_int() { + assert_eq!(Flags::DISABLED.int(), 0b0_0000); + assert_eq!( + Flags { + disambiguate: true, + ..Flags::DISABLED + } + .int(), + 0b0_0001 + ); + assert_eq!( + Flags { + report_events: true, + ..Flags::DISABLED + } + .int(), + 0b0_0010 + ); + assert_eq!(Flags::TRUE.int(), 0b1_1111); + assert_eq!(Flags::from_int(0b1_0100).int(), 0b1_0100); + } + + #[test] + fn flag_stack_set_push_pop() { + let mut stack = FlagStack::default(); + stack.set(SetMode::Set, Flags::from_int(1)); + stack.set(SetMode::Or, Flags::from_int(4)); + assert_eq!(stack.current().int(), 5); + stack.set(SetMode::Not, Flags::from_int(1)); + assert_eq!(stack.current().int(), 4); + stack.push(Flags::from_int(2)); + assert_eq!(stack.current().int(), 2); + stack.pop(1); + assert_eq!(stack.current().int(), 4); + stack.pop(100); + assert_eq!(stack, FlagStack::default()); + } +} diff --git a/crates/ghostty-vt/src/kitty/mod.rs b/crates/ghostty-vt/src/kitty/mod.rs new file mode 100644 index 0000000..57a31d8 --- /dev/null +++ b/crates/ghostty-vt/src/kitty/mod.rs @@ -0,0 +1,3 @@ +//! Kitty protocol types (ZIG/src/terminal/kitty.zig). + +pub mod key; diff --git a/crates/ghostty-vt/src/lib.rs b/crates/ghostty-vt/src/lib.rs new file mode 100644 index 0000000..96ab5d0 --- /dev/null +++ b/crates/ghostty-vt/src/lib.rs @@ -0,0 +1,5 @@ +//! ghostty-vt: VT parsing layer of Ghostty (parser, OSC/CSI/DCS/APC, SGR, +//! colors, modes, stream). See docs/rewrite/ARCHITECTURE.md. + +pub mod kitty; +pub mod parser; diff --git a/crates/ghostty-vt/src/parser.rs b/crates/ghostty-vt/src/parser.rs new file mode 100644 index 0000000..26d1904 --- /dev/null +++ b/crates/ghostty-vt/src/parser.rs @@ -0,0 +1,52 @@ +//! VT-series parser for escape and control sequences +//! (ZIG/src/terminal/Parser.zig). +//! +//! NOTE(W1): this file is owned by work package W1. `MAX_PARAMS` and +//! `SepList` were pre-created by the orchestrator because `sgr` (W2) +//! depends on them; keep their API stable. + +/// Maximum number of CSI parameters. This is a const in Ghostty so we +/// match it. +pub const MAX_PARAMS: usize = 24; + +/// Bitset of length `MAX_PARAMS` marking which parameters were followed by +/// a colon (`:`) separator rather than a semicolon (`;`). Bit `i` set means +/// the separator *after* param `i` was a colon. +/// Equivalent to Zig's `std.StaticBitSet(MAX_PARAMS)`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)] +pub struct SepList(u32); + +impl SepList { + /// An empty set (all separators are semicolons). + pub const fn init_empty() -> Self { + Self(0) + } + + /// Whether bit `i` is set (separator after param `i` is a colon). + pub fn is_set(&self, i: usize) -> bool { + debug_assert!(i < MAX_PARAMS); + self.0 & (1 << i) != 0 + } + + /// Set bit `i`. + pub fn set(&mut self, i: usize) { + debug_assert!(i < MAX_PARAMS); + self.0 |= 1 << i; + } + + /// Clear bit `i`. + pub fn unset(&mut self, i: usize) { + debug_assert!(i < MAX_PARAMS); + self.0 &= !(1 << i); + } + + /// Number of set bits. + pub fn count(&self) -> usize { + self.0.count_ones() as usize + } + + /// Raw mask. + pub fn mask(&self) -> u32 { + self.0 + } +} diff --git a/docs/rewrite/AGENT_GUIDE.md b/docs/rewrite/AGENT_GUIDE.md new file mode 100644 index 0000000..927af5d --- /dev/null +++ b/docs/rewrite/AGENT_GUIDE.md @@ -0,0 +1,56 @@ +# Guide for porting agents + +You are porting part of Ghostty 1.3.1 (Zig) to Rust. Read +`docs/rewrite/ARCHITECTURE.md` first. + +- Zig source (read-only reference): `/pantheon/pfs/git/rust/ghostty-rewrite/ghostty/src` +- Rust workspace: `/pantheon/pfs/git/rust/ghostty-rewrite/ghostty-rs` + +## Rules + +1. **Stay in your lane.** Only edit the crate/modules assigned to you. If you + need something from another crate that doesn't exist yet, write a minimal + local version, mark it `// TODO(integration): ...`, and mention it in your + report. Never rewrite other agents' modules. You MAY add dependencies to your + own crate's `Cargo.toml` (prefer well-known crates; use `cargo add -p `). +2. **Faithful port.** Preserve behaviour, edge cases, defaults and naming + (snake_case of the Zig names). Port doc comments (they are user-visible for + config/actions). Don't "improve" semantics. Idiomatic Rust structure is fine + (enums for tagged unions, traits for comptime interfaces, `Result` for error + unions) as long as behaviour matches. +3. **Port the tests.** Every Zig `test "name"` in files you port becomes a + `#[test] fn name_snake_case()` in a `#[cfg(test)] mod tests` of the + corresponding Rust module. Tests that are about Zig-only concerns + (allocator failure injection, comptime, C ABI, Metal/macOS) may be skipped — + list them in your notes file. Add extra tests when behaviour is subtle. +4. **No `unsafe`** unless your crate is explicitly allowed (renderer, termio, + apprt-gtk), and then only minimal + `// SAFETY:` comments. +5. **Quality gate before you finish** (must all pass for your crate): + - `cargo build -p ` + - `cargo test -p ` + - `cargo clippy -p --all-targets -- -D warnings` + - `cargo fmt -p ` (rustfmt.toml in repo root; if nightly-only options + warn, ignore the warnings) + No `todo!()`/`unimplemented!()` left in code paths that are reachable, + unless documented in your notes as deferred. +6. **Do NOT run `git commit`** or any git command that changes state. The + orchestrator commits. +7. Other agents work concurrently in other crates of the same workspace. If the + *workspace* build breaks because of another crate, use `-p ` and + ignore it. Cargo may block briefly on the build-directory lock; that's normal. +8. **Write notes** to `docs/rewrite/notes/-.md`: what was ported + (file → module), skipped tests with reasons, deviations, TODO(integration) + items, known gaps. +9. **Final report** (your last message): ≤ 25 lines — modules done, test counts + (ported / skipped), quality-gate status, open issues. The orchestrator reads + only this, so be precise and honest about anything incomplete. + +## Conventions + +- `pub mod` per Zig file; file names snake_case (`PageList.zig` → `page_list.rs`). +- Error types: `thiserror` is fine; `anyhow` only in binaries/CLI. +- Logging: the `log` crate (`log::warn!` etc.) where Zig uses `std.log`. +- Integer types: keep the Zig widths (u16 for cell counts `CellCountInt`, etc.). +- Zig `packed struct` bitfields → plain structs unless memory layout matters + (documented in ARCHITECTURE for `Cell`/`Row`). +- Prefer `std` collections; `hashbrown`/`smallvec`/`bitflags` OK. diff --git a/docs/rewrite/ARCHITECTURE.md b/docs/rewrite/ARCHITECTURE.md new file mode 100644 index 0000000..9f2a215 --- /dev/null +++ b/docs/rewrite/ARCHITECTURE.md @@ -0,0 +1,76 @@ +# Ghostty-rs Architecture + +Rust rewrite of Ghostty 1.3.1. Reference source: `../ghostty/` (Zig), referred +to below as `ZIG/` (= `/pantheon/pfs/git/rust/ghostty-rewrite/ghostty`). + +Target platform: **Linux (GTK4 + libadwaita, OpenGL)**. macOS/Metal/CoreText, +WASM, Windows and the embedded C API (`libghostty`) are out of scope. + +## Crate map (workspace `crates/`) + +| Crate | Ports (ZIG/src/...) | Depends on | +|---|---|---| +| `ghostty-vt` | terminal/Parser, parse_table, UTF8Decoder, osc.zig + osc/*, csi, dcs, apc, sgr, color, x11_color, charsets, modes, ansi, device_status, mouse_shape, kitty/key, kitty/color, kitty/graphics_command, stream.zig | – | +| `ghostty-unicode` | unicode/* (width, grapheme break, props) | – | +| `ghostty-datastruct` | datastruct/* (split_tree, lru, circ_buf, blocking_queue, cache_table, ...) | – | +| `ghostty-os` | os/* (xdg, homedir, shell, locale, passwd, env, open, desktop, resourcesdir, hostname, TempDir, uri, ...) | – | +| `ghostty-input` | input/* (key, key_mods, keycodes, function_keys, key_encode, Binding, command, mouse, paste, kitty, Link) | vt | +| `ghostty-config` | config/*, cli/args.zig, cli/CommaSplitter.zig, cli/diagnostics.zig | input, os, vt | +| `ghostty-terminal` | terminal/ page, PageList, Screen, ScreenSet, Terminal, style, hyperlink, Tabstops, size, point, Selection, formatter, render, highlight, search/*, kitty/graphics_{image,storage,exec,unicode,render}, tmux/*, stream_readonly, StringMap, ref_counted_set, bitmap_allocator semantics | vt, unicode, datastruct | +| `ghostty-font` | font/* (Metrics, sprite/*, Atlas, discovery via fontconfig, face via freetype, shaper via harfbuzz, Collection, CodepointResolver, SharedGrid(Set), opentype/*, nerd_font_attributes) | config, unicode, terminal | +| `ghostty-renderer` | renderer/ generic, OpenGL (+ opengl/*), cell, cursor, image, link, size, State, Thread, Overlay, shadertoy, row, shaders/glsl | config, font, terminal | +| `ghostty-termio` | termio/*, pty.zig, Command.zig, shell-integration resources | config, terminal, os | +| `ghostty-core` | App.zig, Surface.zig, surface_mouse.zig, apprt/action.zig, apprt/structs.zig, apprt/surface.zig, global.zig, cli/* actions (+list-fonts, +show-config, ...) | everything below | +| `ghostty-apprt-gtk` | apprt/gtk/* | core, config, input | +| root `ghostty-rs` (bin `ghostty`) | main_ghostty.zig, build_config.zig | core, config, apprt-gtk | + +## Key design decisions + +1. **Safe Rust by default.** Workspace lint `unsafe_code = "forbid"`. Crates + that must do FFI/syscalls (renderer GL loader, termio pty/fork, apprt-gtk) + may override with `unsafe_code = "deny"` and `#[allow(unsafe_code)]` on + minimal, `// SAFETY:`-commented items. Prefer safe wrapper crates (rustix, + glow, gtk4-rs) over raw FFI. +2. **Pages without raw memory.** Ghostty's `page.zig` packs a page into one + mmap'd buffer with offsets. In Rust a `Page` owns `Vec` + `Vec` + (row-major, `cols` per row) plus per-page `RefCountedSet