name: ghostty-code-patterns description: Ghostty codebase coding patterns for Zig (core, C API, termio) and Swift (macOS app). Load when reading or writing Zig or Swift files in this repo. Covers naming, error handling, logging, config, memory, imports, tests, C interop, and state restoration conventions.
Ghostty Codebase Patterns
Conventions extracted from the upstream Ghostty maintainers' code. Follow these when contributing Zig or Swift changes to this repo.
Rule Strength
Use this priority when applying guidance:
- Required: must follow for consistency, correctness, or API safety.
- Preferred: default style; deviate only when the surrounding file/module has an established pattern.
- Direction: migration target for new code; existing code may still use older patterns.
Zig Conventions
Imports
Preferred: order imports consistently across all files:
stdand stdlib extractions (Allocator,posix,assert)- Internal project imports (
termio,terminal,renderer,apprt,configpkg) - Local/sibling imports (
snapshot,shell_integration) const log = std.log.scoped(.scope_name);— always last among imports
File Structure
- Preferred: File-is-struct idiom: files that define a primary struct use
const X = @This();at the top. - Preferred: Private helpers: free functions (no
self), nopub, placed near related code. - Preferred: Init pattern:
pub fn init(self: *Self, alloc: Allocator, ...) !voidwithself.* = .{ ... }assignment. - Preferred: Labeled blocks: use
break :label valuefor complex initialization insideinit.
Naming
- Required:
@""syntax for hyphenated config keys:config.@"window-save-state". - Required: preserve established naming in the file/module; do not rename public APIs just to normalize style.
- Preferred: local/private Zig identifiers are generally
snake_case. - Current practice: some long-lived/public APIs use camelCase; match adjacent code.
- Preferred: common abbreviations:
alloc,cfg,td,io,gpa,env.
Error Handling
- Required: handle all errors explicitly.
- Preferred: use
errdeferimmediately after fallible allocations/resource acquisition. - Preferred: CAPI export functions: complex ones use a two-function split (public export wrapper catches errors + logs, private
_suffixed inner function returns!T). Simpler ones handle errors inline. - Preferred:
catch |err| { log.warn/err(...); return ...; }for inline error handling. - Preferred: prefer
constovervar; usevarwhen mutation is required.
Logging
Format: log.level("description key={fmt}", .{value}).
log.err("error initializing app err={}", .{err});
log.warn("snapshot restore skipped path={s} err={}", .{ path, err });
log.info("deleting widget id={d}", .{id});
- Preferred:
log.errfor initialization failures and critical errors. - Preferred:
log.warnfor recoverable issues, bad input, skipped operations. - Preferred:
log.infofor state changes. - Preferred:
log.debugfor internal diagnostics. - Preferred: key-value pairs:
err={},path={s},id={d},dir={s}(no quotes, no space around=).
Memory
- Required: ownership must be explicit and deinit/free paths must be obvious.
- Preferred: pass allocators explicitly.
- Scope note: runtime bridge/CAPI layers may intentionally use shared runtime allocators (for example
global.alloc) where ownership is process-global and crosses FFI boundaries. - Preferred:
deferimmediately after acquiring a resource. - Preferred: arena allocators for batch/scoped allocations (
ArenaAllocator.init/defer arena.deinit()). - Required (tests):
std.testing.allocatorfor leak detection.
Config Options (src/config/Config.zig)
- Required: doc comments use
///in Pandoc-flavored Markdown. - Preferred: first paragraph is a concise summary.
- Preferred: valid values as bullet list with
* \value`` format. - Preferred: cross-references use backtick-quoted option names.
- Preferred: platform notes at end (for example: "This is currently only supported on macOS. This has no effect on Linux.").
- Required: field names use
@"kebab-case-name".
Tests
- Preferred: descriptive string names:
test "snapshot header roundtrip". - Preferred: first line in test blocks:
const testing = std.testing;. - Required: use
std.testing.allocatorfor leak detection. - Preferred: assertions:
try testing.expectEqual(...),try testing.expectEqualSlices(...),try testing.expectError(...). - Preferred: module test block uses
refAllDecls(@This())plus explicit_ = @import(...)for otherwise-unreferenced modules.
Writer/Reader API
Current practice: codebase contains both std.Io and std.io usage.
Direction (new code): prefer std.Io.Writer, std.Io.Reader (uppercase I) unless you're touching code that is still centered on older std.io stream APIs; in that case, stay consistent within the file/change.
var vt: std.Io.Writer.Allocating = .init(alloc);
var reader: std.Io.Reader = .fixed(data);
CAPI Functions (src/apprt/embedded.zig)
- Required: live inside
pub const CAPI = struct { ... }. - Required: export name prefix
ghostty_with snake_case (for exampleghostty_surface_export_snapshot). - Required: return
boolor?*Tfor fallible operations (no Zig errors cross FFI boundary). - Preferred: parameter naming:
c_path,c_inputfor C-originated pointers;surface,appfor Ghostty objects. - Required: use
std.mem.sliceTo(c_ptr, 0)to convert sentinel-terminated C strings to slices.
Swift Conventions (macOS App)
Naming
- Required: Swift
camelCasefor properties, methods, local variables. - Required: C struct fields stay
snake_case(e.g.,config.working_directory). - Required: config keys use
kebab-casein C API strings,camelCaseas Swift computed properties. - Preferred:
CodingKeysenum cases arecamelCase; append new keys at end for stable ordering.
File Organization
- Preferred: one major type per file.
- Current practice: cohesive files may contain multiple related types/protocols/extensions (especially restoration/state modules). Keep related pieces together when it improves cohesion.
- Preferred:
extension Ghostty { ... }namespace for Ghostty-specific types. - Preferred:
// MARK: -section dividers for protocol conformances and logical sections.
Access Control
- Preferred:
private(set)for externally-readable/internally-writable. - Preferred:
@Published private(set)for observable state. - Preferred:
private staticfor internal helpers. - Preferred: rely on default
internalaccess unless explicit visibility is needed.
Error Handling
- Preferred:
guard ... else { return }for early returns. - Preferred:
do/catchwithlogger.warning(...)for non-fatal errors. - Preferred:
throwspropagation for fatal errors.
Logging
Apple OSLog framework via Logger:
static let logger = Logger(
subsystem: Bundle.main.bundleIdentifier!,
category: String(describing: MyType.self)
)
Called as Self.logger.warning("description key=\(value)") or AppDelegate.logger.warning(...).
Config Access
Preferred: access config through computed properties (ghostty.config.propertyName or appDelegate.ghostty.config.propertyName). Config values are String, Bool, etc., returned by ghostty_config_get calls inside the computed var.
Async / Dispatch
Current practice: mixed model. The macOS app uses both GCD (DispatchQueue) and structured concurrency (async/await, Task).
Preferred: follow the surrounding module's established pattern and actor constraints:
- Use
DispatchQueuefor callback-based/legacy flows and AppKit scheduling. - Use
async/await+Taskin async workflows (for example App Intents, Swift concurrency-native paths). - Capture weak references when escaping closures can outlive object ownership and
selfretention would be problematic.
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) { [weak self] in
guard let self else { return }
// ...
}
C Interop
- Required: preserve C string lifetime safety with
withCString/withCValuewrappers. - Preferred:
Optional<String>.withCStringpassesnilfor.none. - Preferred: each optional C string field adds one nesting level in
withCValue.
Codable / State Restoration
- Required: state versioning via
class var version: Intwhere restoration models require version checks. - Preferred:
CodingKeysenum with camelCase cases. - Preferred:
decodeIfPresentfor newer/optional fields (backward compatibility). - Current practice: Codable models may use
required convenience init(from decoder:); NSCoding restoration paths may useinit?(coder:)wrappers around codable bridges.
Stores / Managers
- Preferred: caseless
enumwith static methods (notclass) for stateless namespaces. - Preferred:
NSLock()for thread safety on shared mutable state. - Preferred:
FileManager+URLAPIs for filesystem operations. - Required: XDG compliance: respect
XDG_STATE_HOME, default to~/.local/state/ghostty/.