oh-my-opencode 5.0.0-beta.31 → 5.0.0-beta.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +136 -44
- package/dist/cli-node/index.js +136 -44
- package/dist/features/background-agent/parent-wake-notifier-types.d.ts +1 -1
- package/dist/features/background-agent/parent-wake-prompt-dispatch.d.ts +1 -1
- package/dist/features/background-agent/parent-wake-session-inspector.d.ts +1 -1
- package/dist/features/monitor/manager-internals.d.ts +1 -1
- package/dist/features/monitor/output-injector-types.d.ts +1 -1
- package/dist/features/team-mode/tools/messaging-live-delivery-recipient.d.ts +1 -0
- package/dist/features/team-mode/tools/messaging-runtime.d.ts +1 -0
- package/dist/hooks/anthropic-context-window-limit-recovery/tool-result-storage.d.ts +11 -0
- package/dist/index.js +286 -99
- package/dist/skills/ultimate-browsing/references/insane-search/README.md +9 -2
- package/dist/tools/session-manager/directory-filter.d.ts +2 -0
- package/dist/tui.js +109 -23
- package/package.json +16 -15
- package/packages/lsp-core/src/lsp/client-diagnostics-freshness.integration.test.ts +47 -19
- package/packages/lsp-core/src/lsp/client-diagnostics-pull-timeout.test.ts +64 -0
- package/packages/lsp-core/src/lsp/client.ts +6 -3
- package/packages/lsp-core/src/lsp/controlled-clock-test-support.ts +61 -0
- package/packages/lsp-core/src/lsp/fixtures/workspace-edit-server.mjs +10 -3
- package/packages/lsp-core/src/lsp/timer-provider.ts +13 -0
- package/packages/lsp-core/src/lsp/transport-request-timeout.test.ts +73 -0
- package/packages/lsp-core/src/lsp/transport.ts +7 -2
- package/packages/lsp-core/src/lsp/workspace-document-state.test.ts +84 -1
- package/packages/lsp-core/src/lsp/workspace-document-state.ts +30 -11
- package/packages/lsp-core/src/tools/parameters.test.ts +51 -0
- package/packages/lsp-daemon/dist/cli.js +41 -16
- package/packages/lsp-daemon/dist/client.js +41 -16
- package/packages/lsp-daemon/dist/index.js +41 -16
- package/packages/lsp-tools-mcp/dist/cli.js +41 -16
- package/packages/lsp-tools-mcp/dist/lsp/manager.js +41 -16
- package/packages/lsp-tools-mcp/dist/mcp.js +41 -16
- package/packages/lsp-tools-mcp/dist/tools.js +41 -16
- package/packages/lsp-tools-mcp/package.json +1 -1
- package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/bootstrap/package.json +1 -1
- package/packages/omo-codex/plugin/components/codegraph/dist/cli.js +3 -17
- package/packages/omo-codex/plugin/components/codegraph/dist/serve.js +3 -17
- package/packages/omo-codex/plugin/components/codegraph/package.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
- package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/lazycodex-executor-verify/package.json +1 -1
- package/packages/omo-codex/plugin/components/lsp/dist/.omo-runtime-manifest.json +3 -3
- package/packages/omo-codex/plugin/components/lsp/dist/cli.js +39 -16
- package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
- package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/rules/package.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/teammode/package.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/AGENTS.md +5 -4
- package/packages/omo-codex/plugin/components/ultrawork/dist/cli.js +489 -4
- package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
- package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -2
- package/packages/omo-codex/plugin/components/ultrawork/scripts/sync-directive.mjs +30 -14
- package/packages/omo-codex/plugin/components/ultrawork/src/directive-content.ts +4 -0
- package/packages/omo-codex/plugin/components/ultrawork/src/directive.ts +5 -2
- package/packages/omo-codex/plugin/components/ultrawork/test/directive-source.test.ts +14 -8
- package/packages/omo-codex/plugin/components/ultrawork/test/package-smoke.test.ts +12 -1
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/hooks/hooks.json +2 -2
- package/packages/omo-codex/plugin/components/ulw-execute-continuation/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/AGENTS.md +4 -2
- package/packages/omo-codex/plugin/components/ulw-loop/README.md +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-continuation.js +9 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.d.ts +14 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-reconciliation.js +10 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.d.ts +8 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint-template.js +113 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/checkpoint.js +7 -5
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-commands.js +2 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.d.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-output.js +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli-subcommands.js +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/cli.js +775 -312
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-instruction.js +18 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.d.ts +13 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/codex-goal-snapshot.js +14 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/domain-types.d.ts +17 -9
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-io.js +17 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.d.ts +13 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/plan-missing-recovery.js +19 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.d.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-aggregate.js +53 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.d.ts +9 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-artifacts.js +85 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate-fields.js +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/dist/quality-gate.js +41 -99
- package/packages/omo-codex/plugin/components/ulw-loop/dist/review-blockers.js +4 -2
- package/packages/omo-codex/plugin/components/ulw-loop/dist/spawn-guard.js +184 -14
- package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.d.ts +6 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/status-next-actions.js +33 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.d.ts +3 -0
- package/packages/omo-codex/plugin/components/ulw-loop/dist/surface.js +15 -0
- package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +4 -4
- package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/skills/ulw-loop/references/full-workflow.md +17 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-continuation.ts +7 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-reconciliation.ts +25 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint-template.ts +127 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/checkpoint.ts +8 -12
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-commands.ts +2 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-output.ts +1 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/cli-subcommands.ts +2 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-instruction.ts +18 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/codex-goal-snapshot.ts +23 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/domain-types.ts +19 -9
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-io.ts +18 -3
- package/packages/omo-codex/plugin/components/ulw-loop/src/plan-missing-recovery.ts +27 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-aggregate.ts +57 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-artifacts.ts +103 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate-fields.ts +1 -1
- package/packages/omo-codex/plugin/components/ulw-loop/src/quality-gate.ts +50 -116
- package/packages/omo-codex/plugin/components/ulw-loop/src/review-blockers.ts +4 -2
- package/packages/omo-codex/plugin/components/ulw-loop/src/spawn-guard.ts +186 -12
- package/packages/omo-codex/plugin/components/ulw-loop/src/status-next-actions.ts +36 -0
- package/packages/omo-codex/plugin/components/ulw-loop/src/surface.ts +22 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-final.test.ts +66 -3
- package/packages/omo-codex/plugin/components/ulw-loop/test/checkpoint-template.test.ts +92 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/cli-status-next-actions.test.ts +183 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/codex-goal-instruction.test.ts +13 -3
- package/packages/omo-codex/plugin/components/ulw-loop/test/guided-recovery.test.ts +105 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-aggregate.test.ts +101 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-roles.test.ts +16 -15
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate-senpi-surface.test.ts +131 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/quality-gate.test.ts +5 -0
- package/packages/omo-codex/plugin/components/ulw-loop/test/spawn-guard.test.ts +300 -3
- package/packages/omo-codex/plugin/components/ulw-loop/test/ultrawork-directive.test.ts +9 -3
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-git-bash-mcp-reminder.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-lsp-diagnostics-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-compact-resetting-project-rule-cache.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-codegraph-init-guidance.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-comments.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-lsp-diagnostics.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-checking-thread-title-hygiene.json +1 -1
- package/packages/omo-codex/plugin/hooks/post-tool-use-matching-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-enforcing-unlimited-goal-budget.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-guarding-ulw-loop-spawns.json +1 -1
- package/packages/omo-codex/plugin/hooks/pre-tool-use-recommending-git-bash-mcp.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-auto-update.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-bootstrap-provisioning.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-checking-codegraph-bootstrap.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/hooks/session-start-recording-session-telemetry.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/stop-checking-ulw-loop-resume.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-checking-ulw-execute-continuation.json +1 -1
- package/packages/omo-codex/plugin/hooks/subagent-stop-verifying-lazycodex-executor-evidence.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ultrawork-trigger.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-checking-ulw-loop-steering.json +1 -1
- package/packages/omo-codex/plugin/hooks/user-prompt-submit-loading-project-rules.json +1 -1
- package/packages/omo-codex/plugin/package-lock.json +13 -13
- package/packages/omo-codex/plugin/package.json +1 -1
- package/packages/omo-codex/plugin/scripts/sync-skills.mjs +31 -2
- package/packages/omo-codex/plugin/skills/ultimate-browsing/references/insane-search/README.md +9 -2
- package/packages/omo-codex/plugin/skills/ulw-loop/references/full-workflow.md +17 -0
- package/packages/omo-codex/plugin/test/sync-skills-test-support.mjs +11 -1
- package/packages/omo-codex/plugin/test/sync-skills.test.mjs +13 -0
- package/packages/omo-codex/plugin/test/ultrawork-skill-pointer.test.mjs +5 -2
- package/packages/omo-codex/scripts/install-dist/install-local.mjs +2 -2
- package/packages/shared-skills/skills/ultimate-browsing/references/insane-search/README.md +9 -2
- package/dist/features/builtin-skills/index.d.ts +0 -1
- package/dist/features/builtin-skills/skill-file-loader.d.ts +0 -1
- package/dist/features/builtin-skills/skills/agent-browser-skill.d.ts +0 -1
- package/dist/features/builtin-skills/skills/agent-browser-template.d.ts +0 -1
- package/dist/features/builtin-skills/skills/debugging.d.ts +0 -1
- package/dist/features/builtin-skills/skills/dev-browser.d.ts +0 -1
- package/dist/features/builtin-skills/skills/frontend.d.ts +0 -1
- package/dist/features/builtin-skills/skills/git-master-sections/commit-atomic-planning.d.ts +0 -1
- package/dist/features/builtin-skills/skills/git-master-sections/commit-context-analysis.d.ts +0 -1
- package/dist/features/builtin-skills/skills/git-master-sections/commit-execution-verification.d.ts +0 -1
- package/dist/features/builtin-skills/skills/git-master-sections/commit-workflow.d.ts +0 -1
- package/dist/features/builtin-skills/skills/git-master-sections/history-search-workflow.d.ts +0 -1
- package/dist/features/builtin-skills/skills/git-master-sections/overview.d.ts +0 -1
- package/dist/features/builtin-skills/skills/git-master-sections/quick-reference.d.ts +0 -1
- package/dist/features/builtin-skills/skills/git-master-sections/rebase-workflow.d.ts +0 -1
- package/dist/features/builtin-skills/skills/git-master-skill-metadata.d.ts +0 -1
- package/dist/features/builtin-skills/skills/git-master.d.ts +0 -1
- package/dist/features/builtin-skills/skills/index.d.ts +0 -1
- package/dist/features/builtin-skills/skills/init-deep.d.ts +0 -1
- package/dist/features/builtin-skills/skills/playwright-cli.d.ts +0 -1
- package/dist/features/builtin-skills/skills/playwright-mcp-skill.d.ts +0 -1
- package/dist/features/builtin-skills/skills/playwright.d.ts +0 -1
- package/dist/features/builtin-skills/skills/remove-ai-slops.d.ts +0 -1
- package/dist/features/builtin-skills/skills/review-work.d.ts +0 -1
- package/dist/features/builtin-skills/skills/security-research.d.ts +0 -1
- package/dist/features/builtin-skills/skills/security-review.d.ts +0 -1
- package/dist/features/builtin-skills/skills/team-mode.d.ts +0 -1
- package/dist/features/builtin-skills/skills/visual-qa.d.ts +0 -1
- package/dist/features/builtin-skills/skills.d.ts +0 -1
- package/dist/features/builtin-skills/types.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/agent-loader.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/command-loader.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/discovery-core.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/discovery-paths.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/discovery.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/hook-loader.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/index.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/install-path-resolver.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/installed-plugin-database.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/loaded-plugin.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/loader.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/mcp-server-loader.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/plugin-key.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/plugin-manifest.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/plugin-path-resolver.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/plugin-settings.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/scope-filter.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/skill-loader.d.ts +0 -1
- package/dist/features/claude-code-plugin-loader/types.d.ts +0 -1
- package/dist/shared/prompt-async-gate/message-inspection-error.d.ts +0 -1
- package/dist/shared/prompt-async-gate/pending-tool-turn.d.ts +0 -1
- package/dist/shared/prompt-async-gate/prompt-message-state.d.ts +0 -1
- package/dist/shared/prompt-async-gate/queue.d.ts +0 -1
- package/dist/shared/prompt-async-gate/recent-dispatches.d.ts +0 -1
- package/dist/shared/prompt-async-gate/reservations.d.ts +0 -1
- package/dist/shared/prompt-async-gate/semantic-dedupe.d.ts +0 -1
- package/dist/shared/prompt-async-gate/session-idle-dispatch.d.ts +0 -1
- package/dist/shared/prompt-async-gate/timing.d.ts +0 -1
- package/dist/shared/prompt-async-gate/types.d.ts +0 -1
- package/packages/omo-codex/plugin/components/ultrawork/directive.md +0 -483
- package/packages/omo-codex/plugin/components/ultrawork/skills/ultrawork/SKILL.md +0 -490
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { PassThrough } from "node:stream";
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "bun:test";
|
|
4
|
+
|
|
5
|
+
import { ControlledClock } from "./controlled-clock-test-support.js";
|
|
6
|
+
import { LspRequestTimeoutError } from "./errors.js";
|
|
7
|
+
import { JsonRpcConnection } from "./json-rpc-connection.js";
|
|
8
|
+
import { LspClientTransport } from "./transport.js";
|
|
9
|
+
import type { LspClientTimeoutOptions } from "./transport.js";
|
|
10
|
+
import type { ResolvedServer } from "./types.js";
|
|
11
|
+
|
|
12
|
+
const fakeServer: ResolvedServer = {
|
|
13
|
+
id: "controlled-clock-fake",
|
|
14
|
+
command: ["controlled-clock-fake"],
|
|
15
|
+
extensions: [".ts"],
|
|
16
|
+
priority: 0,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
class WiredTransport extends LspClientTransport {
|
|
20
|
+
constructor(timeouts: LspClientTimeoutOptions, connection: JsonRpcConnection) {
|
|
21
|
+
super("/tmp", fakeServer, timeouts);
|
|
22
|
+
this.connection = connection;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
request<T>(method: string, params: unknown, options: { timeoutMs?: number }): Promise<T> {
|
|
26
|
+
return this.sendRequest<T>(method, params, options);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function encodeMessage(message: Record<string, unknown>): string {
|
|
31
|
+
const body = JSON.stringify(message);
|
|
32
|
+
return `Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n${body}`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function makeWiredTransport(clock: ControlledClock): { transport: WiredTransport; serverToClient: PassThrough } {
|
|
36
|
+
const serverToClient = new PassThrough();
|
|
37
|
+
const clientToServer = new PassThrough();
|
|
38
|
+
const connection = new JsonRpcConnection(serverToClient, clientToServer);
|
|
39
|
+
connection.listen();
|
|
40
|
+
return { transport: new WiredTransport({ timerProvider: clock }, connection), serverToClient };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe("LspClientTransport request timeout via TimerProvider", () => {
|
|
44
|
+
it("#given a request the server never answers #when the injected clock passes the timeout #then the request rejects with LspRequestTimeoutError", async () => {
|
|
45
|
+
const clock = new ControlledClock();
|
|
46
|
+
const { transport } = makeWiredTransport(clock);
|
|
47
|
+
|
|
48
|
+
const pending = transport.request("textDocument/diagnostic", { textDocument: { uri: "file:///a.ts" } }, {
|
|
49
|
+
timeoutMs: 50,
|
|
50
|
+
});
|
|
51
|
+
await clock.waitForTimer(50);
|
|
52
|
+
expect(clock.scheduledDelays.at(-1)).toBe(50);
|
|
53
|
+
clock.advanceBy(50);
|
|
54
|
+
|
|
55
|
+
await expect(pending).rejects.toBeInstanceOf(LspRequestTimeoutError);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("#given a request answered before the deadline #when the injected clock later passes the timeout #then the settled result stands", async () => {
|
|
59
|
+
const clock = new ControlledClock();
|
|
60
|
+
const { transport, serverToClient } = makeWiredTransport(clock);
|
|
61
|
+
|
|
62
|
+
const pending = transport.request<{ items: unknown[] }>(
|
|
63
|
+
"textDocument/diagnostic",
|
|
64
|
+
{ textDocument: { uri: "file:///a.ts" } },
|
|
65
|
+
{ timeoutMs: 50 },
|
|
66
|
+
);
|
|
67
|
+
await clock.waitForTimer(50);
|
|
68
|
+
serverToClient.write(encodeMessage({ jsonrpc: "2.0", id: 1, result: { items: [] } }));
|
|
69
|
+
|
|
70
|
+
await expect(pending).resolves.toEqual({ items: [] });
|
|
71
|
+
clock.advanceBy(50);
|
|
72
|
+
});
|
|
73
|
+
});
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { reportBestEffortCleanupError } from "./cleanup-errors.js";
|
|
2
|
+
import { realTimerProvider } from "./timer-provider.js";
|
|
3
|
+
import type { TimerProvider } from "./timer-provider.js";
|
|
2
4
|
import { INIT_TIMEOUT_MS, REQUEST_TIMEOUT_MS, STOP_HARD_KILL_TIMEOUT_MS, STOP_SIGKILL_GRACE_MS } from "./constants.js";
|
|
3
5
|
import { LspConnectionClosedError, LspProcessExitedError, LspRequestTimeoutError } from "./errors.js";
|
|
4
6
|
import { JsonRpcConnection } from "./json-rpc-connection.js";
|
|
@@ -12,6 +14,7 @@ export { createLspSpawnEnv } from "./transport-protocol.js";
|
|
|
12
14
|
export interface LspClientTimeoutOptions {
|
|
13
15
|
requestTimeoutMs?: number;
|
|
14
16
|
initializeTimeoutMs?: number;
|
|
17
|
+
timerProvider?: TimerProvider;
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
type WorkspaceApplyEditHandler = (params: unknown) => Promise<WorkspaceApplyEditResponse>;
|
|
@@ -35,6 +38,7 @@ export class LspClientTransport {
|
|
|
35
38
|
protected readonly diagnosticsStore = new Map<string, Diagnostic[]>();
|
|
36
39
|
protected readonly requestTimeoutMs: number;
|
|
37
40
|
protected readonly initializeTimeoutMs: number;
|
|
41
|
+
protected readonly timerProvider: TimerProvider;
|
|
38
42
|
private workspaceApplyEditHandler: WorkspaceApplyEditHandler | null = null;
|
|
39
43
|
private diagnosticPullSupported = false;
|
|
40
44
|
private documentFormattingSupported = false;
|
|
@@ -46,6 +50,7 @@ export class LspClientTransport {
|
|
|
46
50
|
) {
|
|
47
51
|
this.requestTimeoutMs = timeouts.requestTimeoutMs ?? REQUEST_TIMEOUT_MS;
|
|
48
52
|
this.initializeTimeoutMs = timeouts.initializeTimeoutMs ?? INIT_TIMEOUT_MS;
|
|
53
|
+
this.timerProvider = timeouts.timerProvider ?? realTimerProvider;
|
|
49
54
|
}
|
|
50
55
|
|
|
51
56
|
pid(): number | undefined {
|
|
@@ -187,7 +192,7 @@ export class LspClientTransport {
|
|
|
187
192
|
const options = args[1];
|
|
188
193
|
const timeoutMs = options?.timeoutMs ?? this.requestTimeoutMs;
|
|
189
194
|
const timeoutController = new AbortController();
|
|
190
|
-
const timeoutHandle = setTimeout(() => {
|
|
195
|
+
const timeoutHandle = this.timerProvider.setTimeout(() => {
|
|
191
196
|
const stderrTail = this.stderrBuffer.slice(-5).join("\n");
|
|
192
197
|
timeoutController.abort(new LspRequestTimeoutError(method, stderrTail || undefined));
|
|
193
198
|
}, timeoutMs);
|
|
@@ -213,7 +218,7 @@ export class LspClientTransport {
|
|
|
213
218
|
}
|
|
214
219
|
throw error;
|
|
215
220
|
} finally {
|
|
216
|
-
clearTimeout(timeoutHandle);
|
|
221
|
+
this.timerProvider.clearTimeout(timeoutHandle);
|
|
217
222
|
combinedSignal.dispose();
|
|
218
223
|
}
|
|
219
224
|
}
|
|
@@ -1,8 +1,91 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
2
5
|
|
|
6
|
+
import { afterEach, describe, expect, it } from "bun:test";
|
|
7
|
+
|
|
8
|
+
import type { Diagnostic } from "./types.js";
|
|
3
9
|
import { WorkspaceDocumentState } from "./workspace-document-state.js";
|
|
4
10
|
import type { WorkspaceMutation } from "./workspace-edit.js";
|
|
5
11
|
|
|
12
|
+
const workspaces: string[] = [];
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
while (workspaces.length > 0) {
|
|
16
|
+
const workspace = workspaces.pop();
|
|
17
|
+
if (workspace) rmSync(workspace, { recursive: true, force: true });
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
function makeDocuments(): WorkspaceDocumentState {
|
|
22
|
+
return new WorkspaceDocumentState(
|
|
23
|
+
async () => {},
|
|
24
|
+
() => {},
|
|
25
|
+
{ versionlessPublishQuiescenceMs: 0 },
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function makeSourceFile(directorySegment: string): string {
|
|
30
|
+
const workspace = realpathSync(mkdtempSync(join(tmpdir(), "lsp-document-uri-")));
|
|
31
|
+
workspaces.push(workspace);
|
|
32
|
+
const directory = join(workspace, directorySegment);
|
|
33
|
+
mkdirSync(directory, { recursive: true });
|
|
34
|
+
const source = join(directory, "page.ts");
|
|
35
|
+
writeFileSync(source, "const value: number = 'text';\n", "utf-8");
|
|
36
|
+
return source;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const diagnostic: Diagnostic = {
|
|
40
|
+
range: { start: { line: 0, character: 6 }, end: { line: 0, character: 11 } },
|
|
41
|
+
severity: 1,
|
|
42
|
+
message: "Type 'string' is not assignable to type 'number'.",
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
describe("WorkspaceDocumentState published diagnostics under a re-encoded uri", () => {
|
|
46
|
+
it("#given a server that percent-encodes a path segment #when it publishes #then the diagnostics reach the open document", async () => {
|
|
47
|
+
const documents = makeDocuments();
|
|
48
|
+
const source = makeSourceFile("(marketing)");
|
|
49
|
+
await documents.openFile(source);
|
|
50
|
+
const snapshot = documents.captureDiagnosticSnapshot(source);
|
|
51
|
+
expect(snapshot).not.toBeNull();
|
|
52
|
+
const publishedUri = snapshot!.uri.replaceAll("(", "%28").replaceAll(")", "%29");
|
|
53
|
+
expect(publishedUri).not.toBe(snapshot!.uri);
|
|
54
|
+
|
|
55
|
+
documents.recordPublishedDiagnostics({ uri: publishedUri, diagnostics: [diagnostic], version: snapshot!.version });
|
|
56
|
+
|
|
57
|
+
expect(documents.getStoredDiagnostics(snapshot!.uri)).toEqual([diagnostic]);
|
|
58
|
+
expect(documents.resolvePushDiagnostics(snapshot!)).toEqual({ status: "ready", diagnostics: [diagnostic] });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it.skipIf(process.platform !== "win32")(
|
|
62
|
+
"#given a server that lowercases and percent-encodes the drive #when it publishes #then the diagnostics reach the open document",
|
|
63
|
+
async () => {
|
|
64
|
+
const documents = makeDocuments();
|
|
65
|
+
const source = makeSourceFile("routes");
|
|
66
|
+
await documents.openFile(source);
|
|
67
|
+
const snapshot = documents.captureDiagnosticSnapshot(source);
|
|
68
|
+
expect(snapshot).not.toBeNull();
|
|
69
|
+
const publishedUri = snapshot!.uri.replace(/^file:\/\/\/([A-Za-z]):/, (_match, drive: string) => `file:///${drive.toLowerCase()}%3A`);
|
|
70
|
+
expect(publishedUri).not.toBe(snapshot!.uri);
|
|
71
|
+
|
|
72
|
+
documents.recordPublishedDiagnostics({ uri: publishedUri, diagnostics: [diagnostic], version: snapshot!.version });
|
|
73
|
+
|
|
74
|
+
expect(documents.getStoredDiagnostics(snapshot!.uri)).toEqual([diagnostic]);
|
|
75
|
+
expect(documents.resolvePushDiagnostics(snapshot!)).toEqual({ status: "ready", diagnostics: [diagnostic] });
|
|
76
|
+
},
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
it("#given a document opened from a path the client already encodes #when the server echoes it verbatim #then the outgoing uri is unchanged", async () => {
|
|
80
|
+
const documents = makeDocuments();
|
|
81
|
+
const source = makeSourceFile("plain");
|
|
82
|
+
await documents.openFile(source);
|
|
83
|
+
const snapshot = documents.captureDiagnosticSnapshot(source);
|
|
84
|
+
|
|
85
|
+
expect(snapshot?.uri).toBe(pathToFileURL(source).href);
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
|
|
6
89
|
describe("WorkspaceDocumentState watched-file bounds", () => {
|
|
7
90
|
it("#given more closed-file mutations than one batch #when synchronized #then every notification stays bounded", async () => {
|
|
8
91
|
const notifications: unknown[] = [];
|
|
@@ -5,6 +5,7 @@ import { pathToFileURL } from "node:url";
|
|
|
5
5
|
import { effectiveExtension } from "./effective-extension.js";
|
|
6
6
|
import { getLanguageId } from "./language-mappings.js";
|
|
7
7
|
import type { Diagnostic } from "./types.js";
|
|
8
|
+
import type { TimerProvider } from "./timer-provider.js";
|
|
8
9
|
import type { PlannedWorkspaceOperation, WorkspaceMutation, WorkspaceMutationDelta } from "./workspace-edit-types.js";
|
|
9
10
|
|
|
10
11
|
const WATCHED_FILE_BATCH_SIZE = 128;
|
|
@@ -76,6 +77,7 @@ export type PushDiagnosticsResolution =
|
|
|
76
77
|
|
|
77
78
|
export interface WorkspaceDocumentStateOptions {
|
|
78
79
|
readonly now?: () => number;
|
|
80
|
+
readonly timerProvider?: TimerProvider;
|
|
79
81
|
readonly versionlessPublishQuiescenceMs?: number;
|
|
80
82
|
}
|
|
81
83
|
|
|
@@ -88,6 +90,17 @@ function canonicalPath(filePath: string): string {
|
|
|
88
90
|
}
|
|
89
91
|
}
|
|
90
92
|
|
|
93
|
+
function normalizeDocumentUri(uri: string): string {
|
|
94
|
+
let decoded = uri;
|
|
95
|
+
try {
|
|
96
|
+
decoded = decodeURIComponent(uri);
|
|
97
|
+
} catch {
|
|
98
|
+
decoded = uri;
|
|
99
|
+
}
|
|
100
|
+
if (process.platform !== "win32") return decoded;
|
|
101
|
+
return decoded.replace(/^(file:\/\/\/)([a-z]):/, (_match, prefix: string, drive: string) => `${prefix}${drive.toUpperCase()}:`);
|
|
102
|
+
}
|
|
103
|
+
|
|
91
104
|
function isSameOrDescendant(candidate: string, parent: string): boolean {
|
|
92
105
|
const suffix = relative(parent, candidate);
|
|
93
106
|
return suffix === "" || (!suffix.startsWith("..") && suffix !== "..");
|
|
@@ -103,6 +116,7 @@ export class WorkspaceDocumentState {
|
|
|
103
116
|
private readonly openByUri = new Map<string, OpenDocumentState>();
|
|
104
117
|
private readonly openPromises = new Map<string, Promise<void>>();
|
|
105
118
|
private readonly now: () => number;
|
|
119
|
+
private readonly timerProvider: TimerProvider;
|
|
106
120
|
private readonly versionlessPublishQuiescenceMs: number;
|
|
107
121
|
|
|
108
122
|
constructor(
|
|
@@ -110,7 +124,12 @@ export class WorkspaceDocumentState {
|
|
|
110
124
|
private readonly clearDiagnostics: (uri: string) => void,
|
|
111
125
|
options: WorkspaceDocumentStateOptions = {},
|
|
112
126
|
) {
|
|
113
|
-
this.
|
|
127
|
+
this.timerProvider = options.timerProvider ?? {
|
|
128
|
+
now: options.now ?? (() => Date.now()),
|
|
129
|
+
setTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
|
|
130
|
+
clearTimeout: (handle) => clearTimeout(handle),
|
|
131
|
+
};
|
|
132
|
+
this.now = () => this.timerProvider.now();
|
|
114
133
|
this.versionlessPublishQuiescenceMs =
|
|
115
134
|
options.versionlessPublishQuiescenceMs ?? DEFAULT_VERSIONLESS_PUBLISH_QUIESCENCE_MS;
|
|
116
135
|
}
|
|
@@ -135,7 +154,7 @@ export class WorkspaceDocumentState {
|
|
|
135
154
|
}
|
|
136
155
|
|
|
137
156
|
getStoredDiagnostics(uri: string): readonly Diagnostic[] {
|
|
138
|
-
const state = this.openByUri.get(uri);
|
|
157
|
+
const state = this.openByUri.get(normalizeDocumentUri(uri));
|
|
139
158
|
if (!state) return [];
|
|
140
159
|
return state.lastPublish?.diagnostics ?? state.pullCache?.diagnostics ?? [];
|
|
141
160
|
}
|
|
@@ -163,13 +182,13 @@ export class WorkspaceDocumentState {
|
|
|
163
182
|
}
|
|
164
183
|
|
|
165
184
|
getPullCache(snapshot: DiagnosticSnapshot): PullDiagnosticsCacheHit | null {
|
|
166
|
-
const state = this.openByUri.get(snapshot.uri);
|
|
185
|
+
const state = this.openByUri.get(normalizeDocumentUri(snapshot.uri));
|
|
167
186
|
if (!state?.pullCache || state.pullCache.documentVersion !== snapshot.version) return null;
|
|
168
187
|
return state.pullCache;
|
|
169
188
|
}
|
|
170
189
|
|
|
171
190
|
recordPullDiagnostics(snapshot: DiagnosticSnapshot, report: PullDiagnosticsReport): void {
|
|
172
|
-
const state = this.openByUri.get(snapshot.uri);
|
|
191
|
+
const state = this.openByUri.get(normalizeDocumentUri(snapshot.uri));
|
|
173
192
|
if (!state) return;
|
|
174
193
|
state.pullCache = {
|
|
175
194
|
documentVersion: snapshot.version,
|
|
@@ -183,7 +202,7 @@ export class WorkspaceDocumentState {
|
|
|
183
202
|
readonly diagnostics: readonly Diagnostic[];
|
|
184
203
|
readonly version?: number;
|
|
185
204
|
}): void {
|
|
186
|
-
const state = this.openByUri.get(params.uri);
|
|
205
|
+
const state = this.openByUri.get(normalizeDocumentUri(params.uri));
|
|
187
206
|
if (!state) return;
|
|
188
207
|
state.publishGeneration += 1;
|
|
189
208
|
state.lastPublish = {
|
|
@@ -197,7 +216,7 @@ export class WorkspaceDocumentState {
|
|
|
197
216
|
}
|
|
198
217
|
|
|
199
218
|
resolvePushDiagnostics(snapshot: DiagnosticSnapshot): PushDiagnosticsResolution {
|
|
200
|
-
const state = this.openByUri.get(snapshot.uri);
|
|
219
|
+
const state = this.openByUri.get(normalizeDocumentUri(snapshot.uri));
|
|
201
220
|
if (!state?.lastPublish) return { status: "missing" };
|
|
202
221
|
const publish = state.lastPublish;
|
|
203
222
|
if (publish.version !== undefined) {
|
|
@@ -214,18 +233,18 @@ export class WorkspaceDocumentState {
|
|
|
214
233
|
}
|
|
215
234
|
|
|
216
235
|
waitForDiagnosticsActivity(snapshot: DiagnosticSnapshot, timeoutMs: number): Promise<void> {
|
|
217
|
-
const state = this.openByUri.get(snapshot.uri);
|
|
236
|
+
const state = this.openByUri.get(normalizeDocumentUri(snapshot.uri));
|
|
218
237
|
if (!state || timeoutMs <= 0) return Promise.resolve();
|
|
219
238
|
return new Promise((resolveActivity) => {
|
|
220
239
|
let settled = false;
|
|
221
240
|
const finish = () => {
|
|
222
241
|
if (settled) return;
|
|
223
242
|
settled = true;
|
|
224
|
-
clearTimeout(timer);
|
|
243
|
+
this.timerProvider.clearTimeout(timer);
|
|
225
244
|
state.waiters.delete(finish);
|
|
226
245
|
resolveActivity();
|
|
227
246
|
};
|
|
228
|
-
const timer = setTimeout(finish, timeoutMs);
|
|
247
|
+
const timer = this.timerProvider.setTimeout(finish, timeoutMs);
|
|
229
248
|
if (typeof timer.unref === "function") timer.unref();
|
|
230
249
|
state.waiters.add(finish);
|
|
231
250
|
});
|
|
@@ -329,7 +348,7 @@ export class WorkspaceDocumentState {
|
|
|
329
348
|
waiters: new Set(),
|
|
330
349
|
};
|
|
331
350
|
this.openDocuments.set(path, state);
|
|
332
|
-
this.openByUri.set(state.uri, state);
|
|
351
|
+
this.openByUri.set(normalizeDocumentUri(state.uri), state);
|
|
333
352
|
this.notifyWaiters(state);
|
|
334
353
|
await this.sendNotification("textDocument/didOpen", {
|
|
335
354
|
textDocument: { uri: state.uri, languageId: state.languageId, version: state.version, text },
|
|
@@ -356,7 +375,7 @@ export class WorkspaceDocumentState {
|
|
|
356
375
|
|
|
357
376
|
private async closeDocument(state: OpenDocumentState): Promise<void> {
|
|
358
377
|
this.openDocuments.delete(state.path);
|
|
359
|
-
this.openByUri.delete(state.uri);
|
|
378
|
+
this.openByUri.delete(normalizeDocumentUri(state.uri));
|
|
360
379
|
this.clearDiagnostics(state.uri);
|
|
361
380
|
this.notifyWaiters(state);
|
|
362
381
|
await this.sendNotification("textDocument/didClose", { textDocument: { uri: state.uri } });
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
clientOptions,
|
|
5
|
+
optionalBoolean,
|
|
6
|
+
optionalNumber,
|
|
7
|
+
optionalString,
|
|
8
|
+
requireNumber,
|
|
9
|
+
requireString,
|
|
10
|
+
severityFilter,
|
|
11
|
+
} from "./parameters.js";
|
|
12
|
+
|
|
13
|
+
describe("LSP tool parameter helpers", () => {
|
|
14
|
+
test("#given required string parameter #when value is missing or empty #then throws clear error", () => {
|
|
15
|
+
expect(requireString({ filePath: "index.ts" }, "filePath")).toBe("index.ts");
|
|
16
|
+
expect(() => requireString({}, "filePath")).toThrow("Missing required string parameter 'filePath'");
|
|
17
|
+
expect(() => requireString({ filePath: "" }, "filePath")).toThrow("Missing required string parameter 'filePath'");
|
|
18
|
+
expect(() => requireString({ filePath: 42 }, "filePath")).toThrow("Missing required string parameter 'filePath'");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("#given required number parameter #when value is non-finite #then throws clear error", () => {
|
|
22
|
+
expect(requireNumber({ line: 3 }, "line")).toBe(3);
|
|
23
|
+
expect(() => requireNumber({}, "line")).toThrow("Missing required number parameter 'line'");
|
|
24
|
+
expect(() => requireNumber({ line: Number.NaN }, "line")).toThrow("Missing required number parameter 'line'");
|
|
25
|
+
expect(() => requireNumber({ line: Number.POSITIVE_INFINITY }, "line")).toThrow(
|
|
26
|
+
"Missing required number parameter 'line'",
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("#given optional parameters #when values have wrong type #then ignores them", () => {
|
|
31
|
+
expect(optionalString({ query: "symbol" }, "query")).toBe("symbol");
|
|
32
|
+
expect(optionalString({ query: 7 }, "query")).toBeUndefined();
|
|
33
|
+
expect(optionalNumber({ limit: 10 }, "limit")).toBe(10);
|
|
34
|
+
expect(optionalNumber({ limit: Number.NaN }, "limit")).toBeUndefined();
|
|
35
|
+
expect(optionalBoolean({ includeDeclaration: false }, "includeDeclaration")).toBe(false);
|
|
36
|
+
expect(optionalBoolean({ includeDeclaration: "false" }, "includeDeclaration")).toBeUndefined();
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("#given severity parameter #when value is unknown #then defaults to all", () => {
|
|
40
|
+
expect(severityFilter({ severity: "warning" })).toBe("warning");
|
|
41
|
+
expect(severityFilter({ severity: "fatal" })).toBe("all");
|
|
42
|
+
expect(severityFilter({})).toBe("all");
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
test("#given optional abort signal #when building client options #then includes only defined signal", () => {
|
|
46
|
+
const controller = new AbortController();
|
|
47
|
+
|
|
48
|
+
expect(clientOptions(undefined)).toEqual({});
|
|
49
|
+
expect(clientOptions(controller.signal)).toEqual({ signal: controller.signal });
|
|
50
|
+
});
|
|
51
|
+
});
|
|
@@ -595,6 +595,13 @@ import { pathToFileURL as pathToFileURL3 } from "node:url";
|
|
|
595
595
|
// ../lsp-core/src/lsp/connection.ts
|
|
596
596
|
import { pathToFileURL } from "node:url";
|
|
597
597
|
|
|
598
|
+
// ../lsp-core/src/lsp/timer-provider.ts
|
|
599
|
+
var realTimerProvider = {
|
|
600
|
+
now: () => Date.now(),
|
|
601
|
+
setTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
|
|
602
|
+
clearTimeout: (handle) => clearTimeout(handle)
|
|
603
|
+
};
|
|
604
|
+
|
|
598
605
|
// ../lsp-core/src/lsp/constants.ts
|
|
599
606
|
var DEFAULT_MAX_REFERENCES = 200;
|
|
600
607
|
var DEFAULT_MAX_SYMBOLS = 200;
|
|
@@ -1134,6 +1141,7 @@ class LspClientTransport {
|
|
|
1134
1141
|
this.documentFormattingSupported = false;
|
|
1135
1142
|
this.requestTimeoutMs = timeouts.requestTimeoutMs ?? REQUEST_TIMEOUT_MS;
|
|
1136
1143
|
this.initializeTimeoutMs = timeouts.initializeTimeoutMs ?? INIT_TIMEOUT_MS;
|
|
1144
|
+
this.timerProvider = timeouts.timerProvider ?? realTimerProvider;
|
|
1137
1145
|
}
|
|
1138
1146
|
pid() {
|
|
1139
1147
|
return this.proc?.pid;
|
|
@@ -1234,7 +1242,7 @@ class LspClientTransport {
|
|
|
1234
1242
|
const options = args[1];
|
|
1235
1243
|
const timeoutMs = options?.timeoutMs ?? this.requestTimeoutMs;
|
|
1236
1244
|
const timeoutController = new AbortController;
|
|
1237
|
-
const timeoutHandle = setTimeout(() => {
|
|
1245
|
+
const timeoutHandle = this.timerProvider.setTimeout(() => {
|
|
1238
1246
|
const stderrTail = this.stderrBuffer.slice(-5).join(`
|
|
1239
1247
|
`);
|
|
1240
1248
|
timeoutController.abort(new LspRequestTimeoutError(method, stderrTail || undefined));
|
|
@@ -1253,7 +1261,7 @@ class LspClientTransport {
|
|
|
1253
1261
|
}
|
|
1254
1262
|
throw error;
|
|
1255
1263
|
} finally {
|
|
1256
|
-
clearTimeout(timeoutHandle);
|
|
1264
|
+
this.timerProvider.clearTimeout(timeoutHandle);
|
|
1257
1265
|
combinedSignal.dispose();
|
|
1258
1266
|
}
|
|
1259
1267
|
}
|
|
@@ -1634,6 +1642,17 @@ function canonicalPath(filePath) {
|
|
|
1634
1642
|
return absolute;
|
|
1635
1643
|
}
|
|
1636
1644
|
}
|
|
1645
|
+
function normalizeDocumentUri(uri) {
|
|
1646
|
+
let decoded = uri;
|
|
1647
|
+
try {
|
|
1648
|
+
decoded = decodeURIComponent(uri);
|
|
1649
|
+
} catch {
|
|
1650
|
+
decoded = uri;
|
|
1651
|
+
}
|
|
1652
|
+
if (process.platform !== "win32")
|
|
1653
|
+
return decoded;
|
|
1654
|
+
return decoded.replace(/^(file:\/\/\/)([a-z]):/, (_match, prefix, drive) => `${prefix}${drive.toUpperCase()}:`);
|
|
1655
|
+
}
|
|
1637
1656
|
function isSameOrDescendant(candidate, parent) {
|
|
1638
1657
|
const suffix = relative2(parent, candidate);
|
|
1639
1658
|
return suffix === "" || !suffix.startsWith("..") && suffix !== "..";
|
|
@@ -1650,7 +1669,12 @@ class WorkspaceDocumentState {
|
|
|
1650
1669
|
this.openDocuments = new Map;
|
|
1651
1670
|
this.openByUri = new Map;
|
|
1652
1671
|
this.openPromises = new Map;
|
|
1653
|
-
this.
|
|
1672
|
+
this.timerProvider = options.timerProvider ?? {
|
|
1673
|
+
now: options.now ?? (() => Date.now()),
|
|
1674
|
+
setTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
|
|
1675
|
+
clearTimeout: (handle) => clearTimeout(handle)
|
|
1676
|
+
};
|
|
1677
|
+
this.now = () => this.timerProvider.now();
|
|
1654
1678
|
this.versionlessPublishQuiescenceMs = options.versionlessPublishQuiescenceMs ?? DEFAULT_VERSIONLESS_PUBLISH_QUIESCENCE_MS;
|
|
1655
1679
|
}
|
|
1656
1680
|
async openFile(filePath) {
|
|
@@ -1672,7 +1696,7 @@ class WorkspaceDocumentState {
|
|
|
1672
1696
|
return this.openDocuments.get(canonicalPath(filePath))?.version;
|
|
1673
1697
|
}
|
|
1674
1698
|
getStoredDiagnostics(uri) {
|
|
1675
|
-
const state = this.openByUri.get(uri);
|
|
1699
|
+
const state = this.openByUri.get(normalizeDocumentUri(uri));
|
|
1676
1700
|
if (!state)
|
|
1677
1701
|
return [];
|
|
1678
1702
|
return state.lastPublish?.diagnostics ?? state.pullCache?.diagnostics ?? [];
|
|
@@ -1694,13 +1718,13 @@ class WorkspaceDocumentState {
|
|
|
1694
1718
|
return state !== undefined && state.uri === snapshot.uri && state.version === snapshot.version && state.generation === snapshot.documentGeneration;
|
|
1695
1719
|
}
|
|
1696
1720
|
getPullCache(snapshot) {
|
|
1697
|
-
const state = this.openByUri.get(snapshot.uri);
|
|
1721
|
+
const state = this.openByUri.get(normalizeDocumentUri(snapshot.uri));
|
|
1698
1722
|
if (!state?.pullCache || state.pullCache.documentVersion !== snapshot.version)
|
|
1699
1723
|
return null;
|
|
1700
1724
|
return state.pullCache;
|
|
1701
1725
|
}
|
|
1702
1726
|
recordPullDiagnostics(snapshot, report) {
|
|
1703
|
-
const state = this.openByUri.get(snapshot.uri);
|
|
1727
|
+
const state = this.openByUri.get(normalizeDocumentUri(snapshot.uri));
|
|
1704
1728
|
if (!state)
|
|
1705
1729
|
return;
|
|
1706
1730
|
state.pullCache = {
|
|
@@ -1710,7 +1734,7 @@ class WorkspaceDocumentState {
|
|
|
1710
1734
|
};
|
|
1711
1735
|
}
|
|
1712
1736
|
recordPublishedDiagnostics(params) {
|
|
1713
|
-
const state = this.openByUri.get(params.uri);
|
|
1737
|
+
const state = this.openByUri.get(normalizeDocumentUri(params.uri));
|
|
1714
1738
|
if (!state)
|
|
1715
1739
|
return;
|
|
1716
1740
|
state.publishGeneration += 1;
|
|
@@ -1724,7 +1748,7 @@ class WorkspaceDocumentState {
|
|
|
1724
1748
|
this.notifyWaiters(state);
|
|
1725
1749
|
}
|
|
1726
1750
|
resolvePushDiagnostics(snapshot) {
|
|
1727
|
-
const state = this.openByUri.get(snapshot.uri);
|
|
1751
|
+
const state = this.openByUri.get(normalizeDocumentUri(snapshot.uri));
|
|
1728
1752
|
if (!state?.lastPublish)
|
|
1729
1753
|
return { status: "missing" };
|
|
1730
1754
|
const publish = state.lastPublish;
|
|
@@ -1738,7 +1762,7 @@ class WorkspaceDocumentState {
|
|
|
1738
1762
|
return waitMs === 0 ? { status: "ready", diagnostics: publish.diagnostics } : { status: "wait", waitMs };
|
|
1739
1763
|
}
|
|
1740
1764
|
waitForDiagnosticsActivity(snapshot, timeoutMs) {
|
|
1741
|
-
const state = this.openByUri.get(snapshot.uri);
|
|
1765
|
+
const state = this.openByUri.get(normalizeDocumentUri(snapshot.uri));
|
|
1742
1766
|
if (!state || timeoutMs <= 0)
|
|
1743
1767
|
return Promise.resolve();
|
|
1744
1768
|
return new Promise((resolveActivity) => {
|
|
@@ -1747,11 +1771,11 @@ class WorkspaceDocumentState {
|
|
|
1747
1771
|
if (settled)
|
|
1748
1772
|
return;
|
|
1749
1773
|
settled = true;
|
|
1750
|
-
clearTimeout(timer);
|
|
1774
|
+
this.timerProvider.clearTimeout(timer);
|
|
1751
1775
|
state.waiters.delete(finish);
|
|
1752
1776
|
resolveActivity();
|
|
1753
1777
|
};
|
|
1754
|
-
const timer = setTimeout(finish, timeoutMs);
|
|
1778
|
+
const timer = this.timerProvider.setTimeout(finish, timeoutMs);
|
|
1755
1779
|
if (typeof timer.unref === "function")
|
|
1756
1780
|
timer.unref();
|
|
1757
1781
|
state.waiters.add(finish);
|
|
@@ -1859,7 +1883,7 @@ class WorkspaceDocumentState {
|
|
|
1859
1883
|
waiters: new Set
|
|
1860
1884
|
};
|
|
1861
1885
|
this.openDocuments.set(path, state);
|
|
1862
|
-
this.openByUri.set(state.uri, state);
|
|
1886
|
+
this.openByUri.set(normalizeDocumentUri(state.uri), state);
|
|
1863
1887
|
this.notifyWaiters(state);
|
|
1864
1888
|
await this.sendNotification("textDocument/didOpen", {
|
|
1865
1889
|
textDocument: { uri: state.uri, languageId: state.languageId, version: state.version, text }
|
|
@@ -1884,7 +1908,7 @@ class WorkspaceDocumentState {
|
|
|
1884
1908
|
}
|
|
1885
1909
|
async closeDocument(state) {
|
|
1886
1910
|
this.openDocuments.delete(state.path);
|
|
1887
|
-
this.openByUri.delete(state.uri);
|
|
1911
|
+
this.openByUri.delete(normalizeDocumentUri(state.uri));
|
|
1888
1912
|
this.clearDiagnostics(state.uri);
|
|
1889
1913
|
this.notifyWaiters(state);
|
|
1890
1914
|
await this.sendNotification("textDocument/didClose", { textDocument: { uri: state.uri } });
|
|
@@ -2971,6 +2995,7 @@ class LspClient extends LspClientConnection {
|
|
|
2971
2995
|
this.diagnosticPullErrors = [];
|
|
2972
2996
|
this.diagnosticsFreshnessTimeoutMs = options.diagnosticsFreshnessTimeoutMs ?? DIAGNOSTICS_FRESHNESS_TIMEOUT_MS;
|
|
2973
2997
|
this.documents = new WorkspaceDocumentState((method, params) => this.sendNotification(method, params), (uri) => this.diagnosticsStore.delete(uri), {
|
|
2998
|
+
timerProvider: this.timerProvider,
|
|
2974
2999
|
versionlessPublishQuiescenceMs: options.versionlessPublishQuiescenceMs ?? VERSIONLESS_PUBLISH_QUIESCENCE_MS
|
|
2975
3000
|
});
|
|
2976
3001
|
this.workspaceMutations = new WorkspaceMutationController(root, this.documents);
|
|
@@ -3074,7 +3099,7 @@ class LspClient extends LspClientConnection {
|
|
|
3074
3099
|
const absPath = this.resolveWorkspacePath(filePath);
|
|
3075
3100
|
const uri = pathToFileURL3(absPath).href;
|
|
3076
3101
|
await this.openFile(absPath);
|
|
3077
|
-
const deadlineAt =
|
|
3102
|
+
const deadlineAt = this.timerProvider.now() + this.diagnosticsFreshnessTimeoutMs;
|
|
3078
3103
|
for (;; ) {
|
|
3079
3104
|
signal?.throwIfAborted();
|
|
3080
3105
|
const snapshot = this.documents.captureDiagnosticSnapshot(absPath);
|
|
@@ -3087,7 +3112,7 @@ class LspClient extends LspClientConnection {
|
|
|
3087
3112
|
if (!pushFallbackOnly) {
|
|
3088
3113
|
const cached = this.documents.getPullCache(snapshot);
|
|
3089
3114
|
try {
|
|
3090
|
-
const remainingMs2 = deadlineAt -
|
|
3115
|
+
const remainingMs2 = deadlineAt - this.timerProvider.now();
|
|
3091
3116
|
if (remainingMs2 <= 0)
|
|
3092
3117
|
return this.freshnessTimeout(absPath);
|
|
3093
3118
|
const result = await this.sendRequest("textDocument/diagnostic", {
|
|
@@ -3122,7 +3147,7 @@ class LspClient extends LspClientConnection {
|
|
|
3122
3147
|
}
|
|
3123
3148
|
if (!pushFallbackOnly)
|
|
3124
3149
|
continue;
|
|
3125
|
-
const remainingMs = deadlineAt -
|
|
3150
|
+
const remainingMs = deadlineAt - this.timerProvider.now();
|
|
3126
3151
|
if (remainingMs <= 0) {
|
|
3127
3152
|
if (!this.isDiagnosticPullSupported() && snapshot.publishGeneration === 0) {
|
|
3128
3153
|
const cached = this.documents.getPullCache(snapshot);
|