LSP 只读导航

模型读代码最常用的四种语义问题——定义、引用、实现、悬停——收敛成一条只读 seam, 语言服务器的启动与协议细节全部留在 provider 里。对应实现:src/Tether.Lsp/(seam)、 src/Tether.Lsp.Stdio/(stdio provider)与 src/Tether.Lsp.Tools/(工具 consumer)。

seam

public interface ILspService
{
    IDisposable RegisterServer(LspServerSpec spec);
    ValueTask<LspResult> QueryAsync(LspOperation operation, LspPosition position, CancellationToken ct = default);
}

LspOperation 只有四个取值:Definition、References、Implementation、Hover。 没有写操作——seam 的定位是导航而不是编辑,重命名、code action 之类有意不在内。

LspServerSpec 把服务器绑定到一组扩展名(不是语言 id):注册时按 branded id 与扩展名原子登记, 重复 id 或重复扩展名整体失败,不会留下半个注册。查询按文件扩展名路由到认领它的服务器。

线协议纪律

  • UTF-16 零基坐标。 LspPosition 的 line / character 按 LSP 规范走 UTF-16 code unit, provider 负责与 .NET 字符串(同为 UTF-16,但索引语义要对齐)之间的换算。
  • 瞬态文档生命周期。 每次查询临时 textDocument/didOpen,查询结束后 didClose—— 不把整个 workspace 的打开状态留在服务器侧,模型改过的文件下次查询总是以盘上内容为准。
  • 取消与重试。 查询是可取消的;协议层的瞬态失败有界重试,终态失败以稳定码上抛。

stdio provider 与 framing

StdioLspProvider 用手写的 Content-Length JSON-RPC framing 与服务器通信。 LSP 的 framing 足够简单(Content-Length: n\r\n\r\n + JSON 体),手写实现换来零额外依赖与完全可控的缓冲上限, 行为回归由测试守住(见 issue #26 的 intentional deviation 说明)。

LspTransport 负责把响应按 id 配回请求、处理服务器主动发来的通知,并在进程退出时保证不悬挂。

稳定失败码

码含义
LSP_NO_SERVER没有服务器认领这个扩展名
LSP_DUPLICATE注册时 id 或扩展名重复
LSP_OUT_OF_RANGE位置超出文档范围
LSP_UNSUPPORTED服务器声明不支持该操作
LSP_PROTOCOL协议级错误(含服务器返回的 error response)
LSP_START服务器进程启动失败

工具暴露

LspToolsPlugin 把四种操作暴露为模型工具。模型给出文件路径与行列,拿回一组 LspLocation 或悬停文本; 大型结果集交给 溢写 机制落到盘上的 spill store,模型看到的是定位符而非超长正文。

下一步

在 GitHub 上编辑此页