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
This commit is contained in:
1 parent
e04991c1f5
commit
987f2957ad
34 files changed
+759
-4
No files matched your search
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
//! ghostty-apprt-gtk: see docs/rewrite/ARCHITECTURE.md for the module map.
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
//! ghostty-config: see docs/rewrite/ARCHITECTURE.md for the module map.
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
//! ghostty-core: see docs/rewrite/ARCHITECTURE.md for the module map.
|
||||
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "ghostty-datastruct"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1 @@
|
||||
//! ghostty-datastruct: see docs/rewrite/ARCHITECTURE.md for the module map.
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
//! ghostty-font: see docs/rewrite/ARCHITECTURE.md for the module map.
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
//! ghostty-input: see docs/rewrite/ARCHITECTURE.md for the module map.
|
||||
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "ghostty-os"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1 @@
|
||||
//! ghostty-os: see docs/rewrite/ARCHITECTURE.md for the module map.
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
//! ghostty-renderer: see docs/rewrite/ARCHITECTURE.md for the module map.
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
//! ghostty-terminal: see docs/rewrite/ARCHITECTURE.md for the module map.
|
||||
@@ -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
|
||||
@@ -0,0 +1 @@
|
||||
//! ghostty-termio: see docs/rewrite/ARCHITECTURE.md for the module map.
|
||||
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "ghostty-unicode"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -0,0 +1 @@
|
||||
//! ghostty-unicode: see docs/rewrite/ARCHITECTURE.md for the module map.
|
||||
@@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "ghostty-vt"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
//! Kitty protocol types (ZIG/src/terminal/kitty.zig).
|
||||
|
||||
pub mod key;
|
||||
@@ -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;
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user