Skip to content

Commit 20e2164

Browse files
authored
Merge pull request #39 from zerebos/feat/keybinds
Add keybind validation and keybind generator GUI
2 parents 2866d16 + 1d699c1 commit 20e2164

41 files changed

Lines changed: 2447 additions & 689 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@ The only automated deployment built-in is via GitHub Actions which automatically
7272
- [x] Update for Ghostty 1.2
7373
- [x] Update for Ghostty 1.3 (nightly)
7474
- [x] Icon customization
75+
- [x] Keybinding validation
7576
- [ ] Detailed terminal playground
76-
- [ ] Keybinding validation
7777
- [ ] Command palette builder
7878
- [ ] Desktop version via Wails (if demand is there)
7979
- [ ] Community presets for quick setup

bun.lock

Lines changed: 120 additions & 154 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

eslint.config.js

Lines changed: 15 additions & 191 deletions
Original file line numberDiff line numberDiff line change
@@ -1,200 +1,24 @@
1-
import js from "@eslint/js";
2-
import ts from "typescript-eslint";
3-
import svelte from "eslint-plugin-svelte";
4-
import prettier from "eslint-config-prettier";
5-
import globals from "globals";
1+
import {browser} from "@zerebos/eslint-config";
2+
import ts from "@zerebos/eslint-config-typescript";
3+
import {defineConfig} from "eslint/config";
4+
import {build} from "@zerebos/eslint-config-svelte";
5+
import svelteConfig from "./svelte.config.js";
66

7-
/** @type {import('eslint').Linter.Config[]} */
8-
export default [
9-
js.configs.recommended,
10-
...ts.configs.recommended,
11-
...svelte.configs["flat/recommended"],
12-
prettier,
13-
...svelte.configs["flat/prettier"],
7+
8+
export default defineConfig(
9+
defineConfig(
10+
...browser,
11+
...ts.configs.recommendedWithTypes
12+
),
13+
...build(svelteConfig),
1414
{
1515
languageOptions: {
1616
globals: {
17-
...globals.browser,
18-
...globals.node
19-
}
20-
}
21-
},
22-
{
23-
files: ["**/*.svelte"],
24-
languageOptions: {
25-
parserOptions: {
26-
parser: ts.parser
17+
__INSTALLER_LICENSE__: "readonly",
2718
}
2819
}
2920
},
3021
{
31-
ignores: ["build/", ".svelte-kit/", "dist/", "custom/", "config/", "sverdle/"]
32-
},
33-
{
34-
rules: {
35-
"accessor-pairs": "error",
36-
"block-spacing": ["error", "never"],
37-
"brace-style": ["error", "stroustrup", {allowSingleLine: true}],
38-
"curly": ["error", "multi-line", "consistent"],
39-
"dot-location": ["error", "property"],
40-
"dot-notation": "error",
41-
"func-call-spacing": "error",
42-
"handle-callback-err": "error",
43-
"key-spacing": "error",
44-
"keyword-spacing": "error",
45-
"new-cap": ["error", {newIsCap: true}],
46-
"no-array-constructor": "error",
47-
"no-caller": "error",
48-
"no-console": "error",
49-
"no-duplicate-imports": "error",
50-
"no-else-return": "error",
51-
"no-eval": "error",
52-
"no-floating-decimal": "error",
53-
"no-implied-eval": "error",
54-
"no-iterator": "error",
55-
"no-label-var": "error",
56-
"no-labels": "error",
57-
"no-lone-blocks": "error",
58-
"no-mixed-spaces-and-tabs": "error",
59-
"no-multi-spaces": "error",
60-
"no-multi-str": "error",
61-
"no-new": "error",
62-
"no-new-func": "error",
63-
"no-new-object": "error",
64-
"no-new-wrappers": "error",
65-
"no-octal-escape": "error",
66-
"no-path-concat": "error",
67-
"no-proto": "error",
68-
"no-prototype-builtins": "off",
69-
"no-redeclare": ["error", {builtinGlobals: true}],
70-
"no-self-compare": "error",
71-
"no-sequences": "error",
72-
"no-shadow": ["warn", {builtinGlobals: false, hoist: "functions"}],
73-
"no-tabs": "error",
74-
"no-template-curly-in-string": "error",
75-
"no-throw-literal": "error",
76-
"no-undef": "error",
77-
"no-undef-init": "error",
78-
"no-unmodified-loop-condition": "error",
79-
"no-unneeded-ternary": "error",
80-
"no-useless-call": "error",
81-
"no-useless-computed-key": "error",
82-
"no-useless-constructor": "error",
83-
"no-useless-rename": "error",
84-
"no-var": "error",
85-
"no-whitespace-before-property": "error",
86-
"object-curly-spacing": ["error", "never", {objectsInObjects: false}],
87-
"object-property-newline": ["error", {allowAllPropertiesOnSameLine: true}],
88-
"operator-linebreak": [
89-
"error",
90-
"none",
91-
{overrides: {"?": "before", ":": "before", "&&": "before"}}
92-
],
93-
"prefer-const": "error",
94-
"quote-props": ["error", "consistent-as-needed", {keywords: true}],
95-
"quotes": ["error", "double", {allowTemplateLiterals: true}],
96-
"rest-spread-spacing": "error",
97-
"semi": "error",
98-
"semi-spacing": "error",
99-
"space-before-blocks": "error",
100-
"space-in-parens": "error",
101-
"space-infix-ops": "error",
102-
"space-unary-ops": [
103-
"error",
104-
{words: true, nonwords: false, overrides: {"typeof": false}}
105-
],
106-
"spaced-comment": ["error", "always", {exceptions: ["-", "*"]}],
107-
"template-curly-spacing": "error",
108-
"wrap-iife": ["error", "inside"],
109-
"yield-star-spacing": "error",
110-
"yoda": "error"
111-
},
112-
},
113-
{
114-
rules: {
115-
"@typescript-eslint/no-unused-vars": ["error", {argsIgnorePattern: "^_", varsIgnorePattern: "^_"}]
116-
}
22+
ignores: ["build/", ".svelte-kit/", "dist/", "custom/", "config/", "sverdle/", "notes/"]
11723
},
118-
{
119-
rules: {
120-
// Errors
121-
"svelte/infinite-reactive-loop": "error",
122-
"svelte/no-dom-manipulating": "off",
123-
"svelte/no-dupe-else-if-blocks": "error",
124-
"svelte/no-dupe-on-directives": "error",
125-
"svelte/no-dupe-style-properties": "error",
126-
"svelte/no-dupe-use-directives": "error",
127-
"svelte/no-dynamic-slot-name": "error",
128-
"svelte/no-export-load-in-svelte-module-in-kit-pages": "error",
129-
"svelte/no-not-function-handler": "error",
130-
"svelte/no-object-in-text-mustaches": "error",
131-
"svelte/no-reactive-reassign": "error",
132-
"svelte/no-shorthand-style-property-overrides": "error",
133-
"svelte/no-store-async": "error",
134-
"svelte/no-unknown-style-directive-property": "error",
135-
"svelte/require-store-callbacks-use-set-param": "error",
136-
"svelte/require-store-reactive-access": "error",
137-
"svelte/valid-compile": "error",
138-
"svelte/valid-prop-names-in-kit-pages": "error",
139-
140-
// Security
141-
"svelte/no-at-html-tags": "error",
142-
"svelte/no-target-blank": "error",
143-
144-
// Best Practices
145-
"svelte/block-lang": ["error", {script: "ts"}],
146-
"svelte/button-has-type": "error",
147-
"svelte/no-at-debug-tags": "error",
148-
"svelte/no-ignored-unsubscribe": "error",
149-
"svelte/no-immutable-reactive-statements": "error",
150-
"svelte/no-inline-styles": "off",
151-
"svelte/no-reactive-functions": "error",
152-
"svelte/no-reactive-literals": "error",
153-
"svelte/no-svelte-internal": "error",
154-
"svelte/no-unused-class-name": "error",
155-
"svelte/no-unused-svelte-ignore": "error",
156-
"svelte/no-useless-mustaches": "error",
157-
"svelte/prefer-destructured-store-props": "error",
158-
"svelte/require-each-key": "error",
159-
"svelte/require-event-dispatcher-types": "error",
160-
"svelte/require-optimized-style-attribute": "error",
161-
"svelte/require-stores-init": "error",
162-
"svelte/valid-each-key": "error",
163-
164-
// Stylistic Issues
165-
"svelte/derived-has-same-inputs-outputs": "error",
166-
"svelte/first-attribute-linebreak": ["error", {multiline: "below", singleline: "beside"}],
167-
"svelte/html-closing-bracket-spacing": "error",
168-
"svelte/html-quotes": ["error", {prefer: "double"}],
169-
"svelte/html-self-closing": ["error", {"void": "always", "component": "always", "svelte": "always", "normal": "ignore"}],
170-
"svelte/indent": "off",
171-
"svelte/max-attributes-per-line": "off",
172-
"svelte/mustache-spacing": "error",
173-
"svelte/no-extra-reactive-curlies": "error",
174-
// "svelte/no-restricted-html-elements": "error",
175-
"svelte/no-spaces-around-equal-signs-in-attribute": "error",
176-
"svelte/prefer-class-directive": ["error", {prefer: "always"}],
177-
"svelte/prefer-style-directive": "error",
178-
"svelte/shorthand-attribute": "error",
179-
"svelte/shorthand-directive": "error",
180-
"svelte/sort-attributes": "off",
181-
"svelte/spaced-html-comment": "error",
182-
183-
// Extension Rules
184-
"svelte/no-inner-declarations": "error",
185-
"svelte/no-trailing-spaces": "error",
186-
187-
// SvelteKit
188-
"svelte/no-goto-without-base": "error",
189-
190-
// System
191-
"svelte/comment-directive": ["error", {reportUnusedDisableDirectives: true}],
192-
"svelte/system": "error",
193-
},
194-
languageOptions: {
195-
globals: {
196-
$state: false
197-
}
198-
}
199-
}
200-
];
24+
);

package.json

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,29 +14,29 @@
1414
"preview": "vite preview",
1515
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
1616
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
17-
"test": "vitest",
17+
"test": "vitest run . --reporter=verbose",
1818
"lint": "eslint .",
1919
"format": "prettier --write . && eslint . --fix"
2020
},
2121
"devDependencies": {
22-
"@neoconfetti/svelte": "^2.2.2",
23-
"@sveltejs/adapter-auto": "^7.0.0",
22+
"@sveltejs/adapter-auto": "^7.0.1",
2423
"@sveltejs/adapter-static": "^3.0.10",
25-
"@sveltejs/kit": "^2.49.1",
26-
"@sveltejs/vite-plugin-svelte": "^6.2.1",
27-
"@types/eslint": "^9.6.1",
28-
"eslint": "^9.39.1",
24+
"@sveltejs/kit": "^2.53.0",
25+
"@sveltejs/vite-plugin-svelte": "^6.2.4",
26+
"@types/bun": "^1.3.9",
27+
"@zerebos/eslint-config": "^1.0.2",
28+
"@zerebos/eslint-config-svelte": "^1.0.3",
29+
"@zerebos/eslint-config-typescript": "^1.0.3",
30+
"eslint": "^10.0.1",
2931
"eslint-config-prettier": "^10.1.8",
30-
"eslint-plugin-svelte": "^3.13.0",
31-
"globals": "^16.5.0",
32-
"prettier": "^3.7.4",
33-
"prettier-plugin-svelte": "^3.4.0",
34-
"svelte": "^5.45.5",
35-
"svelte-check": "^4.3.4",
32+
"eslint-plugin-svelte": "^3.15.0",
33+
"prettier": "^3.8.1",
34+
"prettier-plugin-svelte": "^3.5.0",
35+
"svelte": "^5.53.2",
36+
"svelte-check": "^4.4.3",
3637
"typescript": "^5.9.3",
37-
"typescript-eslint": "^8.48.1",
38-
"vite": "^7.2.6",
39-
"vitest": "^4.0.15"
38+
"vite": "^7.3.1",
39+
"vitest": "^4.0.18"
4040
},
4141
"type": "module",
4242
"dependencies": {

scripts/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Ghostty keybind sync helpers
2+
3+
These scripts fetch the latest key/action metadata from Ghostty's upstream source.
4+
They are intentionally lightweight so we can periodically refresh validation lists.
5+
6+
Usage (from repo root):
7+
8+
- `bun run scripts/fetch-ghostty-key-names.ts > /tmp/ghostty-keys.json`
9+
- `bun run scripts/fetch-ghostty-action-names.ts > /tmp/ghostty-actions.json`
10+
11+
Outputs are JSON payloads containing:
12+
13+
- `keys`: list of key enum identifiers (from `key.zig`)
14+
- `actions`: list of action names (from `Binding.zig`)
15+
- `sourceUrl`: the GitHub URL used for the fetch
16+
17+
Note: these scripts are purely helpers and do not mutate the repo.

scripts/eslint.config.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import {defineConfig} from "eslint/config";
2+
import base from "../eslint.config.js";
3+
4+
5+
export default defineConfig(
6+
base,
7+
{
8+
rules: {
9+
"no-console": "off",
10+
}
11+
}
12+
);
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
const BINDING_URL = "https://raw.githubusercontent.com/ghostty-org/ghostty/main/src/input/Binding.zig";
2+
3+
type ActionExtract = {
4+
actions: string[];
5+
sourceUrl: string;
6+
};
7+
8+
function extractActionBlock(source: string) {
9+
const marker = "pub const Action = union(enum)";
10+
const start = source.indexOf(marker);
11+
if (start === -1) return null;
12+
const after = source.slice(start + marker.length);
13+
const openIndex = after.indexOf("{");
14+
if (openIndex === -1) return null;
15+
let depth = 0;
16+
let inBlockComment = false;
17+
let inLineComment = false;
18+
for (let i = openIndex; i < after.length; i += 1) {
19+
const char = after[i];
20+
const next = after[i + 1];
21+
if (!inLineComment && !inBlockComment && char === "/" && next === "*") {
22+
inBlockComment = true;
23+
}
24+
else if (inBlockComment && char === "*" && next === "/") {
25+
inBlockComment = false;
26+
i += 1;
27+
continue;
28+
}
29+
else if (!inBlockComment && !inLineComment && char === "/" && next === "/") {
30+
inLineComment = true;
31+
}
32+
else if (inLineComment && char === "\n") {
33+
inLineComment = false;
34+
}
35+
if (inBlockComment || inLineComment) continue;
36+
if (char === "{") depth += 1;
37+
if (char === "}") {
38+
depth -= 1;
39+
if (depth === 0) {
40+
return after.slice(openIndex + 1, i);
41+
}
42+
}
43+
}
44+
return null;
45+
}
46+
47+
function parseActionNames(block: string) {
48+
const actions = new Set<string>();
49+
for (const rawLine of block.split("\n")) {
50+
const line = rawLine.trim();
51+
if (!line) continue;
52+
if (line.startsWith("//")) continue;
53+
if (line.startsWith("///")) continue;
54+
if (line.startsWith("pub const")) break;
55+
if (line.startsWith("test")) break;
56+
const entry = line.replace(/,.*/, "").trim();
57+
if (!entry) continue;
58+
const name = entry.split(":")[0].trim();
59+
if (!name) continue;
60+
actions.add(name);
61+
}
62+
return Array.from(actions).sort();
63+
}
64+
65+
async function fetchActions(): Promise<ActionExtract> {
66+
const response = await fetch(BINDING_URL);
67+
if (!response.ok) {
68+
throw new Error(
69+
`Failed to fetch ${BINDING_URL}: ${response.status} ${response.statusText}`
70+
);
71+
}
72+
const text = await response.text();
73+
const block = extractActionBlock(text);
74+
if (!block) throw new Error("Failed to locate Action union in source");
75+
return {actions: parseActionNames(block), sourceUrl: BINDING_URL};
76+
}
77+
78+
async function main() {
79+
const result = await fetchActions();
80+
console.log(JSON.stringify(result, null, 2));
81+
}
82+
83+
main().catch((error) => {
84+
console.error(error instanceof Error ? error.message : error);
85+
process.exit(1);
86+
});

0 commit comments

Comments
 (0)