LSP 导航
seam 与模型恰好公开 4 项语义查询;该联合是闭合的,因此新增一项查询会通过编译强制要求同步修改 seam、提供方和工具。位置与范围采用从零开始的 UTF-16 坐标,与协议一致;面向模型的工具采用从 1 开始的光标约定,并在输入和输出时进行转换。
/** * The four semantic queries the seam and model expose. A closed union: adding an operation is a * compile-enforced change across the seam, providers, and the tool. Symbols and call hierarchy are * not operations here; they need different schemas. */type LspOperation = 'goToDefinition' | 'findReferences' | 'goToImplementation' | 'hover'/** A zero-based UTF-16 cursor coordinate, matching the LSP wire convention. */interface LspPosition { /** Zero-based line. */ readonly line: number /** Zero-based UTF-16 code-unit offset within the line. */ readonly character: number}/** A zero-based UTF-16 half-open range `[start, end)`. */interface LspRange { readonly start: LspPosition readonly end: LspPosition}每个字段都是必填项:workspaceRoot 由调用方提供,languageId 来自提供方注册而非请求,超时与结果上限由消费方决定。因此没有字段需要由实现提供默认值,也不存在 resolve() 步骤。提供方收到调用方请求和派生的 languageId;后者只用于同步瞬态文档,从不参与选择。
/** * A caller's normalized query. Every field is required: `workspaceRoot` is caller-supplied, * `languageId` comes from the provider registration (not here), and consumers own timeouts and * result limits — so no field needs implementation defaulting and there is no `resolve()` step. */interface LspQueryRequest { /** Which semantic query to run. */ readonly operation: LspOperation /** The source file to query (relative to `workspaceRoot` or absolute; the provider canonicalizes). */ readonly filePath: string /** The zero-based UTF-16 cursor position to query at. */ readonly position: LspPosition /** The workspace root the provider resolves against and indexes; required, never defaulted. */ readonly workspaceRoot: string}/** * A request as a provider receives it: the caller's {@link LspQueryRequest} plus the `languageId` * the seam derived from the provider's extension mapping. The language id only synchronizes the * transient document; it does not participate in selection. */interface LspProviderQuery extends LspQueryRequest { /** The LSP language id for `filePath`, from this provider's extension mapping. */ readonly languageId: string}这是一个闭合的可辨识联合:导航操作规范化为 locations,hover 规范化为内容或 null。消费方使用 switch 对 kind 做穷尽处理,因此新增分支会使编译失败,直到完成处理。findReferences 始终包含声明;提供方在内部强制保证这一点,因此调用方没有对应 flag。locations 变体携带 resolvedWorkspaceUri,即提供方的规范工作区 file: URI。调用方相对化位置 URI 时应使用这一坐标,而不是对可能经过符号链接的请求根目录应用宿主平台路径规则。
/** One resolved location: a document URI and the range within it. */interface LspLocation { /** The target document URI (`file:` or otherwise), verbatim from the server. */ readonly uri: string /** The range within the target document. */ readonly range: LspRange}/** Normalized hover content, or `null` for no hover at the position. */interface LspHover { /** The normalized hover text (markdown or plaintext, provider-joined). */ readonly contents: string /** The range the hover applies to, when the server supplied one. */ readonly range?: LspRange}/** * The closed result union. Navigation operations (`goToDefinition`, `findReferences`, * `goToImplementation`) normalize to `locations`; `hover` normalizes to content or `null`. * Consumers `switch` on `kind` to exhaustiveness so a new arm breaks compilation until handled. * * The `locations` variant carries `resolvedWorkspaceUri`: the provider's canonical `file:` URI for * the request's workspace root. A caller that relativizes location URIs MUST use this, not parse the * request's possibly symlinked process path with host-platform rules; the execution platform may * differ from the caller's. */type LspQueryResult = | { readonly kind: 'locations'; readonly locations: readonly LspLocation[]; readonly resolvedWorkspaceUri: string } | { readonly kind: 'hover'; readonly hover: LspHover | null }提供方与服务
Section titled “提供方与服务”每个提供方拥有一个稳定的品牌化 id,以及一份互斥的、小写且以点开头的扩展名映射。registerProvider 会原子预留 id 和每个扩展名:注册无效或冲突时不发布任何内容;其 disposer 会释放所有保留项。每次查询独立选择提供方,且选择与顺序无关;没有匹配项时抛出 LspError LSP_UNAVAILABLE。该 seam 不公开协议类型、进程或文档控制,也不提供通用 JSON-RPC 逃生口。
/** * A language-server backend registered on `ctx.lsp`. Each provider owns a stable {@link * LspProviderId} and an extension-to-language-id map (lowercase, leading-dot keys). * `findReferences` always includes declarations — the provider enforces this internally; callers * get no flag. */interface LspProvider { /** Stable provider identity, reserved atomically with the extension mappings. */ readonly id: LspProviderId /** Lowercase leading-dot extension → LSP language id (e.g. `{ '.ts': 'typescript' }`). */ readonly extensionToLanguage: Readonly<Record<string, string>> /** * Run one query. The seam has already selected this provider and derived `languageId`. * @param request - the resolved provider query (caller request + derived language id). * @param signal - optional cancellation; the provider stops its own work when it aborts. * @returns the normalized, closed-union result. */ query(request: LspProviderQuery, signal?: AbortSignal): Promise<LspQueryResult>}/** * The LSP capability seam (`ctx.lsp`). Owns provider registration/selection and normalized query * execution; exposes exactly the four operations and no protocol escape hatch. */interface LspService { /** * Register a provider, atomically reserving its id and every normalized extension. Any conflict * or invalid input publishes nothing and throws `LspError`; the returned disposer releases all * reservations. Disposed with the calling fiber. * @param provider - the backend to register. * @returns a synchronous disposer releasing the id and all extension reservations. */ registerProvider(provider: LspProvider): () => void /** * Select a provider by the file's extension and run one query. Selection is per-query and * order-independent; no match throws `LspError` `LSP_UNAVAILABLE`. * @param request - the normalized query. * @param signal - optional cancellation forwarded to the selected provider. * @returns the normalized, closed-union result. */ query(request: LspQueryRequest, signal?: AbortSignal): Promise<LspQueryResult>}LspProviderId 是该 seam 的品牌化 id(来自 dsh-brand 的 Branded<'LspProviderId'>);LspError 扩展 HarnessError,提供 LSP_INVALID_PROVIDER、LSP_CONFLICT、LSP_UNAVAILABLE、LSP_DISPOSED、LSP_UNSUPPORTED_OPERATION 和 LSP_MALFORMED_RESPONSE 等稳定错误码,调用方应按错误码路由,而不是解析 message。
Cordis API
Section titled “Cordis API”Generated from source by scripts/gen-cordis-catalog.ts (verified fresh by pnpm run verify-cordis-catalog in doc-sync; regenerate with pnpm run gen-cordis-catalog) — this section is byte-identical in both language sides of the page. Signature blocks use a ts cordis-catalog fence and keep the original source JSDoc; dispatch modes are defined in the primer, and the framework-inherited ctx API lives in cordis-api/inherited.md.
ctx.lsp — LspService
Section titled “ctx.lsp — LspService”The LSP capability seam (ctx.lsp). Owns provider registration/selection and normalized query execution; exposes exactly the four operations and no protocol escape hatch.
/** * Register a provider, atomically reserving its id and every normalized extension. Any conflict * or invalid input publishes nothing and throws `LspError`; the returned disposer releases all * reservations. Disposed with the calling fiber. * @param provider - the backend to register. * @returns a synchronous disposer releasing the id and all extension reservations. */registerProvider(provider: LspProvider): () => void
/** * Select a provider by the file's extension and run one query. Selection is per-query and * order-independent; no match throws `LspError` `LSP_UNAVAILABLE`. * @param request - the normalized query. * @param signal - optional cancellation forwarded to the selected provider. * @returns the normalized, closed-union result. */query(request: LspQueryRequest, signal?: AbortSignal): Promise<LspQueryResult>