Skip to main content

Overview

Utility modules provide core functionality for config parsing, color operations, and keybind validation.

settings.ts

Location: src/lib/data/settings.ts Settings schema and metadata for the Ghostty Config editor.

Exports

export type KeybindString = `${string}=${string}`;

export interface ColorScheme {
  palette: HexColor[];
  background?: HexColor;
  foreground?: HexColor;
  cursor?: HexColor;
  selection?: HexColor;
  // ... additional color properties
}

export const fetchColorScheme = async (theme: string): Promise<ColorScheme>

export default settings; // Array of Panel objects

Settings Structure

The default export is an array of Panel objects, each containing Groups, which contain individual Settings:
interface Panel {
  id: string;
  name: string;
  groups: Group[];
}

interface Group {
  id: string;
  name: string;
  settings: Array<Switch | Number | Dropdown | Color | Palette | Keybinds | Theme>;
}

Setting Types

  • switch — Boolean toggle
  • number — Numeric input with optional range
  • dropdown — Select from options
  • text — Text input
  • color — Color picker
  • palette — 256-color array
  • keybinds — Keybinding array
  • theme — Theme selector (special dropdown)

fetchColorScheme()

Fetches color schemes from the iTerm2-Color-Schemes repository.
const response = await fetchColorScheme("Dracula");
// Returns: { background: "#282a36", foreground: "#f8f8f2", palette: [...], ... }
Features:
  • Loads themes from GitHub API
  • Parses iTerm2 XML format
  • Converts to Ghostty color format
  • Used by the Theme component

parse.ts

Location: src/lib/utils/parse.ts Parses Ghostty config file format into JavaScript objects.

Default Export

export default function (configString: string): ParsedConfig

Return Type

type ParsedConfig = {
  palette: Array<HexColor | "">;
  keybind: KeybindString[];
  [key: string]: string | string[];
}

Parsing Rules

Line format: key = value
const re = /^\s*([a-z-]+)[\s]*=\s*(.*)\s*$/;
Key conversion: Kebab-case → camelCase
const split = key.split("-");
let newKey = split[0].trim();
for (let s = 1; s < split.length; s++) {
  newKey += split[s].charAt(0).toUpperCase();
  newKey += split[s].substring(1);
}
// "font-size" → "fontSize"

Special Handling

Format: palette = N=color
if (key === "palette") {
  const split = value.split("=");
  const num = parseInt(split[0].trim());
  const color = split[1].trim() as HexColor;
  if (num < 0 || num > 255) continue;
  results.palette[num] = color;
}
Example:
palette = 0=#000000
palette = 15=#ffffff
Accumulated into an array.
if (key === "keybind") {
  results.keybind.push(value as KeybindString);
}
Example:
keybind = ctrl+t=new_tab
keybind = ctrl+shift+v=paste_from_clipboard
Auto-prefixes # for 6-character hex values.
const colors = [
  "background", "foreground", "cursor-color",
  "selection-background", "selection-foreground"
];

if (colors.includes(key) && value.length === 6 && !value.startsWith("#")) {
  results[newKey] = `#${value}`;
}
Example:
background = 1e1e1e     → { background: "#1e1e1e" }
background = #1e1e1e    → { background: "#1e1e1e" }

Usage Example

import parse from "$lib/utils/parse";

const configText = `
font-size = 14
background = 1e1e1e
palette = 0=000000
keybind = ctrl+t=new_tab
`;

const config = parse(configText);
// {
//   fontSize: "14",
//   background: "#1e1e1e",
//   palette: ["#000000", "", "", ...],
//   keybind: ["ctrl+t=new_tab"]
// }

colors.ts

Location: src/lib/utils/colors.ts Color conversion and manipulation utilities.

Types

export type HexColor = `#${string}`;
export type RgbArray = [number, number, number];
export type HsvArray = [number, number, number];
export type HsvObj = {hue: number, saturation: number, value: number};

Functions

Calculates relative luminance of a color.
export function luminosity(color: HexColor): number
Formula: ITU-R BT.709 weighted RGB
const red = int >> 16 & 0xFF;
const green = int >> 8 & 0xFF;
const blue = int >> 0 & 0xFF;
return (0.2126 * red) + (0.7152 * green) + (0.0722 * blue);
Returns: Value from 0 (black) to 255 (white)

Usage

luminosity("#000000")  // 0
luminosity("#ffffff")  // 255
luminosity("#808080")  // ~128
Tests if a color is dark.
export function isDark(color: HexColor): boolean
Returns true if luminosity < 128.

Usage

const textColor = isDark(bgColor) ? "white" : "black";
Converts HSV to RGB.
export function hsvToRgb(
  hue: number,        // 0-1
  saturation: number, // 0-1
  value: number       // 0-1
): RgbArray           // [0-255, 0-255, 0-255]
Algorithm: Standard HSV to RGB conversion from Wikipedia.

Example

hsvToRgb(0, 1, 1)      // [255, 0, 0] - red
hsvToRgb(0.33, 1, 1)   // [0, 255, 0] - green
hsvToRgb(0, 0, 1)      // [255, 255, 255] - white
Converts RGB to HSV.
export function rgbToHsv(
  red: number,    // 0-255
  green: number,  // 0-255
  blue: number    // 0-255
): HsvObj         // {hue: 0-1, saturation: 0-1, value: 0-1}

Example

rgbToHsv(255, 0, 0)
// {hue: 0, saturation: 1, value: 1}

rgbToHsv(128, 128, 128)
// {hue: 0, saturation: 0, value: 0.502}
Converts RGB values to hex color.
export function rgbToHex(
  red: number,    // 0-255
  green: number,  // 0-255
  blue: number    // 0-255
): HexColor

Implementation

const get = (color: number) => 
  color.toString(16).padStart(2, "0").toUpperCase();
return `#${get(red)}${get(green)}${get(blue)}`;

Example

rgbToHex(255, 0, 0)     // "#FF0000"
rgbToHex(64, 128, 128)  // "#408080"
Converts hex color to RGB array.
export function hexToRgb(string: HexColor): RgbArray

Example

hexToRgb("#FF0000")  // [255, 0, 0]
hexToRgb("#408080")  // [64, 128, 128]

keybinds.ts

Location: src/lib/utils/keybinds.ts Keybind parsing, validation, and formatting.

Constants

Array of 177 valid key names.
const KEY_NAMES = [
  "backquote", "backslash", "bracket_left", "bracket_right",
  "key_a", "key_b", ..., "key_z",
  "f1", "f2", ..., "f24",
  "arrow_up", "arrow_down", "arrow_left", "arrow_right",
  "numpad_0", ..., "numpad_9",
  "copy", "paste", "cut",
  // ... many more
] as const;

export type KeyName = (typeof KEY_NAMES)[number];
const VALID_MODIFIERS = ["shift", "ctrl", "alt", "super"];
Aliases (normalized to standard form):
  • controlctrl
  • cmd, commandsuper
  • opt, optionalt
const VALID_PREFIXES = ["all", "global", "unconsumed", "performable"];
Array of 80+ action definitions.
export interface ActionDefinition {
  name: string;
  type: "none" | "number" | "integer" | "free" | "enum" | "text" | "resize";
  options?: string[];
  allowEmpty?: boolean;
}

const ACTION_DEFINITIONS: ActionDefinition[] = [
  {name: "ignore", type: "none"},
  {name: "new_tab", type: "none"},
  {name: "goto_tab", type: "integer"},
  {
    name: "copy_to_clipboard",
    type: "enum",
    options: ["plain", "vt", "html", "mixed"],
    allowEmpty: true
  },
  {name: "resize_split", type: "resize"},
  // ...
];

Types

export type ParsedTriggerStep = {
  key: string;
  modifiers: string[];
};

export type ParsedTrigger = {
  prefixes: string[];
  steps: ParsedTriggerStep[];
};

export type ParsedKeybind = {
  trigger?: ParsedTrigger;
  action?: string;
  args?: string;
  error?: string[];
};

Functions

Parses and validates a complete keybind string.
export function parseKeybind(value: string): ParsedKeybind

Format

[prefixes:]key[+modifier][>key[+modifier]]=action[:arg]

Examples

parseKeybind("ctrl+t=new_tab")
// {
//   trigger: {
//     prefixes: [],
//     steps: [{key: "t", modifiers: ["ctrl"]}]
//   },
//   action: "new_tab",
//   args: undefined,
//   error: []
// }

parseKeybind("global:ctrl+q=quit")
parseKeybind("ctrl+k>ctrl+v=new_split:down")
parseKeybind("shift+alt+super+key_a=ignore")

Validation

Returns errors for:
  • Invalid key names
  • Invalid modifiers
  • Invalid action names
  • Wrong argument types
  • Global/all keybinds with sequences
  • More than 4 modifiers
Parses just the trigger portion (before =).
export function parseTrigger(triggerString: string): ParsedTrigger | null

Process

  1. Normalize whitespace
  2. Extract prefixes (global:, all:, etc.)
  3. Split on > for sequences
  4. Parse each step’s modifiers and key

Example

parseTrigger("global:ctrl+shift+t")
// {
//   prefixes: ["global"],
//   steps: [{key: "t", modifiers: ["ctrl", "shift"]}]
// }

parseTrigger("ctrl+k>v")
// {
//   prefixes: [],
//   steps: [
//     {key: "k", modifiers: ["ctrl"]},
//     {key: "v", modifiers: []}
//   ]
// }
Formats a ParsedTrigger back to string.
export function formatTrigger(trigger: ParsedTrigger): string
Normalizations:
  • Removes duplicate prefixes
  • Sorts modifiers alphabetically
  • Lowercases keys

Example

formatTrigger({
  prefixes: ["global"],
  steps: [{key: "T", modifiers: ["shift", "ctrl"]}]
})
// "global:ctrl+shift+t"
Validates an array of keybinds and detects duplicates.
export function getDiagnostics(keybinds: string[]): Diagnostic[]
Returns:
type Diagnostic = {
  status: "ok" | "invalid";
  duplicate: boolean;
  errors: string[];
  canonical: string;
}

Usage

const diagnostics = getDiagnostics([
  "ctrl+t=new_tab",
  "ctrl+t=new_window",  // duplicate trigger!
  "invalid syntax"
]);
// [
//   {status: "ok", duplicate: true, errors: [], canonical: "ctrl+t"},
//   {status: "ok", duplicate: true, errors: [], canonical: "ctrl+t"},
//   {status: "invalid", duplicate: false, errors: [...], canonical: ""}
// ]
Normalizes trigger to canonical form for comparison.
export function canonicalTrigger(parsed: ParsedTrigger): string
Normalizations:
  • Sorts modifiers alphabetically
  • Lowercases everything
  • Removes whitespace

Example

// These all produce "alt+ctrl+t":
canonicalTrigger(parseTrigger("ctrl+alt+t"));
canonicalTrigger(parseTrigger("alt+ctrl+t"));
canonicalTrigger(parseTrigger("Ctrl + Alt + T"));

Action Type Validation

function validateAction(action: string, args: string | undefined): string[]
Type-specific validation:
  • none: No arguments allowed
  • free: Any argument or none (if allowEmpty)
  • text: Requires Zig string literal
  • number: Must be numeric
  • integer: Must be whole number
  • enum: Must be in options list
  • resize: Format direction,offset where direction ∈

Key Normalization

Accepts multiple formats:
"KeyA""key_a"
"ArrowUp""arrow_up"
"PageDown""page_down"
"F1""f1"
"a""a"  (single character)
"physical:q""physical:q"  (physical layout)

Build docs developers (and LLMs) love