- 开发工具
【免费下载链接】language-server-protocol
Defines a common protocol for language servers.
TextDocumentItem是 Language Server Protocol(LSP)中客户端向服务器传输文本文档内容的核心数据结构,承载文档的 URI、语言标识符、版本号与完整文本内容。本文基于本仓库_specifications/lsp/3.19/types/textDocumentItem.md的完整定义,结合textDocument/didOpen、notebookDocument同步等协议用法与 metaModel 机器可读定义,讲解该类型的字段语义、语言标识符取值规范以及实际传输场景,读完即可准确实现 LSP 客户端的文档打开与同步逻辑。
TextDocumentItem:文本文档传输的最小载体
在 LSP 的文本同步机制中,客户端负责管理已打开文档的内容所有权,而服务器则需要通过一种统一的结构接收文档的完整快照。TextDocumentItem正是为此而生的数据传输对象,协议原文对其定位只有一句话:"An item to transfer a text document from the client to the server."(一个用于把文本文档从客户端传输到服务器的数据项),但它承载了服务器理解文档所需的全部信息。
其 TypeScript 定义如下(见 textDocumentItem.md):
interface TextDocumentItem { /** * The text document's URI. */ uri: DocumentUri; /** * The text document's language identifier. */ languageId: string; /** * The version number of this document (it will increase after each * change, including undo/redo). */ version: integer; /** * The content of the opened text document. */ text: string; }该结构一共只有四个必填字段,却构成了服务器端文档模型的完整骨架:
| 字段 | 类型 | 语义 |
|---|---|---|
uri | DocumentUri | 文档在客户端中的唯一资源标识,服务器据此索引与管理文档 |
languageId | string | 文档的语言标识符(如python、typescript),用于多语言场景下避免重新解析文件扩展名 |
version | integer | 文档版本号,每次内容变化(包括撤销/重做)后递增 |
text | string | 文档被打开时的完整文本内容 |
uri:文档的唯一身份标识
uri的类型是DocumentUri。在本仓库的 uri.md 中有关于 URI 的专门说明:文档的 URI 在 LSP 中通常使用file:之类的 scheme,服务器需要把它当作文档的全局唯一标识来缓存状态。值得注意的是,与TextDocumentItem不同,某些场景(如 notebook 单元格文档)中协议刻意让 URI 保持"不透明"——服务器不应依赖其 scheme 或路径格式,这一点在 notebook 同步章节中会进一步展开。
version:版本号驱动的增量同步基础
version字段是 LSP 文档状态机同步的关键。协议明确规定:版本号在每次变更后递增,包括撤销(undo)与重做(redo),但版本号并不要求连续(见 versionedTextDocumentIdentifier.md 中 "The number doesn't need to be consecutive" 的说明)。
TextDocumentItem中的version与VersionedTextDocumentIdentifier中的version语义一致,区别仅在于:前者在文档打开时随完整内容一并送达服务器,而后者用于textDocument/didChange通知中标记"变更之后"的文档版本。在 didChange.md 给出的同步示例中可以看到这套版本机制的实际协作方式:
| 文档版本 | 用户输入 | 客户端行为 | 请求 |
|---|---|---|---|
| 5 | 文档变更一 | 将文档v5同步给服务器 | textDocument/didChange |
| 5 | - | 基于文档v5向服务器发起请求 | textDocument/completion |
| 6 | 文档变更二 | 将文档v6同步给服务器 | textDocument/didChange |
即:客户端在发起请求(如补全、签名帮助)之前,必须先把文档的最新版本同步给服务器,而TextDocumentItem正是"文档打开"这个同步起点的数据载体。
languageId:语言标识符的作用与推荐取值表
languageId字段的协议原意是:当服务器同时处理多种语言时,用它来识别文档属于哪种语言,从而避免对文件扩展名进行二次推断。也就是说,语言判断的第一依据是客户端显式声明的languageId,而不是文件后缀。
协议建议:当文档属于下列编程语言之一时,客户端应使用下表推荐的标识符(该表为协议正式规范的一部分,完整继承自 textDocumentItem.md):
| 语言 | 标识符 |
|---|---|
| ABAP | abap |
| Windows Bat | bat |
| BibTeX | bibtex |
| Clojure | clojure |
| Coffeescript | coffeescript |
| C | c |
| C++ | cpp |
| C# | csharp |
| CSS | css |
| D | d(@since 3.18.0) |
| Delphi | pascal(@since 3.18.0) |
| Diff | diff |
| Dart | dart |
| Dockerfile | dockerfile |
| Elixir | elixir |
| Erlang | erlang |
| F# | fsharp |
| Git | git-commit和git-rebase |
| Go | go |
| Groovy | groovy |
| Handlebars | handlebars |
| Haskell | haskell |
| HTML | html |
| Ini | ini |
| Java | java |
| JavaScript | javascript |
| JavaScript React | javascriptreact |
| JSON | json |
| LaTeX | latex |
| Less | less |
| Lua | lua |
| Makefile | makefile |
| Markdown | markdown |
| Objective-C | objective-c |
| Objective-C++ | objective-cpp |
| Pascal | pascal(@since 3.18.0) |
| Perl | perl |
| Perl 6 | perl6 |
| PHP | php |
| Plaintext | plaintext |
| Powershell | powershell |
| Pug | jade |
| Python | python |
| R | r |
| Razor (cshtml) | razor |
| Ruby | ruby |
| Rust | rust |
| SCSS | scss(花括号语法)、sass(缩进语法) |
| Scala | scala |
| ShaderLab | shaderlab |
| Shell Script (Bash) | shellscript |
| SQL | sql |
| Swift | swift |
| TypeScript | typescript |
| TypeScript React | typescriptreact |
| TeX | tex |
| Text (plain) | plaintext |
| Visual Basic | vb |
| XML | xml |
| XSL | xsl |
| YAML | yaml |
几点值得注意的细节:
- 一语言多标识符:SCSS 区分了
scss(花括号语法)与sass(缩进语法);Git 相关文档则使用git-commit和git-rebase两个标识符。 - 同标识符复用:Delphi 与 Pascal 都使用
pascal,Plaintext 与 Text (plain) 都使用plaintext。 - 版本演进:
d、pascal三个条目是 3.18.0 版本才补充进规范表的(表中以@since 3.18.0标注),说明语言标识符表会随协议版本持续扩充。 - 该表是"建议"而非强制:协议原文用 "it is recommended that clients use those ids" 表述,客户端可以按自身需求使用表外的自定义标识符,但服务器在实现多语言支持时应以该表为基准。
TextDocumentItem 的两个关键使用场景
场景一:textDocument/didOpen 通知——文档打开即快照
TextDocumentItem最典型的使用场景是textDocument/didOpen通知。该通知由客户端发给服务器,用于宣告新打开的文本文档,其参数结构如下(见 didOpen.md):
interface DidOpenTextDocumentParams { /** * The document that was opened. */ textDocument: TextDocumentItem; }协议对 didOpen 语义有几点严格约定,理解这些约定有助于正确构造TextDocumentItem:
- 内容所有权归客户端:文档一旦 open,其内容就由客户端管理,服务器不得再尝试用文档 URI 去磁盘读取内容。因此
textDocument/didOpen中必须携带完整的text快照,这是服务器获得文档内容的唯一途径。 - open/close 必须配对:同一文档在未发送对应 close 通知前,不允许重复发送 open;同一时刻每个文档的 open 计数最多为 1。
- 语言变更需重开:如果文档的语言标识符发生变化(且服务器支持新语言),客户端必须先发送
textDocument/didClose,再以新languageId重新发送textDocument/didOpen。这正是languageId字段在协议流程中的具体作用点。 - open 不等于编辑器展示:open 仅表示"文档内容由客户端管理",并不要求其内容一定显示在编辑器中。
在 2.0 版本之后,didChange 参数中也引入了规范的版本号(见 didChange.md),TextDocumentItem.version与后续VersionedTextDocumentIdentifier.version构成了"打开即定版本、变更则递增"的连续同步链路。
场景二:Notebook 单元格同步——TextDocumentItem 复用
自 3.17.0 起,LSP 增加了 notebook 文档同步能力,TextDocumentItem被复用于同步 notebook 单元格的文本内容。在 notebook.md 中可以看到:
DidOpenNotebookDocumentParams通过cellTextDocuments: TextDocumentItem[]数组,一次性把所有已打开单元格的文本文档快照发给服务器;- notebook 单元格的文本文档 URI 是"不透明"的,由客户端自行生成,服务器不应依赖其格式——因此
TextDocumentItem.uri在单元格场景下只作为唯一标识,不承载路径语义; - 单元格文本文档被视为普通文本文档,始终以增量同步(incremental sync)方式与服务器保持同步。
这体现了TextDocumentItem作为协议基础类型的通用性:无论普通文档还是 notebook 单元格,客户端向服务器"交底"文档内容时使用的都是同一结构。
从 metaModel 验证结构定义的机器可读形态
本仓库在_specifications/lsp/3.19/metaModel/目录下提供了协议的机器可读元模型,其中 metaModel.json 对TextDocumentItem做了与文档一致的 JSON 描述:
{ "name": "TextDocumentItem", "properties": [ { "name": "uri", "type": { "kind": "base", "name": "DocumentUri" }, "documentation": "The text document's uri." }, { "name": "languageId", "type": { "kind": "reference", "name": "LanguageKind" }, "documentation": "The text document's language identifier." }, { "name": "version", "type": { "kind": "base", "name": "integer" }, "documentation": "The version number of this document (it will increase after each change, including undo/redo)." }, { "name": "text", "type": { "kind": "base", "name": "string" }, "documentation": "The content of the opened text document." } ], "documentation": "An item to transfer a text document from the client to the server." }从该元模型可以看出:languageId在元模型中被引用为LanguageKind类型(对应文档中的语言标识符表),uri被定义为DocumentUri基础类型,version为integer,text为string,四个字段均为必填(无optional标记)。对应的 TypeScript 类型定义见 metaModel.ts,其中声明了DocumentUri、integer、string等基础类型(BaseTypes),可作为实现代码生成器或协议校验器的直接依据。
实现要点与常见误区
结合以上协议细节,客户端与服务器在实现TextDocumentItem时应注意以下几点:
- 必填字段不可省略:
uri、languageId、version、text四字段全部必填,缺少任一字段都会导致服务器无法建立完整的文档快照。 - 首次打开必须携带全量文本:
didOpen是一次性的全量传输,服务器不会也不会去磁盘读取文档,所以text必须是打开时刻的完整内容。 - languageId 优先于扩展名:服务器做语言分派时应优先信任
languageId,不要为每个请求重新根据 URI 后缀推断语言;多语言场景下这正是该字段的设计初衷。 - 版本号只增不减:
version在每次变更(含撤销/重做)后递增,不需要连续,但必须单调递增;服务器可用它做请求与内容快照的匹配。 - Notebook 单元格 URI 保持不透明:当
TextDocumentItem用于 notebook 单元格时,不要对 URI 的 scheme 或路径做任何假设。
相关文档导航
- 类型定义原文:textDocumentItem.md(本文核心依据,3.18 版本定义见 3.18 版本)
- 使用场景:didOpen.md、didChange.md、notebook.md
- 关联类型:versionedTextDocumentIdentifier.md、uri.md
- 机器可读定义:metaModel.json、metaModel.ts
- 开发工具
【免费下载链接】language-server-protocol
Defines a common protocol for language servers.
相关推荐
Language Server Protocol 3.17 TextDocumentItem 详解:客户端到服务器的文本文档传输载体
Language Server Protocol 3.17 TextDocumentItem 详解:客户端到服务器的文本文档传输载体 TextDocumentI
开发工具Language Server Protocol 3.18 `window/logMessage` 通知详解:从服务器向客户端传递日志消息
Language Server Protocol 3.18 window/logMessage 通知详解:从服务器向客户端传递日志消息 window/logMe
开发工具language-server-protocol 3.17 规范精读:TextDocumentIdentifier 与文本文档 URI 标识机制
language server protocol 3.17 规范精读:TextDocumentIdentifier 与文本文档 URI 标识机制 导读 Text
开发工具
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考