深入解读 LSP 3.18 Hover 悬停请求协议:从能力协商到 MarkupContent 渲染

发布时间:2026/10/7 8:02:41
深入解读 LSP 3.18 Hover 悬停请求协议:从能力协商到 MarkupContent 渲染 开发工具【免费下载链接】language-server-protocolDefines a common protocol for language servers.项目地址https://gitcode.com/gh_mirrors/la/language-server-protocol点击查看免费下载悬停Hover是 Language Server ProtocolLSP中最基础、使用最频繁的文本补全类请求之一当用户在编辑器中把鼠标悬停在某个符号上时客户端向语言服务器发起textDocument/hover请求服务器返回类型签名、文档注释等悬浮提示内容。本文以仓库中 3.18 规范文档 _specifications/lsp/3.18/language/hover.md 为主体完整展开 Hover 请求的协议结构、客户端/服务器能力声明、Hover结果字面量以及MarkupContent的两种内容格式并结合仓库中的类型定义与 metaModel.json 源码佐证帮助你从协议层面彻底理解并正确实现一个符合规范的 hover 功能。一、Hover 请求的核心语义客户端 → 服务器Hover 请求的通信方向是从客户端发送到服务器文档中标注为:leftwards_arrow_with_hook:目的是请求指定文本位置上的悬停信息。其方法名为method:textDocument/hoverparams:HoverParamsresult:Hover|null协议定义见 _specifications/lsp/3.18/language/hover.md。当服务器返回null时表示该位置没有任何可显示的悬停内容客户端应保持默认行为不弹出任何提示。位置语义悬停在字符左侧一个容易忽视的细节是位置的解析方式。当客户端发送 hover 请求时position 通常指向被悬停字符左侧的位置。例如用户悬停在偏移量n处的字符c上客户端通常会发送n即该字符之前的位置。而服务器如何解释这个位置、返回什么悬停信息是语言和具体实现相关的——同一位置在不同语言服务器中可能产生完全不同的结果这属于协议允许的实现差异。二、能力协商两端如何宣告 hover 支持LSP 的能力协商发生在初始化阶段见 _specifications/lsp/3.18/general/initialize.md。客户端能力textDocument.hover客户端通过initialize请求的capabilities.textDocument.hover属性宣告自己的 hover 能力类型为HoverClientCapabilitiesexport interface HoverClientCapabilities { /** * Whether hover supports dynamic registration. */ dynamicRegistration?: boolean; /** * Client supports the following content formats if the content * property refers to a literal of type MarkupContent. * The order describes the preferred format of the client. */ contentFormat?: MarkupKind[]; }两个字段的含义dynamicRegistration客户端是否支持 hover 服务的动态注册即允许服务器在运行时通过client/registerCapability请求注册/注销 hover 支持。若为false或缺失服务器只能在初始化响应中声明hoverProvider。contentFormat客户端支持的内容格式数组MarkupKind[]目前可取plaintext或markdown。数组顺序即客户端的偏好顺序——排在最前面的格式是客户端最希望服务器返回的格式服务器应优先选择。对应地在初始化请求中该能力被声明于textDocument.hover下见 _specifications/lsp/3.18/general/initialize.md。服务器能力hoverProvider服务器在初始化响应中通过capabilities.hoverProvider宣告支持 hover属性类型为boolean | HoverOptions。当只需要简单布尔声明时{ capabilities: { hoverProvider: true } }若需要额外声明工作进度work done progress支持则使用HoverOptionsexport interface HoverOptions extends WorkDoneProgressOptions { }其中WorkDoneProgressOptions定义于 _specifications/lsp/3.18/types/workDoneProgress.mdexport interface WorkDoneProgressOptions { workDoneProgress?: boolean; }即{ capabilities: { hoverProvider: { workDoneProgress: true } } }workDoneProgress为true表示服务器会在处理 hover 请求期间通过客户端传入的workDoneToken上报进度例如处理超大型文件时报告正在解析文件。该机制自 3.15.0 起引入$/progress通知的begin/report/end三种载荷结构详见 _specifications/lsp/3.18/types/workDoneProgress.md。三、请求参数HoverParams 的完整结构HoverParams由两部分组合而成export interface HoverParams extends TextDocumentPositionParams, WorkDoneProgressParams { }TextDocumentPositionParams携带目标文档与位置定义见 _specifications/lsp/3.18/types/textDocumentPositionParams.mdinterface TextDocumentPositionParams { /** * The text document. */ textDocument: TextDocumentIdentifier; /** * The position inside the text document. */ position: Position; }其中TextDocumentIdentifier只有一个字段uri: DocumentUri字符串形式的 URI见 _specifications/lsp/3.18/types/textDocumentIdentifier.md。WorkDoneProgressParams可选的workDoneToken用于服务器上报进度export interface WorkDoneProgressParams { /** * An optional token that a server can use to report work done progress. */ workDoneToken?: ProgressToken; }Position 的编码细节Position使用零基的行号与零基的字符偏移表示见 _specifications/lsp/3.18/types/position.mdinterface Position { /** * Line position in a document (zero-based). */ line: uinteger; /** * Character offset on a line in a document (zero-based). The meaning of this * offset is determined by the negotiated PositionEncodingKind. * * If the character value is greater than the line length it defaults back * to the line length. */ character: uinteger; }需要注意的约定一个位置位于两个字符之间如同编辑器中的插入光标因此不支持和-1这类表示行尾的特殊值。character偏移的具体含义由初始化阶段协商的PositionEncodingKind决定。自 3.17.0 起定义了三种预置编码见 _specifications/lsp/3.18/types/position.mdutf-8按 UTF-8 码元字节计数utf-16按 UTF-16 码元计数这是默认值服务器必须始终支持utf-32按 UTF-32 码元计数与 Unicode 码点一致也可用作与编码无关的字符偏移表示。当character大于行长度时协议规定默认回退到行长度避免越界。一个典型的 hover 请求 JSON 如下{ jsonrpc: 2.0, id: 2, method: textDocument/hover, params: { textDocument: { uri: file:///folder/file.ts }, position: { line: 9, character: 5 }, workDoneToken: 1d546990-40a3-4b77-b134-46622995f6ae } }四、响应结果Hover 字面量服务器的响应结果为Hover|null定义如下/** * The result of a hover request. */ export interface Hover { /** * The hovers content. */ contents: MarkedString | MarkedString[] | MarkupContent; /** * An optional range is a range inside a text document * that is used to visualize a hover, e.g. by changing the background color. */ range?: Range; }两个字段的职责contents必填悬停显示的内容三种形态任选其一——单个MarkedString、MarkedString数组或MarkupContent详见下文。range可选文档内的一个范围客户端用它来可视化悬停位置例如改变背景色高亮整个符号。Range用零基的起止位置表示结束位置是排他的exclusive若要包含整行及行尾换行符需将 end 指向下一行的起始位置见 _specifications/lsp/3.18/types/range.mdinterface Range { /** * The ranges start position. */ start: Position; /** * The ranges end position. */ end: Position; }例如覆盖第 5 行第 23 列到第 6 行行首{ start: { line: 5, character: 23 }, end : { line: 6, character: 0 } }遗留类型 MarkedString已废弃/** * MarkedString can be used to render human readable text. It is either a * markdown string or a code-block that provides a language and a code snippet. * The language identifier is semantically equal to the optional language * identifier in fenced code blocks in GitHub issues. * * The pair of a language and a value is an equivalent to markdown: * ${language} * ${value} * * * Note that markdown strings will be sanitized - that means html will be * escaped. * * deprecated use MarkupContent instead. */ type MarkedString string | { language: string; value: string };MarkedString的两种形态纯字符串直接作为 markdown 字符串渲染{ language, value }对象等价于一段带语言标识的围栏代码块${language} ${value}其中 language 与 GitHub Issues 中围栏代码块的可选语言标识语义一致。文档明确标注deprecated建议改用MarkupContent。新实现的服务器应优先返回MarkupContent仅在需要兼容旧客户端时才使用MarkedString。另外注意 markdown 字符串会被消毒处理sanitized即其中的 HTML 会被转义避免脚本执行。五、MarkupContent现代推荐的内容格式MarkupContent是自 3.0 起引入、并在 hover、CompletionItem、SignatureInformation等结果的文档属性中广泛使用的统一内容格式定义见 _specifications/lsp/3.18/types/markupContent.mdexport interface MarkupContent { /** * The type of the Markup. */ kind: MarkupKind; /** * The content itself. */ value: string; }MarkupKindplaintext 与 markdownexport namespace MarkupKind { /** * Plain text is supported as a content format. */ export const PlainText: plaintext plaintext; /** * Markdown is supported as a content format. */ export const Markdown: markdown markdown; } export type MarkupKind plaintext | markdown;kind: plaintext纯文本客户端按字面文本显示kind: markdown内容应遵循GitHub Flavored MarkdownGFM规范可以包含类似 GitHub Issues 中的围栏代码块。协议特别提醒MarkupKind不允许以$开头以$开头的 kind 为协议保留用于内部用途。使用 TypeScript 构造 markdown 内容的官方示例let markdown: MarkdownContent { kind: MarkupKind.Markdown, value: [ # Header, Some text, typescript, someCode();, ].join(\n) };协议同时提醒客户端可能会对返回的 markdown 进行消毒例如移除其中的 HTML 以避免脚本执行因此服务器不应依赖 markdown 中嵌入 HTML 的展示效果。客户端 markdown 解析器能力general.markdown为了让服务器能针对性地生成内容客户端还应通过初始化时声明的general.markdown能力自3.16.0引入告知自己使用的 markdown 解析器/** * Client capabilities specific to the used markdown parser. * * since 3.16.0 */ export interface MarkdownClientCapabilities { /** * The name of the parser. */ parser: string; /** * The version of the parser. */ version?: string; /** * A list of HTML tags that the client allows / supports in * Markdown. * * since 3.17.0 */ allowedTags?: string[]; }parser必填解析器名称version可选解析器版本allowedTags自 3.17.0 起客户端在 markdown 中允许/支持的 HTML 标签白名单。服务器只有在确认某个标签在allowedTags中时才应输出对应的 HTML 标签。文档列举了两个已知的客户端解析器实现ParserVersion说明marked1.1.0基于 marked.js 的 markdown 解析器Python-Markdown3.2.2基于 Python 的 markdown 解析器服务器在生成 markdown 内容前应结合contentFormat偏好顺序与general.markdown解析器能力决定实际返回的格式与标签范围。六、动态注册HoverRegistrationOptions若客户端支持动态注册dynamicRegistration: true服务器可在初始化之后通过client/registerCapability请求动态注册 hover 支持。此时使用的注册选项为export interface HoverRegistrationOptions extends TextDocumentRegistrationOptions, HoverOptions { }它同时继承了两组选项TextDocumentRegistrationOptions通过documentSelector限定该 hover 服务适用的文档过滤器过滤器结构见 _specifications/lsp/3.18/types/documentFilter.mdHoverOptions即上文的能力选项workDoneProgress。与之对应的动态注册客户端能力与服务器声明分别出现在初始化阶段两端的textDocument.hover/hoverProvider字段中见 _specifications/lsp/3.18/general/initialize.md 中hover?: HoverClientCapabilities与hoverProvider?: boolean | HoverOptions的声明。这意味着同一个服务器实现可以仅对特定文档类型如*.py、*.ts启用 hover而不是全局启用。七、错误处理与响应约定当 hover 请求处理过程中发生异常时服务器应返回错误响应其中包含code和message位置无效或文档不存在返回对应协议错误码如-32602Invalid params 或-32600Invalid request服务器内部异常返回-32603Internal error 并附带message说明位置合法但没有可显示的悬停内容返回null而非错误——这是协议规定的正常无结果路径。客户端收到null或错误后均不应弹出任何悬停 UI。值得注意的是hover 请求不支持部分结果partial result——其响应类型Hover | null是单一字面量没有可流式分片的数组形态。部分结果机制partialResultToken仅适用于返回数组的请求如workspace/symbol、textDocument/reference详见 _specifications/lsp/3.18/types/partialResults.md。八、源码佐证元模型中的 hover 定义仓库中的元模型文件 _specifications/lsp/3.18/metaModel/metaModel.json 是协议结构的机器可读权威表达其中可以找到与本文完全对应的条目textDocument/hover请求被正式登记在请求列表中其 method、params 类型与注册选项HoverRegistrationOptions均可在 metaModel.json 中检索到HoverClientCapabilities作为类型定义出现在元模型中文档注释为 Capabilities specific to thetextDocument/hoverrequest.见 metaModel.json。这意味着如果你用该 metaModel.json 生成 SDK 或代码得到的 hover 相关结构会与本文逐字段一致——这也是验证你实现是否符合 3.18 协议的最直接方法。九、完整的 hover 请求-响应示例综合以上所有要素一次完整的 hover 交互如下请求客户端 → 服务器{ jsonrpc: 2.0, id: 2, method: textDocument/hover, params: { textDocument: { uri: file:///folder/file.ts }, position: { line: 9, character: 5 } } }响应服务器 → 客户端使用 MarkupContent{ jsonrpc: 2.0, id: 2, result: { contents: { kind: markdown, value: ## foo\n\nReturns the **foo** value.\n\ntypescript\nfoo(): number\n }, range: { start: { line: 9, character: 4 }, end: { line: 9, character: 7 } } } }无结果响应{ jsonrpc: 2.0, id: 2, result: null }十、实现要点速查服务器侧在初始化响应中声明hoverProvider: true或{ workDoneProgress: true }收到textDocument/hover后依据TextDocumentPositionParams中的 URI 与零基位置查找符号返回Hover字面量优先使用MarkupContentkind: markdown而非已废弃的MarkedString结合客户端contentFormat的顺序选择格式并在生成 HTML 时参考general.markdown.allowedTags白名单。客户端侧在textDocument.hover中声明dynamicRegistration与contentFormat偏好如[markdown, plaintext]并在general.markdown中报告解析器名称、版本与允许的 HTML 标签悬停位置取被悬停字符左侧的位置收到range时用于高亮显示收到null时不弹窗。位置编码默认使用utf-16若服务器实现了utf-8/utf-32需在初始化阶段通过PositionEncodingKind协商确认见 _specifications/lsp/3.18/types/position.md。兼容性对于不支持MarkupContent的旧客户端可以回退到MarkedString字符串形态但新实现应面向MarkupContent编写。通过以上协议结构与实现要点你可以基于 _specifications/lsp/3.18/language/hover.md 及其关联类型文档_specifications/lsp/3.18/types/markupContent.md、_specifications/lsp/3.18/types/textDocumentPositionParams.md、_specifications/lsp/3.18/types/position.md、_specifications/lsp/3.18/types/range.md、_specifications/lsp/3.18/types/workDoneProgress.md完整实现一个符合 3.18 规范、且能与现代编辑器VS Code 等正确互操作的悬停功能。赞分享开发工具【免费下载链接】language-server-protocolDefines a common protocol for language servers.项目地址https://gitcode.com/gh_mirrors/la/language-server-protocol点击查看免费下载相关推荐LSP 文本格式化请求textDocument/formatting协议详解从能力协商到 FormattingOptionsLSP 文本格式化请求textDocument/formatting协议详解从能力协商到 FormattingOptions 本篇指南聚焦语言服务器协议开发工具Language Server Protocol 3.17 Hover 请求详解能力协商、位置语义与 MarkupContent 内容格式Language Server Protocol 3.17 Hover 请求详解能力协商、位置语义与 MarkupContent 内容格式 本篇技术指南以本仓开发工具深入解析 LSP 的 documentHighlight 请求Language Server Protocol 3.18 文档高亮协议全解深入解析 LSP 的 documentHighlight 请求Language Server Protocol 3.18 文档高亮协议全解 textDocum开发工具创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考