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 { Effect, FileSystem, Layer, Logger, Path, Schema } from "effect";import { ServerConfig, type ServerConfigShape } from "./config"; import { DEFAULT_KEYBINDINGS, Keybindings, KeybindingsConfigError, KeybindingsLive, ResolvedKeybindingFromConfig, compileResolvedKeybindingRule, compileResolvedKeybindingsConfig, parseKeybindingShortcut,} from "./keybindings"; const KeybindingsConfigJson = Schema.fromJsonString(KeybindingsConfig);const makeKeybindingsLayer = () => KeybindingsLive.pipe( Layer.provideMerge( Layer.effect( ServerConfig, Effect.gen(function* () { const fs = yield* FileSystem.FileSystem; const { join } = yield* Path.Path; const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3code-server-config-test-" }); const configPath = join(dir, "keybindings.json"); return { keybindingsConfigPath: configPath } as ServerConfigShape; }), ), ), ); 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 encoded = yield* Schema.encodeEffect(KeybindingsConfigJson)(rules); 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("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("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 }, (_, index): KeybindingCommand => `script.concurrent-${index}.run`, ); yield* Effect.gen(function* () { const keybindings = yield* Keybindings; yield* Effect.all( commands.map((command, index) => keybindings.upsertKeybindingRule({ key: `mod+${String.fromCharCode(97 + index)}`, command, }), ), { concurrency: "unbounded", discard: true }, ); }); const persisted = yield* readKeybindingsConfig(keybindingsConfigPath); const persistedCommands = new Set(persisted.map((entry) => entry.command)); for (const command of commands) { assert.isTrue(persistedCommands.has(command), `expected persisted command ${command}`); } }).pipe(Effect.provide(makeKeybindingsLayer())), );});
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 { Effect, FileSystem, Layer, Logger, Path, Schema } from "effect";
import { ServerConfig, type ServerConfigShape } from "./config";
import {
DEFAULT_KEYBINDINGS,
Keybindings,
KeybindingsConfigError,
KeybindingsLive,
ResolvedKeybindingFromConfig,
compileResolvedKeybindingRule,
compileResolvedKeybindingsConfig,
parseKeybindingShortcut,
} from "./keybindings";
const KeybindingsConfigJson = Schema.fromJsonString(KeybindingsConfig);
const makeKeybindingsLayer = () =>
KeybindingsLive.pipe(
Layer.provideMerge(
Layer.effect(
ServerConfig,
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const { join } = yield* Path.Path;
const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3code-server-config-test-" });
const configPath = join(dir, "keybindings.json");
return { keybindingsConfigPath: configPath } as ServerConfigShape;
}),
),
),
);
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 encoded = yield* Schema.encodeEffect(KeybindingsConfigJson)(rules);
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("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("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 },
(_, index): KeybindingCommand => `script.concurrent-${index}.run`,
);
yield* Effect.gen(function* () {
const keybindings = yield* Keybindings;
yield* Effect.all(
commands.map((command, index) =>
keybindings.upsertKeybindingRule({
key: `mod+${String.fromCharCode(97 + index)}`,
command,
}),
),
{ concurrency: "unbounded", discard: true },
);
});
const persisted = yield* readKeybindingsConfig(keybindingsConfigPath);
const persistedCommands = new Set(persisted.map((entry) => entry.command));
for (const command of commands) {
assert.isTrue(persistedCommands.has(command), `expected persisted command ${command}`);
}
}).pipe(Effect.provide(makeKeybindingsLayer())),
);
});