523 lines · 18.4 KB · TypeScript
RawDownload
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.