import { KeybindingCommand, KeybindingRule, KeybindingsConfig } from "@t3tools/contracts";import * as NodeServices from "@effect/platform-node/NodeServices";import { assert, it } from "@effect/vitest";import { assertFailure } from "@effect/vitest/utils";import { Cause, Effect, FileSystem, Layer, Logger, Path, Schema } from "effect";import { ServerConfig } from "./config.ts"; import { DEFAULT_KEYBINDINGS, Keybindings, KeybindingsLive, ResolvedKeybindingFromConfig, compileResolvedKeybindingRule, compileResolvedKeybindingsConfig, parseKeybindingShortcut,} from "./keybindings.ts";import { KeybindingsConfigError } from "@t3tools/contracts"; const KeybindingsConfigJson = Schema.fromJsonString(KeybindingsConfig);const makeKeybindingsLayer = () => { return KeybindingsLive.pipe( Layer.provideMerge( Layer.fresh( ServerConfig.layerTest(process.cwd(), { prefix: "t3code-keybindings-test-", }), ), ), );}; const toDetailResult = <A, R>(effect: Effect.Effect<A, KeybindingsConfigError, R>) => effect.pipe( Effect.mapError((error) => error.detail), Effect.result, ); const writeKeybindingsConfig = (configPath: string, rules: readonly KeybindingRule[]) => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const encoded = yield* Schema.encodeEffect(KeybindingsConfigJson)(rules); yield* fileSystem.makeDirectory(path.dirname(configPath), { recursive: true }); yield* fileSystem.writeFileString(configPath, encoded); }); const readKeybindingsConfig = (configPath: string) => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const rawConfig = yield* fileSystem.readFileString(configPath); return yield* Schema.decodeUnknownEffect(KeybindingsConfigJson)(rawConfig); }); it.layer(NodeServices.layer)("keybindings", (it) => { it.effect("parses shortcuts including plus key", () => Effect.sync(() => { assert.deepEqual(parseKeybindingShortcut("mod+j"), { key: "j", metaKey: false, ctrlKey: false, shiftKey: false, altKey: false, modKey: true, }); assert.deepEqual(parseKeybindingShortcut("mod++"), { key: "+", metaKey: false, ctrlKey: false, shiftKey: false, altKey: false, modKey: true, }); }), ); it.effect("compiles valid rule with parsed when AST", () => Effect.sync(() => { const compiled = compileResolvedKeybindingRule({ key: "mod+d", command: "terminal.split", when: "terminalOpen && !terminalFocus", }); assert.deepEqual(compiled, { command: "terminal.split", shortcut: { key: "d", metaKey: false, ctrlKey: false, shiftKey: false, altKey: false, modKey: true, }, whenAst: { type: "and", left: { type: "identifier", name: "terminalOpen" }, right: { type: "not", node: { type: "identifier", name: "terminalFocus" }, }, }, }); }), ); it.effect("encodes resolved plus-key shortcuts", () => Effect.gen(function* () { const encoded = yield* Schema.encodeEffect(ResolvedKeybindingFromConfig)({ command: "terminal.toggle", shortcut: { key: "+", metaKey: false, ctrlKey: false, shiftKey: false, altKey: false, modKey: true, }, }); assert.equal(encoded.key, "mod++"); assert.equal(encoded.command, "terminal.toggle"); }), ); it.effect("rejects invalid rules", () => Effect.sync(() => { assert.isNull( compileResolvedKeybindingRule({ key: "mod+shift+d+o", command: "terminal.new", }), ); assert.isNull( compileResolvedKeybindingRule({ key: "mod+d", command: "terminal.split", when: "terminalFocus && (", }), ); assert.isNull( compileResolvedKeybindingRule({ key: "mod+d", command: "terminal.split", when: `${"!".repeat(300)}terminalFocus`, }), ); }), ); it.effect("formats invalid resolved keybinding rules with the custom message", () => Effect.sync(() => { const result = Schema.decodeUnknownExit(ResolvedKeybindingFromConfig)({ key: "mod+shift+d+o", command: "terminal.new", }); if (result._tag !== "Failure") { assert.fail("Expected invalid keybinding decode to fail"); } const detail = Cause.pretty(result.cause); assert.isTrue(detail.includes("Invalid keybinding rule")); assert.isFalse(detail.includes("Invalid data")); }), ); it.effect("bootstraps default keybindings when config file is missing", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const { keybindingsConfigPath } = yield* ServerConfig; assert.isFalse(yield* fs.exists(keybindingsConfigPath)); yield* Effect.gen(function* () { const keybindings = yield* Keybindings; yield* keybindings.syncDefaultKeybindingsOnStartup; }); const persisted = yield* readKeybindingsConfig(keybindingsConfigPath); assert.deepEqual(persisted, DEFAULT_KEYBINDINGS); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect("ships configurable thread navigation defaults", () => Effect.sync(() => { const defaultsByCommand = new Map( DEFAULT_KEYBINDINGS.map((binding) => [binding.command, binding.key] as const), ); assert.equal(defaultsByCommand.get("thread.previous"), "mod+shift+["); assert.equal(defaultsByCommand.get("thread.next"), "mod+shift+]"); assert.equal(defaultsByCommand.get("thread.jump.1"), "mod+1"); assert.equal(defaultsByCommand.get("thread.jump.9"), "mod+9"); assert.equal(defaultsByCommand.get("modelPicker.toggle"), "mod+shift+m"); assert.equal(defaultsByCommand.get("modelPicker.jump.1"), "mod+1"); assert.equal(defaultsByCommand.get("modelPicker.jump.9"), "mod+9"); }), ); it.effect("uses defaults in runtime when config is malformed without overriding file", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const { keybindingsConfigPath } = yield* ServerConfig; yield* fs.writeFileString(keybindingsConfigPath, "{ not-json"); const configState = yield* Effect.gen(function* () { const keybindings = yield* Keybindings; return yield* keybindings.loadConfigState; }); assert.deepEqual( configState.keybindings, compileResolvedKeybindingsConfig(DEFAULT_KEYBINDINGS), ); assert.deepEqual(configState.issues, [ { kind: "keybindings.malformed-config", message: configState.issues[0]?.message ?? "", }, ]); assert.equal(yield* fs.readFileString(keybindingsConfigPath), "{ not-json"); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect("ignores invalid entries in runtime and reports them as issues", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const { keybindingsConfigPath } = yield* ServerConfig; yield* fs.writeFileString( keybindingsConfigPath, JSON.stringify([ { key: "mod+j", command: "terminal.toggle" }, { key: "mod+shift+d+o", command: "terminal.new" }, { key: "mod+x", command: "invalid.command" }, ]), ); const configState = yield* Effect.gen(function* () { const keybindings = yield* Keybindings; return yield* keybindings.loadConfigState; }); assert.isTrue(configState.keybindings.some((entry) => entry.command === "terminal.toggle")); assert.isFalse( configState.keybindings.some((entry) => String(entry.command) === "invalid.command"), ); assert.deepEqual(configState.issues, [ { kind: "keybindings.invalid-entry", index: 1, message: configState.issues[0]?.message ?? "", }, { kind: "keybindings.invalid-entry", index: 2, message: configState.issues[1]?.message ?? "", }, ]); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect( "upserts missing default keybindings on startup without overriding existing command rules", () => Effect.gen(function* () { const { keybindingsConfigPath } = yield* ServerConfig; yield* writeKeybindingsConfig(keybindingsConfigPath, [ { key: "mod+shift+t", command: "terminal.toggle" }, { key: "mod+shift+r", command: "script.run-tests.run" }, ]); yield* Effect.gen(function* () { const keybindings = yield* Keybindings; yield* keybindings.syncDefaultKeybindingsOnStartup; }); const persisted = yield* readKeybindingsConfig(keybindingsConfigPath); const byCommand = new Map(persisted.map((entry) => [entry.command, entry])); const persistedToggle = byCommand.get("terminal.toggle"); assert.isNotNull(persistedToggle); assert.equal(persistedToggle?.key, "mod+shift+t"); assert.isFalse( persisted.some((entry) => entry.command === "terminal.toggle" && entry.key === "mod+j"), ); for (const defaultRule of DEFAULT_KEYBINDINGS) { assert.isTrue(byCommand.has(defaultRule.command), `expected ${defaultRule.command}`); } assert.isTrue(byCommand.has("script.run-tests.run")); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect("skips conflicting default keybindings on startup and logs a detailed warning", () => { const messages: string[] = []; const logger = Logger.make(({ message }) => { messages.push(String(message)); }); return Effect.gen(function* () { const { keybindingsConfigPath } = yield* ServerConfig; yield* writeKeybindingsConfig(keybindingsConfigPath, [ { key: "mod+j", command: "script.custom-action.run" }, ]); yield* Effect.gen(function* () { const keybindings = yield* Keybindings; yield* keybindings.syncDefaultKeybindingsOnStartup; }); const persisted = yield* readKeybindingsConfig(keybindingsConfigPath); assert.isFalse(persisted.some((entry) => entry.command === "terminal.toggle")); assert.isTrue(persisted.some((entry) => entry.command === "script.custom-action.run")); assert.isTrue( messages.some((message) => message.includes("skipping default keybinding due to shortcut conflict"), ), ); }).pipe( Effect.provide( Layer.mergeAll( makeKeybindingsLayer(), Logger.layer([logger], { mergeWithExisting: false }), ), ), ); }); it.effect("upserts custom keybindings to configured path", () => Effect.gen(function* () { const { keybindingsConfigPath } = yield* ServerConfig; yield* writeKeybindingsConfig(keybindingsConfigPath, [ { key: "mod+j", command: "terminal.toggle" }, ]); const resolved = yield* Effect.gen(function* () { const keybindings = yield* Keybindings; return yield* keybindings.upsertKeybindingRule({ key: "mod+shift+r", command: "script.run-tests.run", }); }); const persisted = yield* readKeybindingsConfig(keybindingsConfigPath); const persistedView = persisted.map(({ key, command }) => ({ key, command })); assert.deepEqual(persistedView, [ { key: "mod+j", command: "terminal.toggle" }, { key: "mod+shift+r", command: "script.run-tests.run" }, ]); assert.isTrue(resolved.some((entry) => entry.command === "script.run-tests.run")); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect("replaces existing custom keybinding for the same command", () => Effect.gen(function* () { const { keybindingsConfigPath } = yield* ServerConfig; yield* writeKeybindingsConfig(keybindingsConfigPath, [ { key: "mod+r", command: "script.run-tests.run" }, ]); yield* Effect.gen(function* () { const keybindings = yield* Keybindings; return yield* keybindings.upsertKeybindingRule({ key: "mod+shift+r", command: "script.run-tests.run", }); }); const persisted = yield* readKeybindingsConfig(keybindingsConfigPath); const persistedView = persisted.map(({ key, command }) => ({ key, command })); assert.deepEqual(persistedView, [{ key: "mod+shift+r", command: "script.run-tests.run" }]); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect("refuses to overwrite malformed keybindings config", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const { keybindingsConfigPath } = yield* ServerConfig; yield* fs.writeFileString(keybindingsConfigPath, "{ not-json"); const result = yield* Effect.gen(function* () { const keybindings = yield* Keybindings; return yield* keybindings.upsertKeybindingRule({ key: "mod+shift+r", command: "script.run-tests.run", }); }).pipe(toDetailResult); assertFailure(result, "expected JSON array"); const persistedRaw = yield* fs.readFileString(keybindingsConfigPath); assert.equal(persistedRaw, "{ not-json"); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect("reports non-array config parse errors without duplicate prefix", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const { keybindingsConfigPath } = yield* ServerConfig; yield* fs.writeFileString( keybindingsConfigPath, '{"key":"mod+j","command":"terminal.toggle"}', ); const firstResult = yield* Effect.gen(function* () { const keybindings = yield* Keybindings; return yield* keybindings.upsertKeybindingRule({ key: "mod+shift+r", command: "script.run-tests.run", }); }).pipe(toDetailResult); assertFailure(firstResult, "expected JSON array"); const secondResult = yield* Effect.gen(function* () { const keybindings = yield* Keybindings; return yield* keybindings.upsertKeybindingRule({ key: "mod+shift+r", command: "script.run-tests.run", }); }).pipe(toDetailResult); assertFailure(secondResult, "expected JSON array"); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect("fails when config directory is not writable", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const { keybindingsConfigPath } = yield* ServerConfig; const { dirname } = yield* Path.Path; yield* writeKeybindingsConfig(keybindingsConfigPath, [ { key: "mod+j", command: "terminal.toggle" }, ]); yield* fs.chmod(dirname(keybindingsConfigPath), 0o500); const result = yield* Effect.gen(function* () { const keybindings = yield* Keybindings; return yield* keybindings.upsertKeybindingRule({ key: "mod+shift+r", command: "script.run-tests.run", }); }).pipe(toDetailResult); assertFailure(result, "failed to write keybindings config"); yield* fs.chmod(dirname(keybindingsConfigPath), 0o700); const persisted = yield* readKeybindingsConfig(keybindingsConfigPath); const persistedView = persisted.map(({ key, command }) => ({ key, command })); assert.deepEqual(persistedView, [{ key: "mod+j", command: "terminal.toggle" }]); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect("caches loaded resolved config across repeated reads", () => Effect.gen(function* () { const { keybindingsConfigPath } = yield* ServerConfig; yield* writeKeybindingsConfig(keybindingsConfigPath, [ { key: "mod+j", command: "terminal.toggle" }, ]); const [first, second] = yield* Effect.gen(function* () { const keybindings = yield* Keybindings; const firstLoad = (yield* keybindings.loadConfigState).keybindings; const secondLoad = (yield* keybindings.loadConfigState).keybindings; return [firstLoad, secondLoad] as const; }); assert.deepEqual(first, second); assert.isTrue(second.some((entry) => entry.command === "terminal.toggle")); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect("updates cached resolved config after upsert", () => Effect.gen(function* () { const { keybindingsConfigPath } = yield* ServerConfig; yield* writeKeybindingsConfig(keybindingsConfigPath, [ { key: "mod+j", command: "terminal.toggle" }, ]); const loadedAfterUpsert = yield* Effect.gen(function* () { const keybindings = yield* Keybindings; yield* keybindings.loadConfigState; yield* keybindings.upsertKeybindingRule({ key: "mod+shift+r", command: "script.run-tests.run", }); return (yield* keybindings.loadConfigState).keybindings; }); assert.isTrue(loadedAfterUpsert.some((entry) => entry.command === "script.run-tests.run")); assert.isTrue(loadedAfterUpsert.some((entry) => entry.command === "terminal.toggle")); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect("serializes concurrent upserts to avoid lost updates", () => Effect.gen(function* () { const { keybindingsConfigPath } = yield* ServerConfig; yield* writeKeybindingsConfig(keybindingsConfigPath, []); const commands = Array.from( { length: 20 },Lines 1–500 of 523. Load more
import { KeybindingCommand, KeybindingRule, KeybindingsConfig } from "@t3tools/contracts"; import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, it } from "@effect/vitest"; import { assertFailure } from "@effect/vitest/utils"; import { Cause, Effect, FileSystem, Layer, Logger, Path, Schema } from "effect"; import { ServerConfig } from "./config.ts"; import { DEFAULT_KEYBINDINGS, Keybindings, KeybindingsLive, ResolvedKeybindingFromConfig, compileResolvedKeybindingRule, compileResolvedKeybindingsConfig, parseKeybindingShortcut, } from "./keybindings.ts"; import { KeybindingsConfigError } from "@t3tools/contracts"; const KeybindingsConfigJson = Schema.fromJsonString(KeybindingsConfig); const makeKeybindingsLayer = () => { return KeybindingsLive.pipe( Layer.provideMerge( Layer.fresh( ServerConfig.layerTest(process.cwd(), { prefix: "t3code-keybindings-test-", }), ), ), ); }; const toDetailResult = <A, R>(effect: Effect.Effect<A, KeybindingsConfigError, R>) => effect.pipe( Effect.mapError((error) => error.detail), Effect.result, ); const writeKeybindingsConfig = (configPath: string, rules: readonly KeybindingRule[]) => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const path = yield* Path.Path; const encoded = yield* Schema.encodeEffect(KeybindingsConfigJson)(rules); yield* fileSystem.makeDirectory(path.dirname(configPath), { recursive: true }); yield* fileSystem.writeFileString(configPath, encoded); }); const readKeybindingsConfig = (configPath: string) => Effect.gen(function* () { const fileSystem = yield* FileSystem.FileSystem; const rawConfig = yield* fileSystem.readFileString(configPath); return yield* Schema.decodeUnknownEffect(KeybindingsConfigJson)(rawConfig); }); it.layer(NodeServices.layer)("keybindings", (it) => { it.effect("parses shortcuts including plus key", () => Effect.sync(() => { assert.deepEqual(parseKeybindingShortcut("mod+j"), { key: "j", metaKey: false, ctrlKey: false, shiftKey: false, altKey: false, modKey: true, }); assert.deepEqual(parseKeybindingShortcut("mod++"), { key: "+", metaKey: false, ctrlKey: false, shiftKey: false, altKey: false, modKey: true, }); }), ); it.effect("compiles valid rule with parsed when AST", () => Effect.sync(() => { const compiled = compileResolvedKeybindingRule({ key: "mod+d", command: "terminal.split", when: "terminalOpen && !terminalFocus", }); assert.deepEqual(compiled, { command: "terminal.split", shortcut: { key: "d", metaKey: false, ctrlKey: false, shiftKey: false, altKey: false, modKey: true, }, whenAst: { type: "and", left: { type: "identifier", name: "terminalOpen" }, right: { type: "not", node: { type: "identifier", name: "terminalFocus" }, }, }, }); }), ); it.effect("encodes resolved plus-key shortcuts", () => Effect.gen(function* () { const encoded = yield* Schema.encodeEffect(ResolvedKeybindingFromConfig)({ command: "terminal.toggle", shortcut: { key: "+", metaKey: false, ctrlKey: false, shiftKey: false, altKey: false, modKey: true, }, }); assert.equal(encoded.key, "mod++"); assert.equal(encoded.command, "terminal.toggle"); }), ); it.effect("rejects invalid rules", () => Effect.sync(() => { assert.isNull( compileResolvedKeybindingRule({ key: "mod+shift+d+o", command: "terminal.new", }), ); assert.isNull( compileResolvedKeybindingRule({ key: "mod+d", command: "terminal.split", when: "terminalFocus && (", }), ); assert.isNull( compileResolvedKeybindingRule({ key: "mod+d", command: "terminal.split", when: `${"!".repeat(300)}terminalFocus`, }), ); }), ); it.effect("formats invalid resolved keybinding rules with the custom message", () => Effect.sync(() => { const result = Schema.decodeUnknownExit(ResolvedKeybindingFromConfig)({ key: "mod+shift+d+o", command: "terminal.new", }); if (result._tag !== "Failure") { assert.fail("Expected invalid keybinding decode to fail"); } const detail = Cause.pretty(result.cause); assert.isTrue(detail.includes("Invalid keybinding rule")); assert.isFalse(detail.includes("Invalid data")); }), ); it.effect("bootstraps default keybindings when config file is missing", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const { keybindingsConfigPath } = yield* ServerConfig; assert.isFalse(yield* fs.exists(keybindingsConfigPath)); yield* Effect.gen(function* () { const keybindings = yield* Keybindings; yield* keybindings.syncDefaultKeybindingsOnStartup; }); const persisted = yield* readKeybindingsConfig(keybindingsConfigPath); assert.deepEqual(persisted, DEFAULT_KEYBINDINGS); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect("ships configurable thread navigation defaults", () => Effect.sync(() => { const defaultsByCommand = new Map( DEFAULT_KEYBINDINGS.map((binding) => [binding.command, binding.key] as const), ); assert.equal(defaultsByCommand.get("thread.previous"), "mod+shift+["); assert.equal(defaultsByCommand.get("thread.next"), "mod+shift+]"); assert.equal(defaultsByCommand.get("thread.jump.1"), "mod+1"); assert.equal(defaultsByCommand.get("thread.jump.9"), "mod+9"); assert.equal(defaultsByCommand.get("modelPicker.toggle"), "mod+shift+m"); assert.equal(defaultsByCommand.get("modelPicker.jump.1"), "mod+1"); assert.equal(defaultsByCommand.get("modelPicker.jump.9"), "mod+9"); }), ); it.effect("uses defaults in runtime when config is malformed without overriding file", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const { keybindingsConfigPath } = yield* ServerConfig; yield* fs.writeFileString(keybindingsConfigPath, "{ not-json"); const configState = yield* Effect.gen(function* () { const keybindings = yield* Keybindings; return yield* keybindings.loadConfigState; }); assert.deepEqual( configState.keybindings, compileResolvedKeybindingsConfig(DEFAULT_KEYBINDINGS), ); assert.deepEqual(configState.issues, [ { kind: "keybindings.malformed-config", message: configState.issues[0]?.message ?? "", }, ]); assert.equal(yield* fs.readFileString(keybindingsConfigPath), "{ not-json"); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect("ignores invalid entries in runtime and reports them as issues", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const { keybindingsConfigPath } = yield* ServerConfig; yield* fs.writeFileString( keybindingsConfigPath, JSON.stringify([ { key: "mod+j", command: "terminal.toggle" }, { key: "mod+shift+d+o", command: "terminal.new" }, { key: "mod+x", command: "invalid.command" }, ]), ); const configState = yield* Effect.gen(function* () { const keybindings = yield* Keybindings; return yield* keybindings.loadConfigState; }); assert.isTrue(configState.keybindings.some((entry) => entry.command === "terminal.toggle")); assert.isFalse( configState.keybindings.some((entry) => String(entry.command) === "invalid.command"), ); assert.deepEqual(configState.issues, [ { kind: "keybindings.invalid-entry", index: 1, message: configState.issues[0]?.message ?? "", }, { kind: "keybindings.invalid-entry", index: 2, message: configState.issues[1]?.message ?? "", }, ]); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect( "upserts missing default keybindings on startup without overriding existing command rules", () => Effect.gen(function* () { const { keybindingsConfigPath } = yield* ServerConfig; yield* writeKeybindingsConfig(keybindingsConfigPath, [ { key: "mod+shift+t", command: "terminal.toggle" }, { key: "mod+shift+r", command: "script.run-tests.run" }, ]); yield* Effect.gen(function* () { const keybindings = yield* Keybindings; yield* keybindings.syncDefaultKeybindingsOnStartup; }); const persisted = yield* readKeybindingsConfig(keybindingsConfigPath); const byCommand = new Map(persisted.map((entry) => [entry.command, entry])); const persistedToggle = byCommand.get("terminal.toggle"); assert.isNotNull(persistedToggle); assert.equal(persistedToggle?.key, "mod+shift+t"); assert.isFalse( persisted.some((entry) => entry.command === "terminal.toggle" && entry.key === "mod+j"), ); for (const defaultRule of DEFAULT_KEYBINDINGS) { assert.isTrue(byCommand.has(defaultRule.command), `expected ${defaultRule.command}`); } assert.isTrue(byCommand.has("script.run-tests.run")); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect("skips conflicting default keybindings on startup and logs a detailed warning", () => { const messages: string[] = []; const logger = Logger.make(({ message }) => { messages.push(String(message)); }); return Effect.gen(function* () { const { keybindingsConfigPath } = yield* ServerConfig; yield* writeKeybindingsConfig(keybindingsConfigPath, [ { key: "mod+j", command: "script.custom-action.run" }, ]); yield* Effect.gen(function* () { const keybindings = yield* Keybindings; yield* keybindings.syncDefaultKeybindingsOnStartup; }); const persisted = yield* readKeybindingsConfig(keybindingsConfigPath); assert.isFalse(persisted.some((entry) => entry.command === "terminal.toggle")); assert.isTrue(persisted.some((entry) => entry.command === "script.custom-action.run")); assert.isTrue( messages.some((message) => message.includes("skipping default keybinding due to shortcut conflict"), ), ); }).pipe( Effect.provide( Layer.mergeAll( makeKeybindingsLayer(), Logger.layer([logger], { mergeWithExisting: false }), ), ), ); }); it.effect("upserts custom keybindings to configured path", () => Effect.gen(function* () { const { keybindingsConfigPath } = yield* ServerConfig; yield* writeKeybindingsConfig(keybindingsConfigPath, [ { key: "mod+j", command: "terminal.toggle" }, ]); const resolved = yield* Effect.gen(function* () { const keybindings = yield* Keybindings; return yield* keybindings.upsertKeybindingRule({ key: "mod+shift+r", command: "script.run-tests.run", }); }); const persisted = yield* readKeybindingsConfig(keybindingsConfigPath); const persistedView = persisted.map(({ key, command }) => ({ key, command })); assert.deepEqual(persistedView, [ { key: "mod+j", command: "terminal.toggle" }, { key: "mod+shift+r", command: "script.run-tests.run" }, ]); assert.isTrue(resolved.some((entry) => entry.command === "script.run-tests.run")); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect("replaces existing custom keybinding for the same command", () => Effect.gen(function* () { const { keybindingsConfigPath } = yield* ServerConfig; yield* writeKeybindingsConfig(keybindingsConfigPath, [ { key: "mod+r", command: "script.run-tests.run" }, ]); yield* Effect.gen(function* () { const keybindings = yield* Keybindings; return yield* keybindings.upsertKeybindingRule({ key: "mod+shift+r", command: "script.run-tests.run", }); }); const persisted = yield* readKeybindingsConfig(keybindingsConfigPath); const persistedView = persisted.map(({ key, command }) => ({ key, command })); assert.deepEqual(persistedView, [{ key: "mod+shift+r", command: "script.run-tests.run" }]); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect("refuses to overwrite malformed keybindings config", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const { keybindingsConfigPath } = yield* ServerConfig; yield* fs.writeFileString(keybindingsConfigPath, "{ not-json"); const result = yield* Effect.gen(function* () { const keybindings = yield* Keybindings; return yield* keybindings.upsertKeybindingRule({ key: "mod+shift+r", command: "script.run-tests.run", }); }).pipe(toDetailResult); assertFailure(result, "expected JSON array"); const persistedRaw = yield* fs.readFileString(keybindingsConfigPath); assert.equal(persistedRaw, "{ not-json"); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect("reports non-array config parse errors without duplicate prefix", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const { keybindingsConfigPath } = yield* ServerConfig; yield* fs.writeFileString( keybindingsConfigPath, '{"key":"mod+j","command":"terminal.toggle"}', ); const firstResult = yield* Effect.gen(function* () { const keybindings = yield* Keybindings; return yield* keybindings.upsertKeybindingRule({ key: "mod+shift+r", command: "script.run-tests.run", }); }).pipe(toDetailResult); assertFailure(firstResult, "expected JSON array"); const secondResult = yield* Effect.gen(function* () { const keybindings = yield* Keybindings; return yield* keybindings.upsertKeybindingRule({ key: "mod+shift+r", command: "script.run-tests.run", }); }).pipe(toDetailResult); assertFailure(secondResult, "expected JSON array"); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect("fails when config directory is not writable", () => Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const { keybindingsConfigPath } = yield* ServerConfig; const { dirname } = yield* Path.Path; yield* writeKeybindingsConfig(keybindingsConfigPath, [ { key: "mod+j", command: "terminal.toggle" }, ]); yield* fs.chmod(dirname(keybindingsConfigPath), 0o500); const result = yield* Effect.gen(function* () { const keybindings = yield* Keybindings; return yield* keybindings.upsertKeybindingRule({ key: "mod+shift+r", command: "script.run-tests.run", }); }).pipe(toDetailResult); assertFailure(result, "failed to write keybindings config"); yield* fs.chmod(dirname(keybindingsConfigPath), 0o700); const persisted = yield* readKeybindingsConfig(keybindingsConfigPath); const persistedView = persisted.map(({ key, command }) => ({ key, command })); assert.deepEqual(persistedView, [{ key: "mod+j", command: "terminal.toggle" }]); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect("caches loaded resolved config across repeated reads", () => Effect.gen(function* () { const { keybindingsConfigPath } = yield* ServerConfig; yield* writeKeybindingsConfig(keybindingsConfigPath, [ { key: "mod+j", command: "terminal.toggle" }, ]); const [first, second] = yield* Effect.gen(function* () { const keybindings = yield* Keybindings; const firstLoad = (yield* keybindings.loadConfigState).keybindings; const secondLoad = (yield* keybindings.loadConfigState).keybindings; return [firstLoad, secondLoad] as const; }); assert.deepEqual(first, second); assert.isTrue(second.some((entry) => entry.command === "terminal.toggle")); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect("updates cached resolved config after upsert", () => Effect.gen(function* () { const { keybindingsConfigPath } = yield* ServerConfig; yield* writeKeybindingsConfig(keybindingsConfigPath, [ { key: "mod+j", command: "terminal.toggle" }, ]); const loadedAfterUpsert = yield* Effect.gen(function* () { const keybindings = yield* Keybindings; yield* keybindings.loadConfigState; yield* keybindings.upsertKeybindingRule({ key: "mod+shift+r", command: "script.run-tests.run", }); return (yield* keybindings.loadConfigState).keybindings; }); assert.isTrue(loadedAfterUpsert.some((entry) => entry.command === "script.run-tests.run")); assert.isTrue(loadedAfterUpsert.some((entry) => entry.command === "terminal.toggle")); }).pipe(Effect.provide(makeKeybindingsLayer())), ); it.effect("serializes concurrent upserts to avoid lost updates", () => Effect.gen(function* () { const { keybindingsConfigPath } = yield* ServerConfig; yield* writeKeybindingsConfig(keybindingsConfigPath, []); const commands = Array.from( { length: 20 },