语言服务器开发指南

语言服务器介绍

语言服务器(Language Server)让语言开发者不必为每种编辑器都编写一个语言插件,只需要实现一次语言服务器,就可以很方便的在各种编辑器上实现语言服务功能;同样的,如果您想要开发一个插件,它只包含一些语言功能,那么语言服务器也可以用来帮助您开发适用各种编辑器的通用插件

multi-editor.png

如何阅读本指南

本指南对语言服务协议进行了翻译,然后基于语言服务协议实现了一个简单的语言服务器,最后介绍了实际语言服务器插件开发的详细步骤。

  • 如果您已经看完了英文原版语言服务协议,但对实际的功能并不了解,那么您可以直接跳到对应功能的位置进行阅读,为了便于理解,我们给部分功能增加了用于演示的 插图 或者 GIF 动图

  • 如果您对语言服务协议感兴趣,想要借此开发自己的插件,但对英语头疼,那么建议您从头到尾阅读完本指南,相信可以让您有所收获。

  • 如果您正在寻找 语言服务器插件 的开发流程,建议直接阅读 语言服务器插件开发

  • 如果您已经读完了语言服务协议,想要更深入的理解它,建议阅读 语言服务器实现

交流讨论

如果您在阅读语言服务协议或者本指南的过程中有任何问题,欢迎给我发邮件交流讨论: demonre@foxmail.com

LSP

在开发语言服务器之前必须了解语言服务协议(Language Server Protocol,简称LSP),它是语言服务器开发的基础,语言服务器 遵守 语言服务协议 与编辑器进行通信。

如果英文水平不错,建议先阅读LSP原文:language-server-protocol

如果没有阅读原文也没关系,本章结合原文和笔者的理解对LSP做了详细的讲解。

当前LSP的最新版本是:3.17,本章以此版本为标准。

基础协议

基础协议由标头和内容组成。标头和内容之间使用 \r\n 分隔。

标头

标头由标头字段组成。每个标头字段包含一个名称和值,用 : (冒号和空格)分隔。标头字段的结构符合 HTTP 语义。每个标头字段都以 \r\n 结尾。考虑到每个标头字段和整个标头都以 \r\n 结尾,并且至少有一个标头字段,这意味着在内容之前始终都有两个 \r\n

目前支持的标头字段:

标头字段名值类型描述
Content-Lengthnumber内容部分以字节计算的长度,这个标头是必须的。
Content-Typestring内容部分的 MIME 类型。默认是 application/vscode-jsonrpc; charset=utf-8

标头部分使用 ascii 编码进行编码。这包括分隔标题和内容部分的 \r\n

内容

包含消息实际的内容。消息的内容部分使用 JSON-RPC 来描述请求响应通知。内容部分使用 Content-Type 字段中提供的字符集进行编码。它默认为 utf-8,这是目前唯一支持的编码。如果服务器客户端收到的标头编码与 utf-8 不同,则应响应错误。

该协议的早期版本使用字符串常量 utf8,根据规范,它不是正确的编码常量。为了向后兼容,强烈建议客户端和服务器将字符串 utf8 视为 utf-8

示例:

Content-Length: ...\r\n
\r\n
{
	"jsonrpc": "2.0",
	"id": 1,
	"method": "textDocument/completion",
	"params": {
		...
	}
}

基础协议 JSON 结构

以下 TypeScript 定义描述了基本 JSON-RPC 协议:

基础类型

该协议对整数、无符号整数、十进制数、对象和数组使用以下定义:

/**
 * Defines an integer number in the range of -2^31 to 2^31 - 1.
 */
export type integer = number;
/**
 * Defines an unsigned integer number in the range of 0 to 2^31 - 1.
 */
export type uinteger = number;
/**
 * Defines a decimal number. Since decimal numbers are very
 * rare in the language server specification we denote the
 * exact range with every decimal using the mathematics
 * interval notation (e.g. [0, 1] denotes all decimals d with
 * 0 <= d <= 1.
 */
export type decimal = number;
/**
 * The LSP any type
 *
 * @since 3.17.0
 */
export type LSPAny = LSPObject | LSPArray | string | integer | uinteger |
	decimal | boolean | null;
/**
 * LSP object definition.
 *
 * @since 3.17.0
 */
export type LSPObject = { [key: string]: LSPAny };
/**
 * LSP arrays.
 *
 * @since 3.17.0
 */
export type LSPArray = LSPAny[];

抽象消息

由 JSON-RPC 定义的常规消息。语言服务器协议始终使用 "2.0" 作为 jsonrpc 版本。

interface Message {
	jsonrpc: string;
}

请求消息

用于描述客户端和服务器之间的请求的请求消息(可以由客户端发出,也可以由服务端发出)。每个已处理的请求都必须将响应发送回请求的发送方。

interface RequestMessage extends Message {

	/**
	 * The request id.
	 */
	id: integer | string;

	/**
	 * The method to be invoked.
	 */
	method: string;

	/**
	 * The method's params.
	 */
	params?: array | object;
}

响应消息

响应消息作为请求的结果发送。如果请求未提供结果值,请求的接收方仍需要返回响应消息以符合 JSON-RPC 规范。在这种情况下,应将 ResponseMessageresult 属性设置为 null 以表示请求成功。

interface ResponseMessage extends Message {
	/**
	 * The request id.
	 */
	id: integer | string | null;

	/**
	 * The result of a request. This member is REQUIRED on success.
	 * This member MUST NOT exist if there was an error invoking the method.
	 */
	result?: string | number | boolean | array | object | null;

	/**
	 * The error object in case a request fails.
	 */
	error?: ResponseError;
}
interface ResponseError {
	/**
	 * A number indicating the error type that occurred.
	 */
	code: integer;

	/**
	 * A string providing a short description of the error.
	 */
	message: string;

	/**
	 * A primitive or structured value that contains additional
	 * information about the error. Can be omitted.
	 */
	data?: string | number | boolean | array | object | null;
}
export namespace ErrorCodes {
	// Defined by JSON-RPC
	export const ParseError: integer = -32700;
	export const InvalidRequest: integer = -32600;
	export const MethodNotFound: integer = -32601;
	export const InvalidParams: integer = -32602;
	export const InternalError: integer = -32603;

	/**
	 * This is the start range of JSON-RPC reserved error codes.
	 * It doesn't denote a real error code. No LSP error codes should
	 * be defined between the start and end range. For backwards
	 * compatibility the `ServerNotInitialized` and the `UnknownErrorCode`
	 * are left in the range.
	 *
	 * @since 3.16.0
	 */
	export const jsonrpcReservedErrorRangeStart: integer = -32099;
	/** @deprecated use jsonrpcReservedErrorRangeStart */
	export const serverErrorStart: integer = jsonrpcReservedErrorRangeStart;

	/**
	 * Error code indicating that a server received a notification or
	 * request before the server has received the `initialize` request.
	 */
	export const ServerNotInitialized: integer = -32002;
	export const UnknownErrorCode: integer = -32001;

	/**
	 * This is the end range of JSON-RPC reserved error codes.
	 * It doesn't denote a real error code.
	 *
	 * @since 3.16.0
	 */
	export const jsonrpcReservedErrorRangeEnd = -32000;
	/** @deprecated use jsonrpcReservedErrorRangeEnd */
	export const serverErrorEnd: integer = jsonrpcReservedErrorRangeEnd;

	/**
	 * This is the start range of LSP reserved error codes.
	 * It doesn't denote a real error code.
	 *
	 * @since 3.16.0
	 */
	export const lspReservedErrorRangeStart: integer = -32899;

	/**
	 * A request failed but it was syntactically correct, e.g the
	 * method name was known and the parameters were valid. The error
	 * message should contain human readable information about why
	 * the request failed.
	 *
	 * @since 3.17.0
	 */
	export const RequestFailed: integer = -32803;

	/**
	 * The server cancelled the request. This error code should
	 * only be used for requests that explicitly support being
	 * server cancellable.
	 *
	 * @since 3.17.0
	 */
	export const ServerCancelled: integer = -32802;

	/**
	 * The server detected that the content of a document got
	 * modified outside normal conditions. A server should
	 * NOT send this error code if it detects a content change
	 * in it unprocessed messages. The result even computed
	 * on an older state might still be useful for the client.
	 *
	 * If a client decides that a result is not of any use anymore
	 * the client should cancel the request.
	 */
	export const ContentModified: integer = -32801;

	/**
	 * The client has canceled a request and a server has detected
	 * the cancel.
	 */
	export const RequestCancelled: integer = -32800;

	/**
	 * This is the end range of LSP reserved error codes.
	 * It doesn't denote a real error code.
	 *
	 * @since 3.16.0
	 */
	export const lspReservedErrorRangeEnd: integer = -32800;
}

通知消息

通知消息。已处理的通知消息不得发回响应。它们像事件一样工作。

interface NotificationMessage extends Message {
	/**
	 * The method to be invoked.
	 */
	method: string;

	/**
	 * The notification's params.
	 */
	params?: array | object;
}

$通知和请求

方法以 $/ 开头的通知和请求是依赖于协议(由客户端和服务器自行约定)实现的消息,可能无法在所有客户端或服务器中实现。例如,如果服务器实现使用单线程同步编程语言,则服务器几乎无法对 $/cancelRequest 通知做出反应。如果服务器或客户端收到以 $/ 开头的通知,则可以忽略该通知。如果服务器或客户端收到以 $/ 开头的请求,则必须使用错误代码 MethodNotFound (例如 -32601)响应请求。

支持取消

基本协议支持取消请求。若要取消请求,将发送具有以下属性的通知消息:

NotificationMessage:

  • method: "$/cancelRequest"
  • params: CancelParams 定义如下:
interface CancelParams {
	/**
	 * The request id to cancel.
	 */
	id: integer | string;
}

已取消的请求仍需要从服务器返回并发送回响应。它不能保持打开/挂起。这符合 JSON-RPC 协议,该协议要求每个请求都发送回响应。此外,它还允许在取消时返回部分结果。如果请求在取消时返回错误响应,建议将错误代码设置为 ErrorCodes.RequestCancelled。

支持进度

Since version 3.15.0

基本协议还支持以通用方式报告进度。此机制可用于报告任何类型的进度,包括已完成的工作进度(通常用于使用进度条在用户界面中报告进度)和部分结果进度,以支持结果流式处理。

进度通知具有以下属性:

NotificationMessage:

  • method: "$/progress"
  • params: ProgressParams 定义如下:
interface ProgressParams<T> {
	/**
	 * The progress token provided by the client or server.
	 */
	token: ProgressToken;

	/**
	 * The progress data.
	 */
	value: T;
}
type ProgressToken = integer | string;

泛型 T 由协议约定

针对 token 报告进度。token 与请求 id 不同,它允许带外数据报告进度。

语言服务协议

语言服务器协议定义了一组 JSON-RPC 请求、响应和通知消息,这些消息使用 基本协议 进行交换。本节开始介绍协议中使用的基本 JSON 结构。本文档使用 TypeScript 在严格模式下的 interface 来描述这些内容。这意味着,例如,必须显式列出 null 值,并且必须列出必需属性,即使可能存在窜改值。基于基本的 JSON 结构,描述了实际请求及其响应和通知。

例如,从客户端向服务器发送请求,以请求文本文档中特定位置的符号的悬停提示。请求的方法将是 textDocument/hover,其参数如下:

interface HoverParams {
	textDocument: string; /** The text document's URI in string form */
	position: { line: uinteger; character: uinteger; };
}

请求的结果是将要呈现的悬浮提示。在其简单形式中,它可以是一个字符串。所以结果如下所示:

interface HoverResult {
	value: string;
}

另请注意,响应返回值为 null 表示没有结果。它不会告诉客户端重新发送请求。

通常,语言服务协议支持 JSON-RPC 消息,但是,我们约定传递给 请求通知消息的参数为对象类型(如果传递的话)。但是,这并不禁止在自定义消息中使用数组参数类型。

该协议目前假定一个服务器服务于一个工具。该协议目前不支持在不同工具之间共享一个服务器。这种共享将需要额外的协议,例如锁定文档以支持并发编辑。

能力 (Capabilities)

并非每个语言服务器都可以支持协议定义的所有功能。LSP 因此提供了 capabilities。一个能力组对应一套语言功能。开发工具和语言服务器使用 capabilities 宣布其支持的功能。例如,例如,服务器宣布它可以处理 textDocument/hover 请求,但可能无法处理 workspace/symbol 请求。同样,开发工具宣布其能够在保存文档之前提供关于保存的通知,以便服务器可以在保存之前计算文本以格式化编辑的文档。

在初始化 initialize 请求期间,客户端和服务器之间交换 capabilities 的集合。

请求、响应和通知的顺序

对请求的响应的发送顺序应与请求在服务器端或客户端上出现的顺序大致相同。因此,例如,如果服务器收到 textDocument/completion 请求,然后收到 textDocument/signatureHelp 请求,它通常会首先返回 textDocument/completion 的响应,然后返回 textDocument/signatureHelp 的响应。

但是,服务器可能决定使用并行执行策略,并且可能希望以与收到请求不同的顺序返回响应。只要此重新排序不影响响应的正确性,服务器就可以这样做。例如,允许对 textDocument/completiontextDocument/signatureHelp 的结果重新排序,因为这些请求中的每一个通常不会影响另一个请求的输出。另一方面,服务器很可能不应该对 textDocument/definitiontextDocument/rename 请求重新排序,因为执行后者可能会影响前者的结果。

消息文档

和前面一样,LSP 定义了一组请求、响应和通知。在后面对这些消息的描述中,每一项都使用以下格式进行描述:

  • 描述请求的标头

  • 一个可选的 客户端能力(Client capability) 部分,描述了请求的客户端能力。这包括客户端能力属性路径和 json 结构。

  • 一个可选的 服务端能力(Server Capability) 部分,描述了请求的服务端能力,这包括服务端能力属性路径和 json 结构。客户端应该忽略他们不理解的服务端功能(例如,在这种情况下,初始化请求不应该失败)。

  • 一个可选的 注册选项(Registration Options) 部分,描述请求或通知支持动态功能注册时的注册选项。请参阅 注册注销 请求,详细了解其工作原理。

  • 一个 请求(Request) 部分描述发送请求的格式。该方法是标识请求的字符串,参数使用 TypeScript 接口进行记录。它还记录了请求是否支持已完成的工作进度和部分结果进度。

  • 一个 响应(Response) 部分描述了响应的格式。结果项描述成功时返回的数据。可选的部分结果项描述部分结果通知的返回数据。error.data 描述发生错误时返回的数据。请记住,如果失败,响应已包含 error.codeerror.message 字段。仅当协议强制使用某些错误代码或消息时,才指定这些字段。如果服务器可以自由决定这些值,则此处未列出这些值。

基础 JSON 结构

有相当多的 JSON 结构在不同的请求和通知之间共享。本节介绍了它们的结构和功能。

URI

URI 作为字符串传输。URI 的格式在 https://tools.ietf.org/html/rfc3986 中定义

 foo://example.com:8042/over/there?name=ferret#nose
  \_/   \______________/\_________/ \_________/ \__/
   |           |            |            |        |
scheme     authority       path        query   fragment
   |   _____________________|__
  / \ /                        \
  urn:example:animal:ferret:nose

LSP 团队也维护了一个 node 模块,用于将字符串解析为 schemeauthoritypathqueryfragment 的 URI 组件。Github 仓库地址是 https://github.com/Microsoft/vscode-uri, 以及 npm 包地址 https://www.npmjs.com/package/vscode-uri

许多 interface 定义中都包含与文档的 URI 相对应的字段。为清楚起见,此类字段的类型声明为 DocumentUri。通过网络,它仍将作为字符串传输,但这可以保证该字符串的内容可以解析为有效的 URI。

应注意处理 URI 中的编码。例如,某些客户端(如 VS Code)可能会对驱动器号中的冒号进行编码,而其他客户端则不会。下面的 URI 都是有效的,但客户端和服务器应与它们自己使用的表单一致,以确保另一方不会将它们解释为不同的 URI。客户端和服务器不应假定彼此的编码方式相同(例如,在驱动器号中编码冒号的客户端不能假定服务器响应将具有编码的冒号)。这同样适用于驱动器号的大小写 - 一方不应假设另一方将返回与自身大小写相同的驱动器号的路径。

file:///c:/project/readme.md
file:///C%3A/project/readme.md
type DocumentUri = string;

还有一个用于普通非文档 URI 的标记接口。它也映射到字符串。

type URI = string;

正则表达式

正则表达式是一个强大的工具,在语言服务器协议中也有它们的实际用例。然而,它们的缺点是几乎每种编程语言都有自己的一组正则表达式功能,因此规范不能简单地将它们称为正则表达式。因此,LSP 使用两步方法来支持正则表达式:

  • 客户端将宣布它将使用哪个正则表达式引擎。这将允许为非常特定的客户端编写的服务器充分利用客户端的正则表达式功能

  • 该规范将定义一组客户端应支持的正则表达式功能。LSP 不会编写新规范,而是引用 ECMAScript 正则表达式规范,并从中删除在 LSP 上下文中不需要或难以为其他客户端实现的功能。

Client capability:

以下客户端能力用于宣布客户端的正则表达式引擎

  • 属性路径: general.regularExpressions

  • 属性类型: RegularExpressionsClientCapabilities 定义如下:

/**
 * Client capabilities specific to regular expressions.
 */
export interface RegularExpressionsClientCapabilities {
	/**
	 * The engine's name.
	 */
	engine: string;

	/**
	 * The engine's version.
	 */
	version?: string;
}

下表列出了众所周知的引擎值。请注意,该表应由将 LSP 集成到现有客户端的社区驱动。该规范的目标不是列出所有可用的正则表达式引擎。

EngineVersionDocumentation
ECMAScriptES2020ECMAScript 2020 & MDN

正则表达式子集:

ECMAScript 2020 正则表达式规范中的以下功能对于客户端不是必需的:

  • 断言:前瞻断言、否定前瞻断言、后视断言、否定后瞻断言。

  • 字符类:使用插入符号匹配控制字符(例如 \cX)和匹配 UTF-16 代码单元(例如 \uhhhh)。

  • 组和范围:命名捕获组。

  • Unicode 属性转义:不需要支持任何功能。

客户端需要支持的唯一正则表达式标志是“i”,用于指定不区分大小写的搜索。

枚举

该协议支持两种枚举:(a) 基于整数的枚举和 (b) 基于字符串的枚举。基于整数的枚举通常以 1 开头。之前编写的,不是以1开头的,也被保留以保持向后兼容。如果合适,枚举的值集由定义端(例如客户端或服务器)宣布,并在初始化握手期间传输到另一端。一个示例是 CompletionItemKind 枚举。它由客户端使用 textDocument.completion.completionItemKind 客户端属性宣布。

为了支持枚举的演变,枚举的使用端不应在它不知道的枚举值上失败。它应该简单地忽略它作为一个可以使用的价值,并尽最大努力在往返时保持这个价值。让我们再次以 CompletionItemKind 枚举为例:如果在规范的未来版本中,客户端添加并宣布了值为 n 的附加完成项类型,则不知道该值的(较旧的)服务器不应失败,而只是忽略该值作为可用项类型的值。

文本文档

当前协议是为内容可以表示为字符串的文本文档量身定制的。目前不支持二进制文档。文档中的位置(请参阅下面的位置定义)表示为从零开始的行和字符的偏移量。

3.17 中的新功能

在 3.17 之前,偏移量始终基于 UTF-16 字符串表示。因此,在 a𐐨b 形式的字符串中,字符 a 的字符偏移量为 0,𐐨 的字符偏移量为 1,b 的字符偏移量为 3,因为 𐐀 使用 UTF-16 中的两个代码单元表示。从 3.17 开始,客户端和服务器可以就不同的字符串编码表示(例如 UTF-8)达成一致。客户端通过客户端功能 general.positionEncoding 宣布支持编码。该值是客户端支持的位置编码数组,优先级递减(例如,索引 0 处的编码是首选编码)。为了保持向后兼容,唯一的强制性编码是 UTF-16,通过字符串 utf-16 表示。服务器可以选择客户端提供的编码之一,并通过初始化结果的属性 capabilities.positionCoding 将该编码信号传回客户端。如果客户端的功能中缺少字符串值 utf-16,则 general.positionEncodings 服务器可以安全地假定客户端支持 UTF-16。如果服务器在其初始化结果中省略了位置编码,则编码默认为字符串值 utf-16。实现注意事项:由于从一种编码到另一种编码的转换需要文件/行的内容,因此最好在读取文件的位置进行转换,这通常是在服务器端。

为了确保客户端和服务器将字符串拆分为相同的行表示形式,该协议指定了以下行尾序列:"\n"、"\r\n" 和 "\r"。位置与行尾字符无关。因此,您不能指定表示 \r|\n 或 \n| 的位置,其中 | 表示字符偏移量。

export const EOL: string[] = ['\n', '\r\n', '\r'];

Position

在文本文档中的位置,表示为从零开始的行和从零开始的字符偏移量。位置位于两个字符之间,就像编辑器中的 "插入" 光标一样。不支持特殊值,例如 -1 表示行尾。

interface 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;
}

在描述位置时,协议需要指定如何解释偏移量(特别是字符偏移量)。相应的 PositionEncodingKind 在初始化期间在客户端和服务器之间协商。

/**
 * A type indicating how positions are encoded,
 * specifically what column offsets mean.
 *
 * @since 3.17.0
 */
export type PositionEncodingKind = string;

/**
 * A set of predefined position encoding kinds.
 *
 * @since 3.17.0
 */
export namespace PositionEncodingKind {

	/**
	 * Character offsets count UTF-8 code units (e.g bytes).
	 */
	export const UTF8: PositionEncodingKind = 'utf-8';

	/**
	 * Character offsets count UTF-16 code units.
	 *
	 * This is the default and must always be supported
	 * by servers
	 */
	export const UTF16: PositionEncodingKind = 'utf-16';

	/**
	 * Character offsets count UTF-32 code units.
	 *
	 * Implementation note: these are the same as Unicode code points,
	 * so this `PositionEncodingKind` may also be used for an
	 * encoding-agnostic representation of character offsets.
	 */
	export const UTF32: PositionEncodingKind = 'utf-32';
}

Range

文本文档中的范围,表示为(从零开始)开始和结束位置。范围与编辑器中的选区相当。因此,结束位置是排他性的。如果要指定包含包含行尾字符的行的范围,请使用表示下一行开头的结束位置。例如:

{
    start: { line: 5, character: 23 },
    end : { line: 6, character: 0 }
}
interface Range {
	/**
	 * The range's start position.
	 */
	start: Position;

	/**
	 * The range's end position.
	 */
	end: Position;
}

TextDocumentItem

用于将文本文档从客户端传输到服务器的项。

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;
}

文本文档具有语言标识符,用于在处理多种语言时在服务器端标识文档,以避免重新解释文件扩展名。如果文档引用下面列出的编程语言之一,则建议客户端使用这些 ID。

LanguageIdentifier
ABAPabap
Windows Batbat
BibTeXbibtex
Clojureclojure
Coffeescriptcoffeescript
Cc
C++cpp
C#csharp
CSScss
Diffdiff
Dartdart
Dockerfiledockerfile
Elixirelixir
Erlangerlang
F#fsharp
Gitgit-commit and git-rebase
Gogo
Groovygroovy
Handlebarshandlebars
HTMLhtml
Iniini
Javajava
JavaScriptjavascript
JavaScript Reactjavascriptreact
JSONjson
LaTeXlatex
Lessless
Lualua
Makefilemakefile
Markdownmarkdown
Objective-Cobjective-c
Objective-C++objective-cpp
Perlperl
Perl 6perl6
PHPphp
Powershellpowershell
Pugjade
Pythonpython
Rr
Razor (cshtml)razor
Rubyruby
Rustrust
SCSSscss (syntax using curly brackets), sass (indented syntax)
Scalascala
ShaderLabshaderlab
Shell Script (Bash)shellscript
SQLsql
Swiftswift
TypeScripttypescript
TypeScript Reacttypescriptreact
TeXtex
VisualBasic vb
XMLxml
XSLxsl
YAMLyaml

TextDocumentIdentifier

文本文档使用 URI 进行标识。在协议级别,URI 作为字符串传递。相应的 JSON 结构如下所示:

interface TextDocumentIdentifier {
	/**
	 * The text document's URI.
	 */
	uri: DocumentUri;
}

VersionedTextDocumentIdentifier

表示文本文档的特定版本的标识符。此信息通常从客户端流向服务器。

interface VersionedTextDocumentIdentifier extends TextDocumentIdentifier {
	/**
	 * The version number of this document.
	 *
	 * The version number of a document will increase after each change,
	 * including undo/redo. The number doesn't need to be consecutive.
	 */
	version: integer;
}

一个标识符,可选择性地表示文本文档的特定版本。此信息通常从服务器流向客户端。

interface OptionalVersionedTextDocumentIdentifier extends TextDocumentIdentifier {
	/**
	 * The version number of this document. If an optional versioned text document
	 * identifier is sent from the server to the client and the file is not
	 * open in the editor (the server has not received an open notification
	 * before) the server can send `null` to indicate that the version is
	 * known and the content on disk is the master (as specified with document
	 * content ownership).
	 *
	 * The version number of a document will increase after each change,
	 * including undo/redo. The number doesn't need to be consecutive.
	 */
	version: integer | null;
}

TextDocumentPositionParams

是 1.0 中的 TextDocumentPosition,带有内联参数。

用于传递文本文档和该文档内位置的请求中使用的参数文本。在发出文本文档请求时,由客户决定如何将选择转换为位置。例如,客户端可以遵循或忽略选择方向,以使 LSP 请求与内部实现的功能一致。

interface TextDocumentPositionParams {
	/**
	 * The text document.
	 */
	textDocument: TextDocumentIdentifier;

	/**
	 * The position inside the text document.
	 */
	position: Position;
}

DocumentFilter

文档筛选器通过 languageschemepattern 等属性来表示文档。例如,适用于磁盘上的 TypeScript 文件的筛选器。另一个示例是适用于名称为 package.json 的 JSON 文件的筛选器:

{ language: 'typescript', scheme: 'file' }
{ language: 'json', pattern: '**/package.json' }
export interface DocumentFilter {
	/**
	 * A language id, like `typescript`.
	 */
	language?: string;

	/**
	 * A Uri [scheme](#Uri.scheme), like `file` or `untitled`.
	 */
	scheme?: string;

	/**
	 * A glob pattern, like `*.{ts,js}`.
	 *
	 * Glob patterns can have the following syntax:
	 * - `*` to match one or more characters in a path segment
	 * - `?` to match on one character in a path segment
	 * - `**` to match any number of path segments, including none
	 * - `{}` to group sub patterns into an OR expression. (e.g. `**​/*.{ts,js}`
	 *   matches all TypeScript and JavaScript files)
	 * - `[]` to declare a range of characters to match in a path segment
	 *   (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
	 * - `[!...]` to negate a range of characters to match in a path segment
	 *   (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but
	 *   not `example.0`)
	 */
	pattern?: string;
}

请注意,要使文档筛选器有效,必须至少设置 languageschemepattern 的一个属性。为了保持类型定义的简单性,所有属性都标记为可选。

文档选择器是一个或多个文档筛选器的组合。

export type DocumentSelector = DocumentFilter[];

TextEdit & AnnotatedTextEdit

3.16 新版功能: 支持 AnnotatedTextEdit

适用于文本文档的文本编辑。

interface TextEdit {
	/**
	 * The range of the text document to be manipulated. To insert
	 * text into a document create a range where start === end.
	 */
	range: Range;

	/**
	 * The string to be inserted. For delete operations use an
	 * empty string.
	 */
	newText: string;
}

从 3.16.0 开始,还有带注释的文本编辑的概念,它支持向文本编辑添加注释。批注可以添加描述文本编辑更改的信息。

/**
 * Additional information that describes document changes.
 *
 * @since 3.16.0
 */
export interface ChangeAnnotation {
	/**
	 * A human-readable string describing the actual change. The string
	 * is rendered prominent in the user interface.
	 */
	label: string;

	/**
	 * A flag which indicates that user confirmation is needed
	 * before applying the change.
	 */
	needsConfirmation?: boolean;

	/**
	 * A human-readable string which is rendered less prominent in
	 * the user interface.
	 */
	description?: string;
}

通常,客户端会提供选项,以根据与更改关联的注释对更改进行分组。为了在协议中支持这一点,编辑或资源操作使用标识符(ChangeAnnotationIdentifier) 引用 更改注释(ChangeAnnotation),而不是直接使用 更改注释(ChangeAnnotation) 字面量。这允许服务器在多个编辑或资源操作中使用相同的注释,然后允许客户端将操作分组到该更改注释(ChangeAnnotation)下。实际的更改注释及其标识符由 WorkspaceEdit 通过新属性 changeAnnotations 进行管理。

/**
 * An identifier referring to a change annotation managed by a workspace
 * edit.
 *
 * @since 3.16.0.
 */
export type ChangeAnnotationIdentifier = string;

/**
 * A special text edit with an additional change annotation.
 *
 * @since 3.16.0.
 */
export interface AnnotatedTextEdit extends TextEdit {
	/**
	 * The actual annotation identifier.
	 */
	annotationId: ChangeAnnotationIdentifier;
}

TextEdit[]

复杂的文本操作用 TextEditAnnotatedTextEdit 的数组来描述,表示对文档的单个更改。

所有文本编辑范围均是指在应用所以更改前的文本文档中的位置。因此,它们将文档从状态 S1 移动到 S2,而不描述任何中间状态。文本编辑范围绝不能重叠,这意味着原始文档的任何部分都不得由多个编辑操作。但是,多个编辑可能具有相同的起始位置:多个插入,或任意数量的插入,然后进行一次删除或替换编辑。如果多个插入具有相同的位置,则数组中的顺序定义插入的字符串在生成的文本中的显示顺序。

TextDocumentEdit

3.16 新版功能: 支持 AnnotatedTextEdit, 此支持由客户端功能 workspace.workspaceEdit.changeAnnotationSupport 守护,如果客户端未发出该功能的信号,则服务器不应将 AnnotatedTextEdit 字面量发送回客户端。

描述单个文本文档上的文本更改。文本文档被引用为 OptionalVersionedTextDocumentIdentifier,以允许客户端在应用编辑之前检查文本文档版本。TextDocumentEdit 描述版本 Si 上的所有更改,并在应用这些更改后将文档移动到版本 Si+1。因此,TextDocumentEdit 的创建者无需对编辑数组进行排序或进行任何排序。但是,编辑必须不重叠。

export interface TextDocumentEdit {
	/**
	 * The text document to change.
	 */
	textDocument: OptionalVersionedTextDocumentIdentifier;

	/**
	 * The edits to be applied.
	 *
	 * @since 3.16.0 - support for AnnotatedTextEdit. This is guarded by the
	 * client capability `workspace.workspaceEdit.changeAnnotationSupport`
	 */
	edits: (TextEdit | AnnotatedTextEdit)[];
}

Location

表示资源内的位置,如文本文件中的一行。

interface Location {
	uri: DocumentUri;
	range: Range;
}

表示源位置和目标位置之间的链接。

interface LocationLink {

	/**
	 * Span of the origin of this link.
	 *
	 * Used as the underlined span for mouse interaction. Defaults to the word
	 * range at the mouse position.
	 */
	originSelectionRange?: Range;

	/**
	 * The target resource identifier of this link.
	 */
	targetUri: DocumentUri;

	/**
	 * The full target range of this link. If the target for example is a symbol
	 * then target range is the range enclosing this symbol not including
	 * leading/trailing whitespace but everything else like comments. This
	 * information is typically used to highlight the range in the editor.
	 */
	targetRange: Range;

	/**
	 * The range that should be selected and revealed when this link is being
	 * followed, e.g the name of a function. Must be contained by the
	 * `targetRange`. See also `DocumentSymbol#range`
	 */
	targetSelectionRange: Range;
}

Diagnostic

表示诊断,如编译器错误或警告。诊断对象仅在资源范围内有效。

export interface Diagnostic {
	/**
	 * The range at which the message applies.
	 */
	range: Range;

	/**
	 * The diagnostic's severity. Can be omitted. If omitted it is up to the
	 * client to interpret diagnostics as error, warning, info or hint.
	 */
	severity?: DiagnosticSeverity;

	/**
	 * The diagnostic's code, which might appear in the user interface.
	 */
	code?: integer | string;

	/**
	 * An optional property to describe the error code.
	 *
	 * @since 3.16.0
	 */
	codeDescription?: CodeDescription;

	/**
	 * A human-readable string describing the source of this
	 * diagnostic, e.g. 'typescript' or 'super lint'.
	 */
	source?: string;

	/**
	 * The diagnostic's message.
	 */
	message: string;

	/**
	 * Additional metadata about the diagnostic.
	 *
	 * @since 3.15.0
	 */
	tags?: DiagnosticTag[];

	/**
	 * An array of related diagnostic information, e.g. when symbol-names within
	 * a scope collide all definitions can be marked via this property.
	 */
	relatedInformation?: DiagnosticRelatedInformation[];

	/**
	 * A data entry field that is preserved between a
	 * `textDocument/publishDiagnostics` notification and
	 * `textDocument/codeAction` request.
	 *
	 * @since 3.16.0
	 */
	data?: unknown;
}
export namespace DiagnosticSeverity {
	/**
	 * Reports an error.
	 */
	export const Error: 1 = 1;
	/**
	 * Reports a warning.
	 */
	export const Warning: 2 = 2;
	/**
	 * Reports an information.
	 */
	export const Information: 3 = 3;
	/**
	 * Reports a hint.
	 */
	export const Hint: 4 = 4;
}

export type DiagnosticSeverity = 1 | 2 | 3 | 4;
/**
 * The diagnostic tags.
 *
 * @since 3.15.0
 */
export namespace DiagnosticTag {
	/**
	 * Unused or unnecessary code.
	 *
	 * Clients are allowed to render diagnostics with this tag faded out
	 * instead of having an error squiggle.
	 */
	export const Unnecessary: 1 = 1;
	/**
	 * Deprecated or obsolete code.
	 *
	 * Clients are allowed to rendered diagnostics with this tag strike through.
	 */
	export const Deprecated: 2 = 2;
}

export type DiagnosticTag = 1 | 2;
/**
 * Represents a related message and source code location for a diagnostic.
 * This should be used to point to code locations that cause or are related to
 * a diagnostics, e.g when duplicating a symbol in a scope.
 */
export interface DiagnosticRelatedInformation {
	/**
	 * The location of this related diagnostic information.
	 */
	location: Location;

	/**
	 * The message of this related diagnostic information.
	 */
	message: string;
}
/**
 * Structure to capture a description for an error code.
 *
 * @since 3.16.0
 */
export interface CodeDescription {
	/**
	 * An URI to open with more information about the diagnostic error.
	 */
	href: URI;
}

Command

表示对命令的引用。提供一个标题,该标题将用于表示 UI 中的命令。命令由字符串标识符标识。如果客户端和服务器提供相应的功能,则建议处理命令的方法是在服务器端执行命令。或者,工具扩展代码可以处理该命令。该协议当前未指定一组已知命令。

interface Command {
	/**
	 * Title of the command, like `save`.
	 */
	title: string;
	/**
	 * The identifier of the actual command handler.
	 */
	command: string;
	/**
	 * Arguments that the command handler should be
	 * invoked with.
	 */
	arguments?: LSPAny[];
}

MarkupContent

MarkupContent 表示一个字符串值,该值的内容可以用不同的格式表示。目前支持 plaintextMarkdown 格式。MarkupContent 通常用于结果文本(如 CompletionItemSignatureInformation)的文档属性。如果格式为 Markdown,则内容应遵循 GitHub Flavored Markdown 规范。

/**
 * Describes the content type that a client supports in various
 * result literals like `Hover`, `ParameterInfo` or `CompletionItem`.
 *
 * Please note that `MarkupKinds` must not start with a `$`. This kinds
 * are reserved for internal usage.
 */
export 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';
/**
 * A `MarkupContent` literal represents a string value which content is
 * interpreted base on its kind flag. Currently the protocol supports
 * `plaintext` and `markdown` as markup kinds.
 *
 * If the kind is `markdown` then the value can contain fenced code blocks like
 * in GitHub issues.
 *
 * Here is an example how such a string can be constructed using
 * JavaScript / TypeScript:
 * ```typescript
 * let markdown: MarkdownContent = {
 * 	kind: MarkupKind.Markdown,
 * 	value: [
 * 		'# Header',
 * 		'Some text',
 * 		'```typescript',
 * 		'someCode();',
 * 		'```'
 * 	].join('\n')
 * };
 * ```
 *
 * *Please Note* that clients might sanitize the return markdown. A client could
 * decide to remove HTML from the markdown to avoid script execution.
 */
export interface MarkupContent {
	/**
	 * The type of the Markup
	 */
	kind: MarkupKind;

	/**
	 * The content itself
	 */
	value: string;
}

此外,客户端应通过版本 3.16.0 中引入的客户端能力 general.markdown 向他们正在使用的 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[];
}

客户端目前使用的已知 Markdown 解析器有:

ParserVersionDocumentation
marked1.1.0Marked Documentation
Python-Markdown3.2.2Python-Markdown Documentation

文件资源更改

3.13 新版功能.从版本 3.16 开始,文件资源更改可以携带额外的属性 changeAnnotation,以更详细地描述实际更改。客户端是否支持 更改注释(ChangeAnnotation) 由客户端功能 workspace.workspaceEdit.changeAnnotationSupport 守护。

文件资源更改允许服务器通过客户端创建、重命名和删除文件和文件夹。请注意,这些名称谈论的是文件,但这些操作应该适用于文件和文件夹。这与语言服务器协议中的其他命名一致(请参阅可以监视文件和文件夹的文件观察程序)。相应的变更类型如下所示:

/**
 * Options to create a file.
 */
export interface CreateFileOptions {
	/**
	 * Overwrite existing file. Overwrite wins over `ignoreIfExists`
	 */
	overwrite?: boolean;

	/**
	 * Ignore if exists.
	 */
	ignoreIfExists?: boolean;
}
/**
 * Create file operation
 */
export interface CreateFile {
	/**
	 * A create
	 */
	kind: 'create';

	/**
	 * The resource to create.
	 */
	uri: DocumentUri;

	/**
	 * Additional options
	 */
	options?: CreateFileOptions;

	/**
	 * An optional annotation identifier describing the operation.
	 *
	 * @since 3.16.0
	 */
	annotationId?: ChangeAnnotationIdentifier;
}
/**
 * Rename file options
 */
export interface RenameFileOptions {
	/**
	 * Overwrite target if existing. Overwrite wins over `ignoreIfExists`
	 */
	overwrite?: boolean;

	/**
	 * Ignores if target exists.
	 */
	ignoreIfExists?: boolean;
}
/**
 * Rename file operation
 */
export interface RenameFile {
	/**
	 * A rename
	 */
	kind: 'rename';

	/**
	 * The old (existing) location.
	 */
	oldUri: DocumentUri;

	/**
	 * The new location.
	 */
	newUri: DocumentUri;

	/**
	 * Rename options.
	 */
	options?: RenameFileOptions;

	/**
	 * An optional annotation identifier describing the operation.
	 *
	 * @since 3.16.0
	 */
	annotationId?: ChangeAnnotationIdentifier;
}
/**
 * Delete file options
 */
export interface DeleteFileOptions {
	/**
	 * Delete the content recursively if a folder is denoted.
	 */
	recursive?: boolean;

	/**
	 * Ignore the operation if the file doesn't exist.
	 */
	ignoreIfNotExists?: boolean;
}
/**
 * Delete file operation
 */
export interface DeleteFile {
	/**
	 * A delete
	 */
	kind: 'delete';

	/**
	 * The file to delete.
	 */
	uri: DocumentUri;

	/**
	 * Delete options.
	 */
	options?: DeleteFileOptions;

	/**
	 * An optional annotation identifier describing the operation.
	 *
	 * @since 3.16.0
	 */
	annotationId?: ChangeAnnotationIdentifier;
}

WorkspaceEdit

工作区编辑表示对工作区中管理的许多资源的更改。编辑应提供 changesdocumentChanges。如果客户端可以处理版本化文档编辑,并且存在 documentChanges,则后者优先于 changes

从版本 3.13.0 开始,工作区编辑也可以包含资源操作(创建、删除或重命名文件和文件夹)。如果存在资源操作,客户端需要按照提供操作的顺序执行操作。例如,工作区编辑可以包含以下两个更改:(1)创建文件a.txt和 (2)文本文档编辑,将文本插入文件a.txt。无效的序列(例如(1)删除文件a.txt和(2)在文件 a.txt 中插入文本)将导致操作失败。客户端能力描述了客户端如何从故障中恢复:workspace.workspaceEdit.failureHandling

export interface WorkspaceEdit {
	/**
	 * Holds changes to existing resources.
	 */
	changes?: { [uri: DocumentUri]: TextEdit[]; };

	/**
	 * Depending on the client capability
	 * `workspace.workspaceEdit.resourceOperations` document changes are either
	 * an array of `TextDocumentEdit`s to express changes to n different text
	 * documents where each text document edit addresses a specific version of
	 * a text document. Or it can contain above `TextDocumentEdit`s mixed with
	 * create, rename and delete file / folder operations.
	 *
	 * Whether a client supports versioned document edits is expressed via
	 * `workspace.workspaceEdit.documentChanges` client capability.
	 *
	 * If a client neither supports `documentChanges` nor
	 * `workspace.workspaceEdit.resourceOperations` then only plain `TextEdit`s
	 * using the `changes` property are supported.
	 */
	documentChanges?: (
		TextDocumentEdit[] |
		(TextDocumentEdit | CreateFile | RenameFile | DeleteFile)[]
	);

	/**
	 * A map of change annotations that can be referenced in
	 * `AnnotatedTextEdit`s or create, rename and delete file / folder
	 * operations.
	 *
	 * Whether clients honor this property depends on the client capability
	 * `workspace.changeAnnotationSupport`.
	 *
	 * @since 3.16.0
	 */
	changeAnnotations?: {
		[id: string /* ChangeAnnotationIdentifier */]: ChangeAnnotation;
	};
}

WorkspaceEditClientCapabilities

3.13 新版功能:ResourceOperationKindFailureHandlingKind 以及客户端功能 workspace.workspaceEdit.resourceOperations 以及 workspace.workspaceEdit.failureHandling

工作区编辑的能力随着时间的推移而发展。客户端可以使用以下客户端能力描述其支持:

Client Capability:

  • 属性路径: workspace.workspaceEdit

  • 属性类型: WorkspaceEditClientCapabilities 定义如下:

export interface WorkspaceEditClientCapabilities {
	/**
	 * The client supports versioned document changes in `WorkspaceEdit`s
	 */
	documentChanges?: boolean;

	/**
	 * The resource operations the client supports. Clients should at least
	 * support 'create', 'rename' and 'delete' files and folders.
	 *
	 * @since 3.13.0
	 */
	resourceOperations?: ResourceOperationKind[];

	/**
	 * The failure handling strategy of a client if applying the workspace edit
	 * fails.
	 *
	 * @since 3.13.0
	 */
	failureHandling?: FailureHandlingKind;

	/**
	 * Whether the client normalizes line endings to the client specific
	 * setting.
	 * If set to `true` the client will normalize line ending characters
	 * in a workspace edit to the client specific new line character(s).
	 *
	 * @since 3.16.0
	 */
	normalizesLineEndings?: boolean;

	/**
	 * Whether the client in general supports change annotations on text edits,
	 * create file, rename file and delete file changes.
	 *
	 * @since 3.16.0
	 */
	changeAnnotationSupport?: {
		/**
		 * Whether the client groups edits with equal labels into tree nodes,
		 * for instance all edits labelled with "Changes in Strings" would
		 * be a tree node.
		 */
		groupsOnLabel?: boolean;
	};
}
/**
 * The kind of resource operations supported by the client.
 */
export type ResourceOperationKind = 'create' | 'rename' | 'delete';

export namespace ResourceOperationKind {

	/**
	 * Supports creating new files and folders.
	 */
	export const Create: ResourceOperationKind = 'create';

	/**
	 * Supports renaming existing files and folders.
	 */
	export const Rename: ResourceOperationKind = 'rename';

	/**
	 * Supports deleting existing files and folders.
	 */
	export const Delete: ResourceOperationKind = 'delete';
}
export type FailureHandlingKind = 'abort' | 'transactional' | 'undo'
	| 'textOnlyTransactional';

export namespace FailureHandlingKind {

	/**
	 * Applying the workspace change is simply aborted if one of the changes
	 * provided fails. All operations executed before the failing operation
	 * stay executed.
	 */
	export const Abort: FailureHandlingKind = 'abort';

	/**
	 * All operations are executed transactional. That means they either all
	 * succeed or no changes at all are applied to the workspace.
	 */
	export const Transactional: FailureHandlingKind = 'transactional';


	/**
	 * If the workspace edit contains only textual file changes they are
	 * executed transactional. If resource changes (create, rename or delete
	 * file) are part of the change the failure handling strategy is abort.
	 */
	export const TextOnlyTransactional: FailureHandlingKind
		= 'textOnlyTransactional';

	/**
	 * The client tries to undo the operations already executed. But there is no
	 * guarantee that this is succeeding.
	 */
	export const Undo: FailureHandlingKind = 'undo';
}

Work Done Progress(工作完成进度)

从版本 3.15.0 开始

使用通用的 $/progress 通知报告已完成的工作进度。已完成工作进度通知的值有效负载可以有三种不同的形式。

Work Done Progress Begin

若要开始进度报告,必须发送包含以下有效负载的 $/progress 通知:

export interface WorkDoneProgressBegin {

	kind: 'begin';

	/**
	 * Mandatory title of the progress operation. Used to briefly inform about
	 * the kind of operation being performed.
	 *
	 * Examples: "Indexing" or "Linking dependencies".
	 */
	title: string;

	/**
	 * Controls if a cancel button should show to allow the user to cancel the
	 * long running operation. Clients that don't support cancellation are
	 * allowed to ignore the setting.
	 */
	cancellable?: boolean;

	/**
	 * Optional, more detailed associated progress message. Contains
	 * complementary information to the `title`.
	 *
	 * Examples: "3/25 files", "project/src/module2", "node_modules/some_dep".
	 * If unset, the previous progress message (if any) is still valid.
	 */
	message?: string;

	/**
	 * Optional progress percentage to display (value 100 is considered 100%).
	 * If not provided infinite progress is assumed and clients are allowed
	 * to ignore the `percentage` value in subsequent in report notifications.
	 *
	 * The value should be steadily rising. Clients are free to ignore values
	 * that are not following this rule. The value range is [0, 100]
	 */
	percentage?: uinteger;
}

Work Done Progress Report

使用以下有效负载报告进度:

export interface WorkDoneProgressReport {

	kind: 'report';

	/**
	 * Controls enablement state of a cancel button. This property is only valid
	 * if a cancel button got requested in the `WorkDoneProgressBegin` payload.
	 *
	 * Clients that don't support cancellation or don't support control the
	 * button's enablement state are allowed to ignore the setting.
	 */
	cancellable?: boolean;

	/**
	 * Optional, more detailed associated progress message. Contains
	 * complementary information to the `title`.
	 *
	 * Examples: "3/25 files", "project/src/module2", "node_modules/some_dep".
	 * If unset, the previous progress message (if any) is still valid.
	 */
	message?: string;

	/**
	 * Optional progress percentage to display (value 100 is considered 100%).
	 * If not provided infinite progress is assumed and clients are allowed
	 * to ignore the `percentage` value in subsequent in report notifications.
	 *
	 * The value should be steadily rising. Clients are free to ignore values
	 * that are not following this rule. The value range is [0, 100]
	 */
	percentage?: uinteger;
}

Work Done Progress End

使用以下有效负载发出进度报告结束的信号:

export interface WorkDoneProgressEnd {

	kind: 'end';

	/**
	 * Optional, a final message indicating to for example indicate the outcome
	 * of the operation.
	 */
	message?: string;
}

Initiating Work Done Progress

可以通过两种不同的方式启动 Work Done progress 进度:

  • 由请求的发送方(主要是客户端)在请求参数中预定义 workDoneToken 属性。本文将这种进度称为 客户端启动的进度

  • 由服务器使用请求 window/workDoneProgress/create。本文将这种进度称为服务器启动的进度

客户端启动的进度

假设客户端向服务器发送 textDocument/reference 请求,并且客户端接受有关该请求的 Work Done Progress

为了向服务器发出此信号,客户端为请求参数添加 workDoneToken 属性。像这样:

{
	"textDocument": {
		"uri": "file:///folder/file.ts"
	},
	"position": {
		"line": 9,
		"character": 5
	},
	"context": {
		"includeDeclaration": true
	},
	// The token used to report work done progress.
	"workDoneToken": "1d546990-40a3-4b77-b134-46622995f6ae"
}

参数属性的相应类型定义如下所示:

export interface WorkDoneProgressParams {
	/**
	 * An optional token that a server can use to report work done progress.
	 */
	workDoneToken?: ProgressToken;
}

服务器使用 workDoneToken 报告特定 textDocument/reference 的进度。对于上述请求,$/progress 通知参数如下所示:

{
	"token": "1d546990-40a3-4b77-b134-46622995f6ae",
	"value": {
		"kind": "begin",
		"title": "Finding references for A#foo",
		"cancellable": false,
		"message": "Processing file X.ts",
		"percentage": 0
	}
}

仅当请求未发送响应时,请求的参数中的 workDoneToken 属性接收的 token 才有效。取消 Work Done Progress 只需取消相应的请求即可。

没有特定的客户端功能来指示客户端是否将每个请求发送进度令牌。这样做的原因是,这在许多客户端中不是静态的,甚至可能对于同一请求类型的每个请求实例而更改。因此,该功能在每个请求实例上都通过存在 workDoneToken 属性发出信号。

为了避免客户端在发送请求之前设置进度监视器用户界面,但服务器实际上并未报告任何进度,服务器需要在相应的 服务器能力(Server Capability) 中发出支持常规 Work Done Progress 的信号。对于上述查找引用示例,服务器将通过在服务器功能中设置 referencesProvider 属性来发出此类支持的信号,如下所示:

{
	"referencesProvider": {
		"workDoneProgress": true
	}
}

服务器能力(Server Capability) 的相应类型定义如下所示:

export interface WorkDoneProgressOptions {
	workDoneProgress?: boolean;
}

服务器启动的进度

服务器还可以使用 window/workDoneProgress/create 请求启动进度报告。如果服务器需要报告请求之外的进度(例如,服务器需要重新索引数据库),这将非常有用。然后,可以使用 workDoneToken 与客户端启动的进度相同的通知来报告进度。创建请求中提供的令牌只能使用一次(例如,只应向其发送一个开始、多个报告和一个结束通知)。

为了保持协议向后兼容,允许服务器使用 window/workDoneProgress/create 请求的前提是客户端使用 客户端能力(Client capability) window.workDoneProgress 发出相应的支持信号,其定义如下:

    /**
     * Window specific client capabilities.
     */
    window?: {
        /**
         * Whether client supports server initiated progress using the
         * `window/workDoneProgress/create` request.
         */
        workDoneProgress?: boolean;
    };

Partial Result Progress(部分结果进度)

从版本 3.15.0 开始

部分结果也使用通用的 $/progress 通知进行报告。在大多数情况下,部分结果进度通知的有效负载值与最终结果相同。例如,workspace/symbol 请求,有 SymbolInformation[] | WorkspaceSymbol[] 作为结果类型。因此,部分结果的类型也是 SymbolInformation[] | WorkspaceSymbol[]。客户端是否接受请求的部分结果通知是通过向请求参数添加 partialResultToken 来指示的。例如,同时支持已完成工作和部分结果进度的 textDocument/reference 请求可能如下所示:

{
	"textDocument": {
		"uri": "file:///folder/file.ts"
	},
	"position": {
		"line": 9,
		"character": 5
	},
	"context": {
		"includeDeclaration": true
	},
	// The token used to report work done progress.
	"workDoneToken": "1d546990-40a3-4b77-b134-46622995f6ae",
	// The token used to report partial result progress.
	"partialResultToken": "5f6f349e-4f81-4a3b-afff-ee04bff96804"
}

然后,使用 partialResultToken 报告查找引用请求的部分结果。

如果服务器通过相应的 $/progress 报告部分结果,则必须使用 n 个 $/progress 通知报告整个结果。每个 $/progress 通知都会将项目附加到结果中。就结果值而言,最终响应必须为空。这避免了对最终结果应如何解释的混淆,例如,作为另一个部分结果或作为替换结果。

如果响应错误,则应按如下方式处理提供的部分结果:

  • code 等于 RequestCancelled: 客户端可以自由使用提供的结果,但应明确请求已取消并且可能不完整。

  • 在所有其他情况下,不应使用提供的部分结果。

PartialResultParams

用于传递部分结果标记的参数:

export interface PartialResultParams {
	/**
	 * An optional token that a server can use to report partial results (e.g.
	 * streaming) to the client.
	 */
	partialResultToken?: ProgressToken;
}

TraceValue

TraceValue 表示服务器使用 $/logTrace 通知系统地报告其执行跟踪的详细程度。初始跟踪值由客户端在初始化时设置,以后可以使用 $/setTrace 通知进行修改。

export type TraceValue = 'off' | 'messages' | 'verbose';

生命周期消息

当前的协议规范定义了服务器的生命周期由客户端(例如VS Code或Emacs等工具)管理。由客户端决定何时启动(进程方面)以及何时关闭服务器。

initialize 请求

初始化请求作为从客户端发送到服务器的第一个请求。如果服务器在初始化请求之前收到请求或通知,则应按如下方式操作:

  • 对于请求,响应应为 code: -32002 的错误。服务器可以选择处理其中的消息。

  • 除退出通知外,应丢弃通知。这将允许在没有初始化请求的情况下退出服务器。

在服务器使用 InitializeResult 响应初始化请求之前,客户端不得向服务器发送任何其他请求或通知。此外,在使用 InitializeResult 做出响应之前,不允许服务器向客户端发送任何请求或通知,但在初始化请求期间,允许服务器向客户端发送通知 window/showMessagewindow/logMessagetelemetry/event 以及 window/showMessageRequest 请求。如果客户端在初始化参数中设置了进度令牌(例如属性 workDoneToken),则还允许服务器使用从服务器发送到客户端的 $/progress 通知来使用该令牌(并且仅使用该令牌)。

初始化请求只能发送一次。

请求(Request):

  • method: "initialize"
  • params: InitializeParams 定义如下:
interface InitializeParams extends WorkDoneProgressParams {
	/**
	 * The process Id of the parent process that started the server. Is null if
	 * the process has not been started by another process. If the parent
	 * process is not alive then the server should exit (see exit notification)
	 * its process.
	 */
	processId: integer | null;

	/**
	 * Information about the client
	 *
	 * @since 3.15.0
	 */
	clientInfo?: {
		/**
		 * The name of the client as defined by the client.
		 */
		name: string;

		/**
		 * The client's version as defined by the client.
		 */
		version?: string;
	};

	/**
	 * The locale the client is currently showing the user interface
	 * in. This must not necessarily be the locale of the operating
	 * system.
	 *
	 * Uses IETF language tags as the value's syntax
	 * (See https://en.wikipedia.org/wiki/IETF_language_tag)
	 *
	 * @since 3.16.0
	 */
	locale?: string;

	/**
	 * The rootPath of the workspace. Is null
	 * if no folder is open.
	 *
	 * @deprecated in favour of `rootUri`.
	 */
	rootPath?: string | null;

	/**
	 * The rootUri of the workspace. Is null if no
	 * folder is open. If both `rootPath` and `rootUri` are set
	 * `rootUri` wins.
	 *
	 * @deprecated in favour of `workspaceFolders`
	 */
	rootUri: DocumentUri | null;

	/**
	 * User provided initialization options.
	 */
	initializationOptions?: LSPAny;

	/**
	 * The capabilities provided by the client (editor or tool)
	 */
	capabilities: ClientCapabilities;

	/**
	 * The initial trace setting. If omitted trace is disabled ('off').
	 */
	trace?: TraceValue;

	/**
	 * The workspace folders configured in the client when the server starts.
	 * This property is only available if the client supports workspace folders.
	 * It can be `null` if the client supports workspace folders but none are
	 * configured.
	 *
	 * @since 3.6.0
	 */
	workspaceFolders?: WorkspaceFolder[] | null;
}

TextDocumentClientCapabilities

TextDocumentClientCapabilities 定义编辑器/工具在文本文档上提供的功能。

/**
 * Text document specific client capabilities.
 */
export interface TextDocumentClientCapabilities {

	synchronization?: TextDocumentSyncClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/completion` request.
	 */
	completion?: CompletionClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/hover` request.
	 */
	hover?: HoverClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/signatureHelp` request.
	 */
	signatureHelp?: SignatureHelpClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/declaration` request.
	 *
	 * @since 3.14.0
	 */
	declaration?: DeclarationClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/definition` request.
	 */
	definition?: DefinitionClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/typeDefinition` request.
	 *
	 * @since 3.6.0
	 */
	typeDefinition?: TypeDefinitionClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/implementation` request.
	 *
	 * @since 3.6.0
	 */
	implementation?: ImplementationClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/references` request.
	 */
	references?: ReferenceClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/documentHighlight` request.
	 */
	documentHighlight?: DocumentHighlightClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/documentSymbol` request.
	 */
	documentSymbol?: DocumentSymbolClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/codeAction` request.
	 */
	codeAction?: CodeActionClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/codeLens` request.
	 */
	codeLens?: CodeLensClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/documentLink` request.
	 */
	documentLink?: DocumentLinkClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/documentColor` and the
	 * `textDocument/colorPresentation` request.
	 *
	 * @since 3.6.0
	 */
	colorProvider?: DocumentColorClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/formatting` request.
	 */
	formatting?: DocumentFormattingClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/rangeFormatting` request.
	 */
	rangeFormatting?: DocumentRangeFormattingClientCapabilities;

	/** request.
	 * Capabilities specific to the `textDocument/onTypeFormatting` request.
	 */
	onTypeFormatting?: DocumentOnTypeFormattingClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/rename` request.
	 */
	rename?: RenameClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/publishDiagnostics`
	 * notification.
	 */
	publishDiagnostics?: PublishDiagnosticsClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/foldingRange` request.
	 *
	 * @since 3.10.0
	 */
	foldingRange?: FoldingRangeClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/selectionRange` request.
	 *
	 * @since 3.15.0
	 */
	selectionRange?: SelectionRangeClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/linkedEditingRange` request.
	 *
	 * @since 3.16.0
	 */
	linkedEditingRange?: LinkedEditingRangeClientCapabilities;

	/**
	 * Capabilities specific to the various call hierarchy requests.
	 *
	 * @since 3.16.0
	 */
	callHierarchy?: CallHierarchyClientCapabilities;

	/**
	 * Capabilities specific to the various semantic token requests.
	 *
	 * @since 3.16.0
	 */
	semanticTokens?: SemanticTokensClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/moniker` request.
	 *
	 * @since 3.16.0
	 */
	moniker?: MonikerClientCapabilities;

	/**
	 * Capabilities specific to the various type hierarchy requests.
	 *
	 * @since 3.17.0
	 */
	typeHierarchy?: TypeHierarchyClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/inlineValue` request.
	 *
	 * @since 3.17.0
	 */
	inlineValue?: InlineValueClientCapabilities;

	/**
	 * Capabilities specific to the `textDocument/inlayHint` request.
	 *
	 * @since 3.17.0
	 */
	inlayHint?: InlayHintClientCapabilities;

	/**
	 * Capabilities specific to the diagnostic pull model.
	 *
	 * @since 3.17.0
	 */
	diagnostic?: DiagnosticClientCapabilities;
}

NotebookDocumentClientCapabilities

NotebookDocumentClientCapabilities 定义编辑器/工具在 notebook 文档上提供的功能。

/**
 * Capabilities specific to the notebook document support.
 *
 * @since 3.17.0
 */
export interface NotebookDocumentClientCapabilities {
	/**
	 * Capabilities specific to notebook document synchronization
	 *
	 * @since 3.17.0
	 */
	synchronization: NotebookDocumentSyncClientCapabilities;
}

ClientCapabilities

ClientCapabilities 定义客户端支持的动态注册、工作区和文本文档功能的功能。ClientCapabilities.experimental 选项可用于正在开发的实验能力。为了实现将来的兼容性,ClientCapabilities 对象文本可以设置比当前定义的属性更多的属性。接收具有未知属性的 ClientCapabilities 对象的服务器应忽略这些属性。缺少的属性应解释为缺少该功能。如果缺少的属性是子属性,则所有缺少的子属性都应解释为缺少相应的功能。

客户端功能随协议 3.0 版一起引入。因此,它们仅描述在 3.x 或更高版本中引入的功能。协议 2.x 版本中存在的功能对于客户端仍然是必需的。客户不能选择不提供它们。因此,即使客户端省略了 ClientCapabilities.textDocument.synchronization,客户端仍然需要提供文本文档同步(例如打开、更改和关闭的通知)。

interface ClientCapabilities {
	/**
	 * Workspace specific client capabilities.
	 */
	workspace?: {
		/**
		 * The client supports applying batch edits
		 * to the workspace by supporting the request
		 * 'workspace/applyEdit'
		 */
		applyEdit?: boolean;

		/**
		 * Capabilities specific to `WorkspaceEdit`s
		 */
		workspaceEdit?: WorkspaceEditClientCapabilities;

		/**
		 * Capabilities specific to the `workspace/didChangeConfiguration`
		 * notification.
		 */
		didChangeConfiguration?: DidChangeConfigurationClientCapabilities;

		/**
		 * Capabilities specific to the `workspace/didChangeWatchedFiles`
		 * notification.
		 */
		didChangeWatchedFiles?: DidChangeWatchedFilesClientCapabilities;

		/**
		 * Capabilities specific to the `workspace/symbol` request.
		 */
		symbol?: WorkspaceSymbolClientCapabilities;

		/**
		 * Capabilities specific to the `workspace/executeCommand` request.
		 */
		executeCommand?: ExecuteCommandClientCapabilities;

		/**
		 * The client has support for workspace folders.
		 *
		 * @since 3.6.0
		 */
		workspaceFolders?: boolean;

		/**
		 * The client supports `workspace/configuration` requests.
		 *
		 * @since 3.6.0
		 */
		configuration?: boolean;

		/**
		 * Capabilities specific to the semantic token requests scoped to the
		 * workspace.
		 *
		 * @since 3.16.0
		 */
		 semanticTokens?: SemanticTokensWorkspaceClientCapabilities;

		/**
		 * Capabilities specific to the code lens requests scoped to the
		 * workspace.
		 *
		 * @since 3.16.0
		 */
		codeLens?: CodeLensWorkspaceClientCapabilities;

		/**
		 * The client has support for file requests/notifications.
		 *
		 * @since 3.16.0
		 */
		fileOperations?: {
			/**
			 * Whether the client supports dynamic registration for file
			 * requests/notifications.
			 */
			dynamicRegistration?: boolean;

			/**
			 * The client has support for sending didCreateFiles notifications.
			 */
			didCreate?: boolean;

			/**
			 * The client has support for sending willCreateFiles requests.
			 */
			willCreate?: boolean;

			/**
			 * The client has support for sending didRenameFiles notifications.
			 */
			didRename?: boolean;

			/**
			 * The client has support for sending willRenameFiles requests.
			 */
			willRename?: boolean;

			/**
			 * The client has support for sending didDeleteFiles notifications.
			 */
			didDelete?: boolean;

			/**
			 * The client has support for sending willDeleteFiles requests.
			 */
			willDelete?: boolean;
		};

		/**
		 * Client workspace capabilities specific to inline values.
		 *
		 * @since 3.17.0
		 */
		inlineValue?: InlineValueWorkspaceClientCapabilities;

		/**
		 * Client workspace capabilities specific to inlay hints.
		 *
		 * @since 3.17.0
		 */
		inlayHint?: InlayHintWorkspaceClientCapabilities;

		/**
		 * Client workspace capabilities specific to diagnostics.
		 *
		 * @since 3.17.0.
		 */
		diagnostics?: DiagnosticWorkspaceClientCapabilities;
	};

	/**
	 * Text document specific client capabilities.
	 */
	textDocument?: TextDocumentClientCapabilities;

	/**
	 * Capabilities specific to the notebook document support.
	 *
	 * @since 3.17.0
	 */
	notebookDocument?: NotebookDocumentClientCapabilities;

	/**
	 * Window specific client capabilities.
	 */
	window?: {
		/**
		 * It indicates whether the client supports server initiated
		 * progress using the `window/workDoneProgress/create` request.
		 *
		 * The capability also controls Whether client supports handling
		 * of progress notifications. If set servers are allowed to report a
		 * `workDoneProgress` property in the request specific server
		 * capabilities.
		 *
		 * @since 3.15.0
		 */
		workDoneProgress?: boolean;

		/**
		 * Capabilities specific to the showMessage request
		 *
		 * @since 3.16.0
		 */
		showMessage?: ShowMessageRequestClientCapabilities;

		/**
		 * Client capabilities for the show document request.
		 *
		 * @since 3.16.0
		 */
		showDocument?: ShowDocumentClientCapabilities;
	};

	/**
	 * General client capabilities.
	 *
	 * @since 3.16.0
	 */
	general?: {
		/**
		 * Client capability that signals how the client
		 * handles stale requests (e.g. a request
		 * for which the client will not process the response
		 * anymore since the information is outdated).
		 *
		 * @since 3.17.0
		 */
		staleRequestSupport?: {
			/**
			 * The client will actively cancel the request.
			 */
			cancel: boolean;

			/**
			 * The list of requests for which the client
			 * will retry the request if it receives a
			 * response with error code `ContentModified``
			 */
			 retryOnContentModified: string[];
		}

		/**
		 * Client capabilities specific to regular expressions.
		 *
		 * @since 3.16.0
		 */
		regularExpressions?: RegularExpressionsClientCapabilities;

		/**
		 * Client capabilities specific to the client's markdown parser.
		 *
		 * @since 3.16.0
		 */
		markdown?: MarkdownClientCapabilities;

		/**
		 * The position encodings supported by the client. Client and server
		 * have to agree on the same position encoding to ensure that offsets
		 * (e.g. character position in a line) are interpreted the same on both
		 * side.
		 *
		 * To keep the protocol backwards compatible the following applies: if
		 * the value 'utf-16' is missing from the array of position encodings
		 * servers can assume that the client supports UTF-16. UTF-16 is
		 * therefore a mandatory encoding.
		 *
		 * If omitted it defaults to ['utf-16'].
		 *
		 * Implementation considerations: since the conversion from one encoding
		 * into another requires the content of the file / line the conversion
		 * is best done where the file is read which is usually on the server
		 * side.
		 *
		 * @since 3.17.0
		 */
		positionEncodings?: PositionEncodingKind[];
	};

	/**
	 * Experimental client capabilities.
	 */
	experimental?: LSPAny;
}

InitializeResult

响应(Response):

  • result: InitializeResult 定义如下:
interface InitializeResult {
	/**
	 * The capabilities the language server provides.
	 */
	capabilities: ServerCapabilities;

	/**
	 * Information about the server.
	 *
	 * @since 3.15.0
	 */
	serverInfo?: {
		/**
		 * The name of the server as defined by the server.
		 */
		name: string;

		/**
		 * The server's version as defined by the server.
		 */
		version?: string;
	};
}
  • error.code:
/**
 * Known error codes for an `InitializeErrorCodes`;
 */
export namespace InitializeErrorCodes {

	/**
	 * If the protocol version provided by the client can't be handled by
	 * the server.
	 *
	 * @deprecated This initialize error got replaced by client capabilities.
	 * There is no version handshake in version 3.0x
	 */
	export const unknownProtocolVersion: 1 = 1;
}

export type InitializeErrorCodes = 1;
  • error.data:
interface InitializeError {
	/**
	 * Indicates whether the client execute the following retry logic:
	 * (1) show the message provided by the ResponseError to the user
	 * (2) user selects retry or cancel
	 * (3) if user selected retry the initialize method is sent again.
	 */
	retry: boolean;
}

ServerCapabilities

服务器可以发出以下能力信号:

interface ServerCapabilities {

	/**
	 * The position encoding the server picked from the encodings offered
	 * by the client via the client capability `general.positionEncodings`.
	 *
	 * If the client didn't provide any position encodings the only valid
	 * value that a server can return is 'utf-16'.
	 *
	 * If omitted it defaults to 'utf-16'.
	 *
	 * @since 3.17.0
	 */
	positionEncoding?: PositionEncodingKind;

	/**
	 * Defines how text documents are synced. Is either a detailed structure
	 * defining each notification or for backwards compatibility the
	 * TextDocumentSyncKind number. If omitted it defaults to
	 * `TextDocumentSyncKind.None`.
	 */
	textDocumentSync?: TextDocumentSyncOptions | TextDocumentSyncKind;

	/**
	 * Defines how notebook documents are synced.
	 *
	 * @since 3.17.0
	 */
	notebookDocumentSync?: NotebookDocumentSyncOptions
		| NotebookDocumentSyncRegistrationOptions;

	/**
	 * The server provides completion support.
	 */
	completionProvider?: CompletionOptions;

	/**
	 * The server provides hover support.
	 */
	hoverProvider?: boolean | HoverOptions;

	/**
	 * The server provides signature help support.
	 */
	signatureHelpProvider?: SignatureHelpOptions;

	/**
	 * The server provides go to declaration support.
	 *
	 * @since 3.14.0
	 */
	declarationProvider?: boolean | DeclarationOptions
		| DeclarationRegistrationOptions;

	/**
	 * The server provides goto definition support.
	 */
	definitionProvider?: boolean | DefinitionOptions;

	/**
	 * The server provides goto type definition support.
	 *
	 * @since 3.6.0
	 */
	typeDefinitionProvider?: boolean | TypeDefinitionOptions
		| TypeDefinitionRegistrationOptions;

	/**
	 * The server provides goto implementation support.
	 *
	 * @since 3.6.0
	 */
	implementationProvider?: boolean | ImplementationOptions
		| ImplementationRegistrationOptions;

	/**
	 * The server provides find references support.
	 */
	referencesProvider?: boolean | ReferenceOptions;

	/**
	 * The server provides document highlight support.
	 */
	documentHighlightProvider?: boolean | DocumentHighlightOptions;

	/**
	 * The server provides document symbol support.
	 */
	documentSymbolProvider?: boolean | DocumentSymbolOptions;

	/**
	 * The server provides code actions. The `CodeActionOptions` return type is
	 * only valid if the client signals code action literal support via the
	 * property `textDocument.codeAction.codeActionLiteralSupport`.
	 */
	codeActionProvider?: boolean | CodeActionOptions;

	/**
	 * The server provides code lens.
	 */
	codeLensProvider?: CodeLensOptions;

	/**
	 * The server provides document link support.
	 */
	documentLinkProvider?: DocumentLinkOptions;

	/**
	 * The server provides color provider support.
	 *
	 * @since 3.6.0
	 */
	colorProvider?: boolean | DocumentColorOptions
		| DocumentColorRegistrationOptions;

	/**
	 * The server provides document formatting.
	 */
	documentFormattingProvider?: boolean | DocumentFormattingOptions;

	/**
	 * The server provides document range formatting.
	 */
	documentRangeFormattingProvider?: boolean | DocumentRangeFormattingOptions;

	/**
	 * The server provides document formatting on typing.
	 */
	documentOnTypeFormattingProvider?: DocumentOnTypeFormattingOptions;

	/**
	 * The server provides rename support. RenameOptions may only be
	 * specified if the client states that it supports
	 * `prepareSupport` in its initial `initialize` request.
	 */
	renameProvider?: boolean | RenameOptions;

	/**
	 * The server provides folding provider support.
	 *
	 * @since 3.10.0
	 */
	foldingRangeProvider?: boolean | FoldingRangeOptions
		| FoldingRangeRegistrationOptions;

	/**
	 * The server provides execute command support.
	 */
	executeCommandProvider?: ExecuteCommandOptions;

	/**
	 * The server provides selection range support.
	 *
	 * @since 3.15.0
	 */
	selectionRangeProvider?: boolean | SelectionRangeOptions
		| SelectionRangeRegistrationOptions;

	/**
	 * The server provides linked editing range support.
	 *
	 * @since 3.16.0
	 */
	linkedEditingRangeProvider?: boolean | LinkedEditingRangeOptions
		| LinkedEditingRangeRegistrationOptions;

	/**
	 * The server provides call hierarchy support.
	 *
	 * @since 3.16.0
	 */
	callHierarchyProvider?: boolean | CallHierarchyOptions
		| CallHierarchyRegistrationOptions;

	/**
	 * The server provides semantic tokens support.
	 *
	 * @since 3.16.0
	 */
	semanticTokensProvider?: SemanticTokensOptions
		| SemanticTokensRegistrationOptions;

	/**
	 * Whether server provides moniker support.
	 *
	 * @since 3.16.0
	 */
	monikerProvider?: boolean | MonikerOptions | MonikerRegistrationOptions;

	/**
	 * The server provides type hierarchy support.
	 *
	 * @since 3.17.0
	 */
	typeHierarchyProvider?: boolean | TypeHierarchyOptions
		 | TypeHierarchyRegistrationOptions;

	/**
	 * The server provides inline values.
	 *
	 * @since 3.17.0
	 */
	inlineValueProvider?: boolean | InlineValueOptions
		 | InlineValueRegistrationOptions;

	/**
	 * The server provides inlay hints.
	 *
	 * @since 3.17.0
	 */
	inlayHintProvider?: boolean | InlayHintOptions
		 | InlayHintRegistrationOptions;

	/**
	 * The server has support for pull model diagnostics.
	 *
	 * @since 3.17.0
	 */
	diagnosticProvider?: DiagnosticOptions | DiagnosticRegistrationOptions;

	/**
	 * The server provides workspace symbol support.
	 */
	workspaceSymbolProvider?: boolean | WorkspaceSymbolOptions;

	/**
	 * Workspace specific server capabilities
	 */
	workspace?: {
		/**
		 * The server supports workspace folder.
		 *
		 * @since 3.6.0
		 */
		workspaceFolders?: WorkspaceFoldersServerCapabilities;

		/**
		 * The server is interested in file notifications/requests.
		 *
		 * @since 3.16.0
		 */
		fileOperations?: {
			/**
			 * The server is interested in receiving didCreateFiles
			 * notifications.
			 */
			didCreate?: FileOperationRegistrationOptions;

			/**
			 * The server is interested in receiving willCreateFiles requests.
			 */
			willCreate?: FileOperationRegistrationOptions;

			/**
			 * The server is interested in receiving didRenameFiles
			 * notifications.
			 */
			didRename?: FileOperationRegistrationOptions;

			/**
			 * The server is interested in receiving willRenameFiles requests.
			 */
			willRename?: FileOperationRegistrationOptions;

			/**
			 * The server is interested in receiving didDeleteFiles file
			 * notifications.
			 */
			didDelete?: FileOperationRegistrationOptions;

			/**
			 * The server is interested in receiving willDeleteFiles file
			 * requests.
			 */
			willDelete?: FileOperationRegistrationOptions;
		};
	};

	/**
	 * Experimental server capabilities.
	 */
	experimental?: LSPAny;
}

Initialized 通知

在客户端收到 initialize 请求的结果之后,但在客户端向服务器发送任何其他请求或通知之前,Initialized 通知将从客户端发送到服务器。例如,服务器可以使用 initialized 通知来动态注册功能。初始化的通知只能发送一次。

通知(Notification):

  • method: "initialized"
  • params: InitializedParams 定义如下:
interface InitializedParams {
}

注册能力

client/registerCapability 请求从服务器发送到客户端,以在客户端注册新功能。并非所有客户端都需要支持动态功能注册。客户端通过特定客户端功能的 dynamicRegistration 属性选择加入。客户端甚至可以为功能 A 提供动态注册,但不能为功能 B 提供动态注册(请参阅 TextDocumentClientCapabilities 作为示例)。

服务器不得动态注册与初始化结果中静态注册相同的功能,也不得为同一文档选择器动态注册相同的功能。如果服务器想要同时支持静态和动态注册,则需要在初始化请求中检查客户端功能,并且仅在客户端不支持该功能的动态注册时才静态注册该功能。

请求(Request):

  • method: "client/registerCapability"
  • params: RegistrationParams 定义如下:
export interface RegistrationParams {
	registrations: Registration[];
}
/**
 * General parameters to register for a capability.
 */
export interface Registration {
	/**
	 * The id used to register the request. The id can be used to deregister
	 * the request again.
	 */
	id: string;

	/**
	 * The method / capability to register for.
	 */
	method: string;

	/**
	 * Options necessary for the registration.
	 */
	registerOptions?: LSPAny;
}

由于大多数注册选项都需要指定文档选择器,因此可以使用基础 interface。请参阅 TextDocumentRegistrationOptions

用于在客户端动态注册 textDocument/willSaveWaitUntil 功能的示例 JSON-RPC 消息如下所示(仅显示详细信息):

{
	"method": "client/registerCapability",
	"params": {
		"registrations": [
			{
				"id": "79eee87c-c409-4664-8102-e03263673f6f",
				"method": "textDocument/willSaveWaitUntil",
				"registerOptions": {
					"documentSelector": [
						{ "language": "javascript" }
					]
				}
			}
		]
	}
}

此消息从服务器发送到客户端,在客户端成功执行请求后,进一步的 textDocument/willSaveWaitUntil 请求将从客户端发送到服务器。

响应(Response):

  • result: void.
  • error: codemessage,以防在请求期间发生异常。

StaticRegistrationOptions 可用于使用给定的服务器控件 ID 在初始化结果中注册功能,以便以后能够取消注册该功能。

/**
 * Static registration options to be returned in the initialize request.
 */
export interface StaticRegistrationOptions {
	/**
	 * The id used to register the request. The id can be used to deregister
	 * the request again. See also Registration#id.
	 */
	id?: string;
}

TextDocumentRegistrationOptions 可用于动态注册一组文本文档的请求。

/**
 * General text document registration options.
 */
export interface TextDocumentRegistrationOptions {
	/**
	 * A document selector to identify the scope of the registration. If set to
	 * null the document selector provided on the client side will be used.
	 */
	documentSelector: DocumentSelector | null;
}

取消注册能力

client/unregisterCapability 请求从服务器发送到客户端,以注销以前注册的能力。

请求(Request):

  • method: "client/unregisterCapability"
  • params: UnregistrationParams 定义如下:
export interface UnregistrationParams {
	// This should correctly be named `unregistrations`. However changing this
	// is a breaking change and needs to wait until we deliver a 4.x version
	// of the specification.
	unregisterations: Unregistration[];
}
/**
 * General parameters to unregister a capability.
 */
export interface Unregistration {
	/**
	 * The id used to unregister the request or notification. Usually an id
	 * provided during the register request.
	 */
	id: string;

	/**
	 * The method / capability to unregister for.
	 */
	method: string;
}

用于注销上述已注册的 textDocument/willSaveWaitUntil 能力的示例 JSON-RPC 消息如下所示:

{
	"method": "client/unregisterCapability",
	"params": {
		"unregisterations": [
			{
				"id": "79eee87c-c409-4664-8102-e03263673f6f",
				"method": "textDocument/willSaveWaitUntil"
			}
		]
	}
}

响应(Response):

  • result: void.
  • error: codemessage,以防在请求期间发生异常。

SetTrace 通知

客户端应使用该通知来修改服务器的跟踪设置。

通知(Notification):

  • method: "$/setTrace"
  • params: SetTraceParams 定义如下:
interface SetTraceParams {
	/**
	 * The new value that should be assigned to the trace setting.
	 */
	value: TraceValue;
}

LogTrace 通知

用于记录服务器执行跟踪的通知。这些通知的数量和内容取决于当前跟踪配置。如果 trace 为 "off",则服务器不应发送任何 logTrace 通知。如果 trace 为 "messages",则服务器不应在 LogTraceParams 中添加 verbose 字段。

$/logTrace 应用于系统跟踪报告。对于单个调试消息,服务器应发送 window/logMessage 通知。

通知(Notification):

  • method: "$/logTrace"
  • params: LogTraceParams 定义如下:
interface LogTraceParams {
	/**
	 * The message to be logged.
	 */
	message: string;
	/**
	 * Additional information that can be computed if the `trace` configuration
	 * is set to `'verbose'`
	 */
	verbose?: string;
}

Shutdown 请求

关闭请求从客户端发送到服务器。它要求服务器关闭,但不退出(否则可能无法将响应正确传递到客户端)。有一个单独的退出通知,要求服务器退出。客户端不得向已向其发送关闭请求的服务器发送除退出通知之外的任何请求和通知。客户端还应等待发送退出通知,直到收到来自关闭请求的响应。

如果服务器在关闭请求后收到请求,则这些请求应出现错误,并显示 InvalidRequest

请求(Request):

  • method: "shutdown"
  • params: none

响应(Response):

  • result: null
  • error: codemessage,以防在请求期间发生异常。

Exit 通知

要求服务器退出其进程的通知。如果之前已收到关闭请求,则服务器应退出并显示成功代码 0; 否则,error 代码为 1。

通知(Notification):

  • method: "exit"
  • params: none

文本文档同步

客户端对 textDocument/didOpentextDocument/didChangetextDocument/didClose 通知的支持在协议中是强制性的,客户端无法选择不支持它们。这包括 textDocument/didChange 通知中的完全同步和增量同步。此外,服务器必须实现所有这三个,或者不实现。因此,它们是通过客户端能力和服务端能力来联合控制的。仅当客户端显示的文档是只读的时,选择退出文本文档同步才有意义。否则,服务器可能会收到文档请求,这些文档的内容在客户端中进行管理(例如,它们可能已更改)。

客户端能力(Client capability):

  • 属性路径: textDocument.synchronization.dynamicRegistration
  • 属性类型: boolean

控制文本文档同步是否支持动态注册。

服务端能力(Server capability):

  • 属性路径: textDocumentSync
  • 属性类型: TextDocumentSyncKind | TextDocumentSyncOptions。下面的 TextDocumentSyncOptions 定义仅涵盖特定于打开、更改和关闭通知的属性:
export interface TextDocumentSyncOptions {
	/**
	 * Open and close notifications are sent to the server. If omitted open
	 * close notifications should not be sent.
	 */
	openClose?: boolean;

	/**
	 * Change notifications are sent to the server. See
	 * TextDocumentSyncKind.None, TextDocumentSyncKind.Full and
	 * TextDocumentSyncKind.Incremental. If omitted it defaults to
	 * TextDocumentSyncKind.None.
	 */
	change?: TextDocumentSyncKind;
}
/**
 * Defines how the host (editor) should sync document changes to the language
 * server.
 */
export namespace TextDocumentSyncKind {
	/**
	 * Documents should not be synced at all.
	 */
	export const None = 0;

	/**
	 * Documents are synced by always sending the full content
	 * of the document.
	 */
	export const Full = 1;

	/**
	 * Documents are synced by sending the full content on open.
	 * After that only incremental updates to the document are
	 * sent.
	 */
	export const Incremental = 2;
}

export type TextDocumentSyncKind = 0 | 1 | 2;

DidOpenTextDocument 通知

文档打开通知从客户端发送到服务器,以发出新打开的文本文档的信号。文档的内容现在由客户端管理,服务器不得尝试使用文档的 Uri 读取文档的内容。从这个意义上说,Open 意味着它由客户端管理。这并不一定意味着它的内容在编辑器中呈现。如果没有相应的关闭通知,则不得多次发送打开通知。这意味着打开和关闭通知必须平衡,并且特定 textDocument 的最大打开计数为 1。请注意,服务器满足请求的能力与文本文档是打开还是关闭无关。

DidOpenTextDocumentParams 包含与文档关联的语言 ID。如果文档的语言 ID 发生更改,则客户端需要向服务器发送 textDocument/didClose,如果服务器也处理新的语言 ID,则需要发送具有新语言 ID 的 textDocument/didOpen

客户端能力(Client capability): 请参阅通用文本文档同步的 客户端能力

服务端能力(Server Capability): 请参阅通用文本文档同步的 服务端能力

注册选项(Registration Options): TextDocumentRegistrationOptions

通知(Notification):

  • method: "textDocument/didOpen"
  • params: DidOpenTextDocumentParams 定义如下:
interface DidOpenTextDocumentParams {
	/**
	 * The document that was opened.
	 */
	textDocument: TextDocumentItem;
}

DidChangeTextDocument 通知

文档更改通知从客户端发送到服务器,以发出对文本文档的更改信号。在客户端可以更改文本文档之前,它必须使用 textDocument/didOpen 通知声明其内容的所有权。在 2.0 中,参数的形状已更改为包含正确的版本号。

客户端能力(Client capability): 请参阅通用文本文档同步的 客户端能力

服务端能力(Server Capability): 请参阅通用文本文档同步的 服务端能力

注册选项(Registration Options): TextDocumentChangeRegistrationOptions 定义如下:

/**
 * Describe options to be used when registering for text document change events.
 */
export interface TextDocumentChangeRegistrationOptions
	extends TextDocumentRegistrationOptions {
	/**
	 * How documents are synced to the server. See TextDocumentSyncKind.Full
	 * and TextDocumentSyncKind.Incremental.
	 */
	syncKind: TextDocumentSyncKind;
}

通知(Notification):

  • method: "textDocument/didChange"
  • params: DidChangeTextDocumentParams 定义如下:
interface DidChangeTextDocumentParams {
	/**
	 * The document that did change. The version number points
	 * to the version after all provided content changes have
	 * been applied.
	 */
	textDocument: VersionedTextDocumentIdentifier;

	/**
	 * The actual content changes. The content changes describe single state
	 * changes to the document. So if there are two content changes c1 (at
	 * array index 0) and c2 (at array index 1) for a document in state S then
	 * c1 moves the document from S to S' and c2 from S' to S''. So c1 is
	 * computed on the state S and c2 is computed on the state S'.
	 *
	 * To mirror the content of a document using change events use the following
	 * approach:
	 * - start with the same initial content
	 * - apply the 'textDocument/didChange' notifications in the order you
	 *   receive them.
	 * - apply the `TextDocumentContentChangeEvent`s in a single notification
	 *   in the order you receive them.
	 */
	contentChanges: TextDocumentContentChangeEvent[];
}
/**
 * An event describing a change to a text document. If only a text is provided
 * it is considered to be the full content of the document.
 */
export type TextDocumentContentChangeEvent = {
	/**
	 * The range of the document that changed.
	 */
	range: Range;

	/**
	 * The optional length of the range that got replaced.
	 *
	 * @deprecated use range instead.
	 */
	rangeLength?: uinteger;

	/**
	 * The new text for the provided range.
	 */
	text: string;
} | {
	/**
	 * The new text of the whole document.
	 */
	text: string;
};

WillSaveTextDocument 通知

在实际保存文档之前,文档将保存通知从客户端发送到服务器。如果服务器已注册打开/关闭事件,则客户端应确保在发送 willSave 通知之前打开文档,因为客户端无法在不转移所有权的情况下更改文件的内容。

客户端能力(Client capability):

  • 属性路径: textDocument.synchronization.willSave
  • 属性类型: boolean

服务端能力(Server capability):

  • 属性路径: textDocumentSync.willSave
  • 属性类型: boolean

注册选项(Registration Options): TextDocumentRegistrationOptions

通知(Notification):

  • method: "textDocument/willSave"
  • params: WillSaveTextDocumentParams 定义如下:
/**
 * The parameters send in a will save text document notification.
 */
export interface WillSaveTextDocumentParams {
	/**
	 * The document that will be saved.
	 */
	textDocument: TextDocumentIdentifier;

	/**
	 * The 'TextDocumentSaveReason'.
	 */
	reason: TextDocumentSaveReason;
}
/**
 * Represents reasons why a text document is saved.
 */
export namespace TextDocumentSaveReason {

	/**
	 * Manually triggered, e.g. by the user pressing save, by starting
	 * debugging, or by an API call.
	 */
	export const Manual = 1;

	/**
	 * Automatic after a delay.
	 */
	export const AfterDelay = 2;

	/**
	 * When the editor lost focus.
	 */
	export const FocusOut = 3;
}

export type TextDocumentSaveReason = 1 | 2 | 3;

WillSaveWaitUntilTextDocument 请求

在文档实际保存之前,文档将保存请求从客户端发送到服务器。该请求可以返回一个 TextEdits 数组,该数组将在保存文本文档之前应用于文本文档。请注意,如果计算文本编辑花费的时间过长,或者服务器在此请求上不断失败,则客户端可能会丢弃结果。这样做是为了保持保存的快速和可靠。如果服务器已注册打开/关闭事件,则客户端应确保在发送 willSaveWaitUntil 通知之前打开文档,因为客户端无法在不转移所有权的情况下更改文件的内容。

客户端能力(Client capability):

  • 属性路径: textDocument.synchronization.willSaveWaitUntil
  • 属性类型: boolean

服务端能力(Server capability):

  • 属性路径: textDocumentSync.willSaveWaitUntil
  • 属性类型: boolean

注册选项(Registration Options): TextDocumentRegistrationOptions

请求(Request):

  • method: "textDocument/willSaveWaitUntil"
  • params: WillSaveTextDocumentParams

响应(Response):

  • result: TextEdit[] | null
  • error: codemessage,以防在请求期间发生异常。

DidSaveTextDocument 通知

当文档在客户端中被保存时,文档保存通知将从客户端发送到服务器。

客户端能力(Client capability):

  • 属性路径: textDocument.synchronization.didSave
  • 属性类型: boolean

服务端能力(Server capability):

  • 属性路径: textDocumentSync.save
  • 属性类型: boolean | SaveOptionsSaveOptions 定义如下:
export interface SaveOptions {
	/**
	 * The client is supposed to include the content on save.
	 */
	includeText?: boolean;
}

注册选项(Registration Options): TextDocumentSaveRegistrationOptions, 定义如下:

export interface TextDocumentSaveRegistrationOptions extends TextDocumentRegistrationOptions {
	/**
	 * The client is supposed to include the content on save.
	 */
	includeText?: boolean;
}

通知(Notification):

  • method: "textDocument/didSave"
  • params: DidSaveTextDocumentParams, 定义如下:
interface DidSaveTextDocumentParams {
	/**
	 * The document that was saved.
	 */
	textDocument: TextDocumentIdentifier;

	/**
	 * Optional the content when saved. Depends on the includeText value
	 * when the save notification was requested.
	 */
	text?: string;
}

DidCloseTextDocument 通知

当文档在客户端中关闭时,文档关闭通知将从客户端发送到服务器。文档的 Uri 现在存在于文档的 Uri 指向的位置(例如,如果文档的 Uri 是文件 Uri,则主文件现在存在于磁盘上)。与打开通知一样,发送关闭通知意味着它不再由客户端管理,收到关闭通知并不意味着文档之前已在编辑器中打开。只有打开通知发送后,才能发送关闭通知。请注意,服务器满足请求的能力与文本文档是打开还是关闭无关。

客户端能力(Client capability): 请参阅通用文本文档同步的 客户端能力

服务端能力(Server Capability): 请参阅通用文本文档同步的 服务端能力

注册选项(Registration Options): TextDocumentRegistrationOptions

通知(Notification):

  • method: "textDocument/didClose"
  • params: DidCloseTextDocumentParams, 定义如下:
interface DidCloseTextDocumentParams {
	/**
	 * The document that was closed.
	 */
	textDocument: TextDocumentIdentifier;
}

重命名文档

文档重命名应向服务器发出信号,使用文档的旧名称发送文档关闭通知,然后使用文档的新名称发送打开通知。这样做的主要原因是除了名称之外,其他属性也可以更改,例如与文档关联的语言。此外,服务器可能不再对新文档感兴趣。

服务器可以通过订阅 workspace/didRenameFiles 通知或 workspace/willRenameFiles 请求来参与文档重命名。

TextDocumentSyncClientCapabilitiesTextDocumentSyncOptions 服务器选项的最终结构如下所示:

export interface TextDocumentSyncClientCapabilities {
	/**
	 * Whether text document synchronization supports dynamic registration.
	 */
	dynamicRegistration?: boolean;

	/**
	 * The client supports sending will save notifications.
	 */
	willSave?: boolean;

	/**
	 * The client supports sending a will save request and
	 * waits for a response providing text edits which will
	 * be applied to the document before it is saved.
	 */
	willSaveWaitUntil?: boolean;

	/**
	 * The client supports did save notifications.
	 */
	didSave?: boolean;
}
export interface TextDocumentSyncOptions {
	/**
	 * Open and close notifications are sent to the server. If omitted open
	 * close notification should not be sent.
	 */
	openClose?: boolean;
	/**
	 * Change notifications are sent to the server. See
	 * TextDocumentSyncKind.None, TextDocumentSyncKind.Full and
	 * TextDocumentSyncKind.Incremental. If omitted it defaults to
	 * TextDocumentSyncKind.None.
	 */
	change?: TextDocumentSyncKind;
	/**
	 * If present will save notifications are sent to the server. If omitted
	 * the notification should not be sent.
	 */
	willSave?: boolean;
	/**
	 * If present will save wait until requests are sent to the server. If
	 * omitted the request should not be sent.
	 */
	willSaveWaitUntil?: boolean;
	/**
	 * If present save notifications are sent to the server. If omitted the
	 * notification should not be sent.
	 */
	save?: boolean | SaveOptions;
}

Notebook 文档同步

Notebook 正变得越来越流行。通过向语言服务器协议添加对它们的支持,笔记本编辑器可以分别在 NotebookNotebook cell 中重用服务器提供的语言智能。为了重用协议部分,从而重用服务器实现,在 LSP 中按以下方式对笔记本进行建模:

  • Notebook document: 通常存储在磁盘文件中的笔记本单元的集合。笔记本文档具有类型,可以使用资源 URI 进行唯一标识。

  • notebook cell: 保存实际的文本内容。单元格有类型(代码或 markdown)。单元格的实际文本内容存储在文本文档中,该文本文档可以像所有其他文本文档一样同步到服务器。单元格文本文档具有 URI,但服务器不应依赖此 URI 的任何格式,因为如何创建这些 URI 取决于客户端。URI 在所有 notebook cell 中必须是唯一的,因此可用于唯一标识 notebook cellnotebook cell 的文本文档。

这两个概念的定义如下:

/**
 * A notebook document.
 *
 * @since 3.17.0
 */
export interface NotebookDocument {

	/**
	 * The notebook document's URI.
	 */
	uri: URI;

	/**
	 * The type of the notebook.
	 */
	notebookType: string;

	/**
	 * The version number of this document (it will increase after each
	 * change, including undo/redo).
	 */
	version: integer;

	/**
	 * Additional metadata stored with the notebook
	 * document.
	 */
	metadata?: LSPObject;

	/**
	 * The cells of a notebook.
	 */
	cells: NotebookCell[];
}
/**
 * A notebook cell.
 *
 * A cell's document URI must be unique across ALL notebook
 * cells and can therefore be used to uniquely identify a
 * notebook cell or the cell's text document.
 *
 * @since 3.17.0
 */
export interface NotebookCell {

	/**
	 * The cell's kind
	 */
	kind: NotebookCellKind;

	/**
	 * The URI of the cell's text document
	 * content.
	 */
	document: DocumentUri;

	/**
	 * Additional metadata stored with the cell.
	 */
	metadata?: LSPObject;

	/**
	 * Additional execution summary information
	 * if supported by the client.
	 */
	executionSummary?: ExecutionSummary;
}
/**
 * A notebook cell kind.
 *
 * @since 3.17.0
 */
export namespace NotebookCellKind {

	/**
	 * A markup-cell is formatted source that is used for display.
	 */
	export const Markup: 1 = 1;

	/**
	 * A code-cell is source code.
	 */
	export const Code: 2 = 2;
}
export interface ExecutionSummary {
	/**
	 * A strict monotonically increasing value
	 * indicating the execution order of a cell
	 * inside a notebook.
	 */
	executionOrder: uinteger;

	/**
	 * Whether the execution was successful or
	 * not if known by the client.
	 */
	success?: boolean;
}

接下来,我们将介绍如何将笔记本、笔记本单元格和笔记本单元格的内容同步到语言服务器。同步单元格的文本内容相对容易,因为客户端应将它们建模为文本文档。但是,由于笔记本单元的文本文档的 URI 应该是不透明的,因此服务器无法知道其 schemepath。所知道的只是笔记本文档本身。因此,我们为笔记本单元格文档引入了一个特殊的过滤器:

/**
 * A notebook cell text document filter denotes a cell text
 * document by different properties.
 *
 * @since 3.17.0
 */
export interface NotebookCellTextDocumentFilter {
	/**
	 * A filter that matches against the notebook
	 * containing the notebook cell. If a string
	 * value is provided it matches against the
	 * notebook type. '*' matches every notebook.
	 */
	notebook: string | NotebookDocumentFilter;

	/**
	 * A language id like `python`.
	 *
	 * Will be matched against the language id of the
	 * notebook cell document. '*' matches every language.
	 */
	language?: string;
}
/**
 * A notebook document filter denotes a notebook document by
 * different properties.
 *
 * @since 3.17.0
 */
export type NotebookDocumentFilter = {
	/** The type of the enclosing notebook. */
	notebookType?: string;

	/** A Uri [scheme](#Uri.scheme), like `file` or `untitled`. */
	scheme?: string;

	/** A glob pattern. */
	pattern?: string;
}

NotebookDocumentFilter 中的 notebookType, schemepattern 至少存在一个必填,这里为了简化,都定义为了可选。

给定这些结构,可以按如下方式识别 Jupyter Notebook 中存储在磁盘上的 Python 单元文档,该文档的路径中包含 books1 的文件夹中;

{
	notebook: {
		scheme: 'file',
		pattern '**/books1/**',
		notebookType: 'jupyter-notebook'
	},
	language: 'python'
}

NotebookCellTextDocumentFilter 可用于为某些请求(如代码完成或悬停)注册提供程序。如果注册了此类提供程序,则客户端将使用单元格文本文档的 URI 作为文档 URI 向服务器发送相应的 textDocument/* 请求。

在某些情况下,仅仅知道单元格的文本内容不足以让服务器推理单元格内容并提供良好的语言智能。有时需要了解笔记本文档的所有单元格,包括笔记本文档本身。考虑一个笔记本,它有两个 JavaScript 单元格,其中包含以下内容:

单元格一:

function add(a, b) {
	return a + b;
}

单元格二:

add/*<cursor>*/;

在标记的光标位置的第二个单元格中请求代码辅助应该建议函数 add,这只有在服务器知道单元格 1 和单元格 2 并且知道它们属于同一个笔记本文档时才有可能。

因此,在同步单元格文本内容时,该协议将支持两种模式:

  • cellContent: 在此模式下,仅使用标准 textDocument/did* 通知将单元格文本内容同步到服务器。没有 Notebook document,也没有单元格结构同步。此模式对于 Notebook 服务器便于采用,因为服务器可以重用大部分实现逻辑。

  • notebook: 在此模式下,Notebook 文档、Notebook 单元格和 Notebook 单元格文本内容将同步到服务器。为了允许服务器创建与 Notebook 文档一致的视图,不使用标准 textDocument/did* 通知同步单元格文本内容。相反,它使用特殊的 notebookDocument/did* 通知进行同步。这可确保单元格及其文本内容使用一个 open、change 或 close 事件到达服务器。

要请求单元格内容,只能使用普通的文档选择器。例如,选择器 [{ language: 'python' }] 会将 Python Notebook 文档单元格同步到服务器。但是,由于这也可能同步不需要的文档,因此文档筛选器也可以是 NotebookCellTextDocumentFilter。所以 { notebook: { scheme: 'file', notebookType: 'jupyter-notebook' }, language: 'python' } 同步存储在磁盘上的 Jupyter Notebook 中的所有 Python 单元格。

若要同步整个笔记本文档,服务器在其服务器能力中提供 notebookDocumentSync。例如:

{
	notebookDocumentSync: {
		notebookSelector: [
			{
				notebook: { scheme: 'file', notebookType: 'jupyter-notebook' },
				cells: [{ language: 'python' }]
			}
		]
	}
}

如果 Notebook 存储在磁盘上,则将 Notebook(包括所有 Python 单元)同步到服务器。

客户端能力(Client capability):

  • 属性路径: notebookDocument.synchronization
  • 属性类型: NotebookDocumentSyncClientCapabilities, 定义如下:
/**
 * Notebook specific client capabilities.
 *
 * @since 3.17.0
 */
export interface NotebookDocumentSyncClientCapabilities {

	/**
	 * Whether implementation supports dynamic registration. If this is
	 * set to `true` the client supports the new
	 * `(NotebookDocumentSyncRegistrationOptions & NotebookDocumentSyncOptions)`
	 * return value for the corresponding server capability as well.
	 */
	dynamicRegistration?: boolean;

	/**
	 * The client supports sending execution summary data per cell.
	 */
	executionSummarySupport?: boolean;
}

服务端能力(Server capability):

  • 属性路径: notebookDocumentSync
  • 属性类型: NotebookDocumentSyncOptions | NotebookDocumentSyncRegistrationOptions, NotebookDocumentOptions 定义如下:
/**
 * Options specific to a notebook plus its cells
 * to be synced to the server.
 *
 * If a selector provides a notebook document
 * filter but no cell selector all cells of a
 * matching notebook document will be synced.
 *
 * If a selector provides no notebook document
 * filter but only a cell selector all notebook
 * documents that contain at least one matching
 * cell will be synced.
 *
 * @since 3.17.0
 */
export interface NotebookDocumentSyncOptions {
	/**
	 * The notebooks to be synced
	 */
	notebookSelector: ({
		/**
		 * The notebook to be synced. If a string
		 * value is provided it matches against the
		 * notebook type. '*' matches every notebook.
		 */
		notebook: string | NotebookDocumentFilter;

		/**
		 * The cells of the matching notebook to be synced.
		 */
		cells?: { language: string }[];
	} | {
		/**
		 * The notebook to be synced. If a string
		 * value is provided it matches against the
		 * notebook type. '*' matches every notebook.
		 */
		notebook?: string | NotebookDocumentFilter;

		/**
		 * The cells of the matching notebook to be synced.
		 */
		cells: { language: string }[];
	})[];

	/**
	 * Whether save notification should be forwarded to
	 * the server. Will only be honored if mode === `notebook`.
	 */
	save?: boolean;
}

注册选项(Registration Options): notebookDocumentSyncRegistrationOptions, 定义如下:

/**
 * Registration options specific to a notebook.
 *
 * @since 3.17.0
 */
export interface NotebookDocumentSyncRegistrationOptions extends
	NotebookDocumentSyncOptions, StaticRegistrationOptions {
}

DidOpenNotebookDocument 通知

打开 Notebook 文档时,打开通知将从客户端发送到服务器。仅当服务器提供 notebookDocumentSync 能力时,客户端才会发送它。

通知(Notification):

  • method: "notebookDocument/didOpen"
  • params: DidOpenNotebookDocumentParams, 定义如下:
/**
 * The params sent in an open notebook document notification.
 *
 * @since 3.17.0
 */
export interface DidOpenNotebookDocumentParams {

	/**
	 * The notebook document that got opened.
	 */
	notebookDocument: NotebookDocument;

	/**
	 * The text documents that represent the content
	 * of a notebook cell.
	 */
	cellTextDocuments: TextDocumentItem[];
}

DidChangeNotebookDocument 通知

当笔记本文档发生更改时,更改通知将从客户端发送到服务器。仅当服务器提供 notebookDocumentSync 能力时,客户端才会发送它。

通知(Notification):

  • method: "notebookDocument/didChange"
  • params: DidChangeNotebookDocumentParams, 定义如下:
/**
 * The params sent in a change notebook document notification.
 *
 * @since 3.17.0
 */
export interface DidChangeNotebookDocumentParams {

	/**
	 * The notebook document that did change. The version number points
	 * to the version after all provided changes have been applied.
	 */
	notebookDocument: VersionedNotebookDocumentIdentifier;

	/**
	 * The actual changes to the notebook document.
	 *
	 * The change describes single state change to the notebook document.
	 * So it moves a notebook document, its cells and its cell text document
	 * contents from state S to S'.
	 *
	 * To mirror the content of a notebook using change events use the
	 * following approach:
	 * - start with the same initial content
	 * - apply the 'notebookDocument/didChange' notifications in the order
	 *   you receive them.
	 */
	change: NotebookDocumentChangeEvent;
}
/**
 * A versioned notebook document identifier.
 *
 * @since 3.17.0
 */
export interface VersionedNotebookDocumentIdentifier {

	/**
	 * The version number of this notebook document.
	 */
	version: integer;

	/**
	 * The notebook document's URI.
	 */
	uri: URI;
}
/**
 * A change event for a notebook document.
 *
 * @since 3.17.0
 */
export interface NotebookDocumentChangeEvent {
	/**
	 * The changed meta data if any.
	 */
	metadata?: LSPObject;

	/**
	 * Changes to cells
	 */
	cells?: {
		/**
		 * Changes to the cell structure to add or
		 * remove cells.
		 */
		structure?: {
			/**
			 * The change to the cell array.
			 */
			array: NotebookCellArrayChange;

			/**
			 * Additional opened cell text documents.
			 */
			didOpen?: TextDocumentItem[];

			/**
			 * Additional closed cell text documents.
			 */
			didClose?: TextDocumentIdentifier[];
		};

		/**
		 * Changes to notebook cells properties like its
		 * kind, execution summary or metadata.
		 */
		data?: NotebookCell[];

		/**
		 * Changes to the text content of notebook cells.
		 */
		textContent?: {
			document: VersionedTextDocumentIdentifier;
			changes: TextDocumentContentChangeEvent[];
		}[];
	};
}
/**
 * A change describing how to move a `NotebookCell`
 * array from state S to S'.
 *
 * @since 3.17.0
 */
export interface NotebookCellArrayChange {
	/**
	 * The start offset of the cell that changed.
	 */
	start: uinteger;

	/**
	 * The deleted cells
	 */
	deleteCount: uinteger;

	/**
	 * The new cells, if any
	 */
	cells?: NotebookCell[];
}

DidSaveNotebookDocument 通知

保存笔记本文档时,保存通知将从客户端发送到服务器。仅当服务器提供 notebookDocumentSync 能力时,客户端才会发送它。

通知(Notification):

  • method: "notebookDocument/didSave"
  • params: DidSaveNotebookDocumentParams, 定义如下:
/**
 * The params sent in a save notebook document notification.
 *
 * @since 3.17.0
 */
export interface DidSaveNotebookDocumentParams {
	/**
	 * The notebook document that got saved.
	 */
	notebookDocument: NotebookDocumentIdentifier;
}

DidCloseNotebookDocument 通知

关闭笔记本文档时,关闭通知将从客户端发送到服务器。仅当服务器提供 notebookDocumentSync 能力时,客户端才会发送它。

通知(Notification):

  • method: "notebookDocument/didClose"
  • params: DidCloseNotebookDocumentParams, 定义如下:
/**
 * The params sent in a close notebook document notification.
 *
 * @since 3.17.0
 */
export interface DidCloseNotebookDocumentParams {

	/**
	 * The notebook document that got closed.
	 */
	notebookDocument: NotebookDocumentIdentifier;

	/**
	 * The text documents that represent the content
	 * of a notebook cell that got closed.
	 */
	cellTextDocuments: TextDocumentIdentifier[];
}
/**
 * A literal to identify a notebook document in the client.
 *
 * @since 3.17.0
 */
export interface NotebookDocumentIdentifier {
	/**
	 * The notebook document's URI.
	 */
	uri: URI;
}

语言功能

语言功能在语言服务器协议中提供实际的智能。它们通常在 [text document, position] 元组上执行。主要语言功能类别包括:

  • 代码理解功能,如悬停或转到定义。

  • 编码功能,如诊断、代码完成或代码操作。

语言功能应根据文档的同步状态进行计算。

Goto Declaration 请求

转到声明请求从客户端发送到服务器,以解析给定文本文档位置的符号声明位置。

结果类型 LocationLink[] 是在 3.14.0 版中引入的,它依赖于相应的客户端功能 textDocument.declaration.linkSupport

客户端能力(Client capability):

  • 属性路径: textDocument.declaration
  • 属性类型: DeclarationClientCapabilities, 定义如下:
export interface DeclarationClientCapabilities {
	/**
	 * Whether declaration supports dynamic registration. If this is set to
	 * `true` the client supports the new `DeclarationRegistrationOptions`
	 * return value for the corresponding server capability as well.
	 */
	dynamicRegistration?: boolean;

	/**
	 * The client supports additional metadata in the form of declaration links.
	 */
	linkSupport?: boolean;
}

服务端能力(Server capability):

  • 属性路径: declarationProvider
  • 属性类型: boolean | DeclarationOptions | DeclarationRegistrationOptions, DeclarationOptions 定义如下:
export interface DeclarationOptions extends WorkDoneProgressOptions {
}

注册选项(Registration Options): DeclarationRegistrationOptions, 定义如下:

export interface DeclarationRegistrationOptions extends DeclarationOptions,
	TextDocumentRegistrationOptions, StaticRegistrationOptions {
}

请求(Request):

  • method: "textDocument/declaration"
  • params: DeclarationParams, 定义如下:
export interface DeclarationParams extends TextDocumentPositionParams,
	WorkDoneProgressParams, PartialResultParams {
}

响应(Response):

  • result: Location | Location[] | LocationLink[] |null
  • partial result: Location[] | LocationLink[]
  • error: codemessage,以防在请求期间发生异常。

Goto Definition 请求

转到定义请求从客户端发送到服务器,以解析给定文本文档位置的符号的定义位置。

goto-definition

结果类型 LocationLink[] 是在 3.14.0 版中引入的,它依赖于相应的客户端功能 textDocument.definition.linkSupport

客户端能力(Client capability):

  • 属性路径: textDocument.definition
  • 属性类型: DefinitionClientCapabilities, 定义如下:
export interface DefinitionClientCapabilities {
	/**
	 * Whether definition supports dynamic registration.
	 */
	dynamicRegistration?: boolean;

	/**
	 * The client supports additional metadata in the form of definition links.
	 *
	 * @since 3.14.0
	 */
	linkSupport?: boolean;
}

服务端能力(Server capability):

  • 属性路径: definitionProvider
  • 属性类型: boolean | DefinitionOptions, DefinitionOptions 定义如下:
export interface DefinitionOptions extends WorkDoneProgressOptions {
}

注册选项(Registration Options): DefinitionRegistrationOptions, 定义如下:

export interface DefinitionRegistrationOptions extends
	TextDocumentRegistrationOptions, DefinitionOptions {
}

请求(Request):

  • method: "textDocument/definition"
  • params: DefinitionParams, 定义如下:
export interface DefinitionParams extends TextDocumentPositionParams,
	WorkDoneProgressParams, PartialResultParams {
}

响应(Response):

  • result: Location | Location[] | LocationLink[] | null
  • partial result: Location[] | LocationLink[]
  • error: codemessage,以防在请求期间发生异常。

Goto Type Definition 请求

从版本 3.6.0 开始

转到类型定义请求从客户端发送到服务器,以解析给定文本文档位置处符号的类型定义位置。

goto-type-definition

结果类型 LocationLink[] 是在 3.14.0 版中引入的,它依赖于相应的客户端功能 textDocument.typeDefinition.linkSupport

客户端能力(Client capability):

  • 属性路径: textDocument.typeDefinition
  • 属性类型: TypeDefinitionClientCapabilities, 定义如下:
export interface TypeDefinitionClientCapabilities {
	/**
	 * Whether implementation supports dynamic registration. If this is set to
	 * `true` the client supports the new `TypeDefinitionRegistrationOptions`
	 * return value for the corresponding server capability as well.
	 */
	dynamicRegistration?: boolean;

	/**
	 * The client supports additional metadata in the form of definition links.
	 *
	 * @since 3.14.0
	 */
	linkSupport?: boolean;
}

服务端能力(Server capability):

  • 属性路径: typeDefinitionProvider
  • 属性类型: boolean | TypeDefinitionOptions | TypeDefinitionRegistrationOptions, TypeDefinitionOptions 定义如下:
export interface TypeDefinitionOptions extends WorkDoneProgressOptions {
}

注册选项(Registration Options): TypeDefinitionRegistrationOptions, 定义如下:

export interface TypeDefinitionRegistrationOptions extends
	TextDocumentRegistrationOptions, TypeDefinitionOptions,
	StaticRegistrationOptions {
}

请求(Request):

  • method: "textDocument/typeDefinition"
  • params: TypeDefinitionParams, 定义如下:
export interface TypeDefinitionParams extends TextDocumentPositionParams,
	WorkDoneProgressParams, PartialResultParams {
}

响应(Response):

  • result: Location | Location[] | LocationLink[] | null
  • partial result: Location[] | LocationLink[]
  • error: codemessage,以防在请求期间发生异常。

Goto Implementation 请求

从版本 3.6.0 开始

转到实现请求从客户端发送到服务器,以解析给定文本文档位置的符号的实现位置。

goto-implementation

结果类型 LocationLink[] 是在 3.14.0 版本中引入的,它依赖于相应的客户端功能 textDocument.implementation.linkSupport

客户端能力(Client capability):

  • 属性路径: textDocument.implementation
  • 属性类型: ImplementationClientCapabilities, 定义如下:
export interface ImplementationClientCapabilities {
	/**
	 * Whether implementation supports dynamic registration. If this is set to
	 * `true` the client supports the new `ImplementationRegistrationOptions`
	 * return value for the corresponding server capability as well.
	 */
	dynamicRegistration?: boolean;

	/**
	 * The client supports additional metadata in the form of definition links.
	 *
	 * @since 3.14.0
	 */
	linkSupport?: boolean;
}

服务端能力(Server capability):

  • 属性路径: implementationProvider
  • 属性类型: boolean | ImplementationOptions | ImplementationRegistrationOptions, ImplementationOptions 定义如下:
export interface ImplementationOptions extends WorkDoneProgressOptions {
}

注册选项(Registration Options): ImplementationRegistrationOptions 定义如下:

export interface ImplementationRegistrationOptions extends
	TextDocumentRegistrationOptions, ImplementationOptions,
	StaticRegistrationOptions {
}

请求(Request):

  • method: "textDocument/implementation"
  • params: ImplementationParams, 定义如下:
export interface ImplementationParams extends TextDocumentPositionParams,
	WorkDoneProgressParams, PartialResultParams {
}

响应(Response):

  • result: Location | Location[] | LocationLink[] | null
  • partial result: Location[] | LocationLink[]
  • error: codemessage,以防在请求期间发生异常。

Find References 请求

引用请求从客户端发送到服务器,以解析由给定文本文档位置表示的符号的项目范围引用。

find-reference

客户端能力(Client capability):

  • 属性路径: textDocument.references
  • 属性类型: ReferenceClientCapabilities, 定义如下:
export interface ReferenceClientCapabilities {
	/**
	 * Whether references supports dynamic registration.
	 */
	dynamicRegistration?: boolean;
}

服务端能力(Server capability):

  • 属性路径: referencesProvider
  • 属性类型: boolean | ReferenceOptions, ReferenceOptions 定义如下:
export interface ReferenceOptions extends WorkDoneProgressOptions {
}

注册选项(Registration Options): ReferenceRegistrationOptions, 定义如下:

export interface ReferenceRegistrationOptions extends
	TextDocumentRegistrationOptions, ReferenceOptions {
}

请求(Request):

  • method: "textDocument/references"
  • params: ReferenceParams, 定义如下:
export interface ReferenceParams extends TextDocumentPositionParams,
	WorkDoneProgressParams, PartialResultParams {
	context: ReferenceContext;
}
export interface ReferenceContext {
	/**
	 * Include the declaration of the current symbol.
	 */
	includeDeclaration: boolean;
}

响应(Response):

  • result: Location[] | null
  • partial result: Location[]
  • error: codemessage,以防在请求期间发生异常。

Call Hierarchy

Prepare Call Hierarchy 请求

调用层次结构请求从客户端发送到服务器,以返回给定文本文档位置的语言元素的调用层次结构。调用层次结构请求分两个步骤执行:

  • 首先,为给定的文本文档位置解析调用层次结构项

  • 对于调用层次结构项,将解析传入或传出调用层次结构项。

call-hierarchy

客户端能力(Client capability):

  • 属性路径: textDocument.callHierarchy
  • 属性类型: CallHierarchyClientCapabilities, 定义如下:
interface CallHierarchyClientCapabilities {
	/**
	 * Whether implementation supports dynamic registration. If this is set to
	 * `true` the client supports the new `(TextDocumentRegistrationOptions &
	 * StaticRegistrationOptions)` return value for the corresponding server
	 * capability as well.
	 */
	dynamicRegistration?: boolean;
}

服务端能力(Server capability):

  • 属性路径: callHierarchyProvider
  • 属性类型: boolean | CallHierarchyOptions | CallHierarchyRegistrationOptions, CallHierarchyOptions 定义如下:
export interface CallHierarchyOptions extends WorkDoneProgressOptions {
}

注册选项(Registration Options): CallHierarchyRegistrationOptions, 定义如下:

export interface CallHierarchyRegistrationOptions extends
	TextDocumentRegistrationOptions, CallHierarchyOptions,
	StaticRegistrationOptions {
}

请求(Request):

  • method: "textDocument/prepareCallHierarchy"
  • params: CallHierarchyPrepareParams, 定义如下:
export interface CallHierarchyPrepareParams extends TextDocumentPositionParams,
	WorkDoneProgressParams {
}

响应(Response):

  • result: CallHierarchyItem[] | null, 定义如下:
export interface CallHierarchyItem {
	/**
	 * The name of this item.
	 */
	name: string;

	/**
	 * The kind of this item.
	 */
	kind: SymbolKind;

	/**
	 * Tags for this item.
	 */
	tags?: SymbolTag[];

	/**
	 * More detail for this item, e.g. the signature of a function.
	 */
	detail?: string;

	/**
	 * The resource identifier of this item.
	 */
	uri: DocumentUri;

	/**
	 * The range enclosing this symbol not including leading/trailing whitespace
	 * but everything else, e.g. comments and code.
	 */
	range: Range;

	/**
	 * The range that should be selected and revealed when this symbol is being
	 * picked, e.g. the name of a function. Must be contained by the
	 * [`range`](#CallHierarchyItem.range).
	 */
	selectionRange: Range;

	/**
	 * A data entry field that is preserved between a call hierarchy prepare and
	 * incoming calls or outgoing calls requests.
	 */
	data?: unknown;
}
  • error: codemessage,以防在请求期间发生异常。

Call Hierarchy Incoming Calls 请求

从版本 3.16.0 开始

请求从客户端发送到服务器,以解决给定调用层次结构项的传入调用。请求未定义自己的客户端和服务器能力。仅当服务器注册 textDocument/prepareCallHierarchy 请求时,才会发出它。

call-hierarchy-incoming-calls

请求(Request):

  • method: "callHierarchy/incomingCalls"
  • params: CallHierarchyIncomingCallsParams, 定义如下:
export interface CallHierarchyIncomingCallsParams extends
	WorkDoneProgressParams, PartialResultParams {
	item: CallHierarchyItem;
}

响应(Response):

  • result: CallHierarchyIncomingCall[] | null, 定义如下:
export interface CallHierarchyIncomingCall {

	/**
	 * The item that makes the call.
	 */
	from: CallHierarchyItem;

	/**
	 * The ranges at which the calls appear. This is relative to the caller
	 * denoted by [`this.from`](#CallHierarchyIncomingCall.from).
	 */
	fromRanges: Range[];
}
  • partial result: CallHierarchyIncomingCall[]
  • error: codemessage,以防在请求期间发生异常。

Call Hierarchy Outgoing Calls 请求

请求从客户端发送到服务器,以解决给定调用层次结构项的传出调用。请求未定义自己的客户端和服务器能力。仅当服务器注册 textDocument/prepareCallHierarchy 请求时,才会发出它。

call-hierarchy-outgoing-calls

请求(Request):

  • method: "callHierarchy/outgoingCalls"
  • params: CallHierarchyOutgoingCallsParams, 定义如下:
export interface CallHierarchyOutgoingCallsParams extends
	WorkDoneProgressParams, PartialResultParams {
	item: CallHierarchyItem;
}

响应(Response):

  • result: CallHierarchyOutgoingCall[] | null, 定义如下:
export interface CallHierarchyOutgoingCall {

	/**
	 * The item that is called.
	 */
	to: CallHierarchyItem;

	/**
	 * The range at which this item is called. This is the range relative to
	 * the caller, e.g the item passed to `callHierarchy/outgoingCalls` request.
	 */
	fromRanges: Range[];
}
  • partial result: CallHierarchyOutgoingCall[]
  • error: codemessage,以防在请求期间发生异常。

Type Hierarchy

Prepare Type Hierarchy 请求

从版本 3.17.0 开始

类型层次结构请求从客户端发送到服务器,以返回给定文本文档位置的语言元素的类型层次结构。如果服务器无法从该位置推断出有效类型,则将返回 null。类型层次结构请求分两个步骤执行:

  • 首先,为给定的文本文档位置准备类型层次结构项。

  • 对于类型层次结构项,将解析超类型或子类型层次结构项。

客户端能力(Client capability):

  • 属性路径: textDocument.typeHierarchy
  • 属性类型: TypeHierarchyClientCapabilities, 定义如下:
type TypeHierarchyClientCapabilities = {
	/**
	 * Whether implementation supports dynamic registration. If this is set to
	 * `true` the client supports the new `(TextDocumentRegistrationOptions &
	 * StaticRegistrationOptions)` return value for the corresponding server
	 * capability as well.
	 */
	dynamicRegistration?: boolean;
};

服务端能力(Server capability):

  • 属性路径: typeHierarchyProvider
  • 属性类型: boolean | TypeHierarchyOptions | TypeHierarchyRegistrationOptions, TypeHierarchyOptions 定义如下:
export interface TypeHierarchyOptions extends WorkDoneProgressOptions {
}

注册选项(Registration Options): TypeHierarchyRegistrationOptions, 定义如下:

export interface TypeHierarchyRegistrationOptions extends
	TextDocumentRegistrationOptions, TypeHierarchyOptions,
	StaticRegistrationOptions {
}

请求(Request):

  • method: "textDocument/prepareTypeHierarchy"
  • params: TypeHierarchyPrepareParams, 定义如下:
export interface TypeHierarchyPrepareParams extends TextDocumentPositionParams,
	WorkDoneProgressParams {
}

响应(Response):

  • result: TypeHierarchyItem[] | null, 定义如下:
export interface TypeHierarchyItem {
	/**
	 * The name of this item.
	 */
	name: string;

	/**
	 * The kind of this item.
	 */
	kind: SymbolKind;

	/**
	 * Tags for this item.
	 */
	tags?: SymbolTag[];

	/**
	 * More detail for this item, e.g. the signature of a function.
	 */
	detail?: string;

	/**
	 * The resource identifier of this item.
	 */
	uri: DocumentUri;

	/**
	 * The range enclosing this symbol not including leading/trailing whitespace
	 * but everything else, e.g. comments and code.
	 */
	range: Range;

	/**
	 * The range that should be selected and revealed when this symbol is being
	 * picked, e.g. the name of a function. Must be contained by the
	 * [`range`](#TypeHierarchyItem.range).
	 */
	selectionRange: Range;

	/**
	 * A data entry field that is preserved between a type hierarchy prepare and
	 * supertypes or subtypes requests. It could also be used to identify the
	 * type hierarchy in the server, helping improve the performance on
	 * resolving supertypes and subtypes.
	 */
	data?: LSPAny;
}
  • error: codemessage,以防在请求期间发生异常。

Type Hierarchy Supertypes 调用

从版本 3.17.0 开始

请求从客户端发送到服务器,以解析给定类型层次结构项的超类型。如果服务器无法从参数中的项推断出有效类型,则将返回 null。请求未定义自己的客户端和服务器功能。仅当服务器注册 textDocument/prepareTypeHierarchy 请求时,才会发出它。

请求(Request):

  • method: "typeHierarchy/supertypes"
  • params: TypeHierarchySupertypesParams, 定义如下:
export interface TypeHierarchySupertypesParams extends
	WorkDoneProgressParams, PartialResultParams {
	item: TypeHierarchyItem;
}

响应(Response):

  • result: TypeHierarchyItem[] | null
  • partial result: TypeHierarchyItem[]
  • error: codemessage,以防在请求期间发生异常。

Type Hierarchy Subtypes 调用

从版本 3.17.0 开始

请求从客户端发送到服务器,以解析给定类型层次结构项的子类型。如果服务器无法从参数中的项推断出有效类型,则将返回 null。请求未定义自己的客户端和服务器功能。仅当服务器注册 textDocument/prepareTypeHierarchy 请求时,才会发出它。

请求(Request):

  • method: "typeHierarchy/subtypes"
  • params: TypeHierarchySubtypesParams, 定义如下:
export interface TypeHierarchySubtypesParams extends
	WorkDoneProgressParams, PartialResultParams {
	item: TypeHierarchyItem;
}

响应(Response):

  • result: TypeHierarchyItem[] | null
  • partial result: TypeHierarchyItem[]
  • error: codemessage,以防在请求期间发生异常。

Document Highlights 请求

文档突出显示请求从客户端发送到服务器,以解决给定文本文档位置的文档突出显示问题。对于编程语言,这通常会突出显示对此文件范围的符号的所有引用。但是,我们将 textDocument/documentHighlighttextDocument/references 分开,因为第一个请求允许更加模糊。符号匹配通常 DocumentHighlightKindReadWrite,而模糊或文本匹配使用 Text 作为类型。

document-highlight

客户端能力(Client capability):

  • 属性路径: textDocument.documentHighlight
  • 属性类型: DocumentHighlightClientCapabilities, 定义如下:
export interface DocumentHighlightClientCapabilities {
	/**
	 * Whether document highlight supports dynamic registration.
	 */
	dynamicRegistration?: boolean;
}

服务端能力(Server capability):

  • 属性路径: documentHighlightProvider
  • 属性类型: boolean | DocumentHighlightOptions, DocumentHighlightOptions 定义如下:
export interface DocumentHighlightOptions extends WorkDoneProgressOptions {
}

注册选项(Registration Options): DocumentHighlightRegistrationOptions, 定义如下:

export interface DocumentHighlightRegistrationOptions extends
	TextDocumentRegistrationOptions, DocumentHighlightOptions {
}

请求(Request):

  • method: "textDocument/documentHighlight"
  • params: DocumentHighlightParams, 定义如下:
export interface DocumentHighlightParams extends TextDocumentPositionParams,
	WorkDoneProgressParams, PartialResultParams {
}

响应(Response):

  • result: DocumentHighlight[] | null, 定义如下:
/**
 * A document highlight is a range inside a text document which deserves
 * special attention. Usually a document highlight is visualized by changing
 * the background color of its range.
 *
 */
export interface DocumentHighlight {
	/**
	 * The range this highlight applies to.
	 */
	range: Range;

	/**
	 * The highlight kind, default is DocumentHighlightKind.Text.
	 */
	kind?: DocumentHighlightKind;
}
/**
 * A document highlight kind.
 */
export namespace DocumentHighlightKind {
	/**
	 * A textual occurrence.
	 */
	export const Text = 1;

	/**
	 * Read-access of a symbol, like reading a variable.
	 */
	export const Read = 2;

	/**
	 * Write-access of a symbol, like writing to a variable.
	 */
	export const Write = 3;
}

export type DocumentHighlightKind = 1 | 2 | 3;
  • partial result: DocumentHighlight[]
  • error: codemessage,以防在请求期间发生异常。

Document Link

文档链接请求从客户端发送到服务器,以请求文档中链接的位置。

document-link

客户端能力(Client capability):

  • 属性路径: textDocument.documentLink
  • 属性类型: DocumentLinkClientCapabilities, 定义如下:
export interface DocumentLinkClientCapabilities {
	/**
	 * Whether document link supports dynamic registration.
	 */
	dynamicRegistration?: boolean;

	/**
	 * Whether the client supports the `tooltip` property on `DocumentLink`.
	 *
	 * @since 3.15.0
	 */
	tooltipSupport?: boolean;
}

服务端能力(Server capability):

  • 属性路径: documentLinkProvider
  • 属性类型: DocumentLinkOptions, 定义如下:
export interface DocumentLinkOptions extends WorkDoneProgressOptions {
	/**
	 * Document links have a resolve provider as well.
	 */
	resolveProvider?: boolean;
}

注册选项(Registration Options): DocumentLinkRegistrationOptions, 定义如下:

export interface DocumentLinkRegistrationOptions extends
	TextDocumentRegistrationOptions, DocumentLinkOptions {
}

请求(Request):

  • method: "textDocument/documentLink"
  • params: DocumentLinkParams, 定义如下:
interface DocumentLinkParams extends WorkDoneProgressParams,
	PartialResultParams {
	/**
	 * The document to provide document links for.
	 */
	textDocument: TextDocumentIdentifier;
}

响应(Response):

  • result: DocumentLink[] | null, 定义如下:
/**
 * A document link is a range in a text document that links to an internal or
 * external resource, like another text document or a web site.
 */
interface DocumentLink {
	/**
	 * The range this link applies to.
	 */
	range: Range;

	/**
	 * The uri this link points to. If missing a resolve request is sent later.
	 */
	target?: URI;

	/**
	 * The tooltip text when you hover over this link.
	 *
	 * If a tooltip is provided, is will be displayed in a string that includes
	 * instructions on how to trigger the link, such as `{0} (ctrl + click)`.
	 * The specific instructions vary depending on OS, user settings, and
	 * localization.
	 *
	 * @since 3.15.0
	 */
	tooltip?: string;

	/**
	 * A data entry field that is preserved on a document link between a
	 * DocumentLinkRequest and a DocumentLinkResolveRequest.
	 */
	data?: LSPAny;
}
  • partial result: DocumentLink[]
  • error: codemessage,以防在请求期间发生异常。

文档链接解析请求从客户端发送到服务器,以解析给定文档链接的目标。

请求(Request):

  • method: "documentLink/resolve"
  • params: DocumentLink

响应(Response):

  • result: DocumentLink
  • error: codemessage,以防在请求期间发生异常。

Hover 请求

悬停请求从客户端发送到服务器,以请求给定文本文档位置的悬停信息。

hover

客户端能力(Client capability):

  • 属性路径: textDocument.hover
  • 属性类型: HoverClientCapabilities, 定义如下:
export interface HoverClientCapabilities {
	/**
	 * Whether hover supports dynamic registration.
	 */
	dynamicRegistration?: boolean;

	/**
	 * Client supports the follow content formats if the content
	 * property refers to a `literal of type MarkupContent`.
	 * The order describes the preferred format of the client.
	 */
	contentFormat?: MarkupKind[];
}

服务端能力(Server capability):

  • 属性路径: hoverProvider
  • 属性类型: boolean | HoverOptions, 定义如下:
export interface HoverOptions extends WorkDoneProgressOptions {
}

注册选项(Registration Options): HoverRegistrationOptions, 定义如下:

export interface HoverRegistrationOptions
	extends TextDocumentRegistrationOptions, HoverOptions {
}

请求(Request):

  • method: "textDocument/hover"
  • params: HoverParams, 定义如下:
export interface HoverParams extends TextDocumentPositionParams,
	WorkDoneProgressParams {
}

响应(Response):

  • result: Hover | null, 定义如下:
/**
 * The result of a hover request.
 */
export interface Hover {
	/**
	 * The hover's 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;
}
/**
 * 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 };
  • error: codemessage,以防在请求期间发生异常。

Code Lens

Code Lens 请求

代码信息指示器请求从客户端发送到服务器,以计算给定文本文档的代码信息指示器。

code-lens

客户端能力(Client capability):

  • 属性路径: textDocument.codeLens
  • 属性类型: CodeLensClientCapabilities, 定义如下:
export interface CodeLensClientCapabilities {
	/**
	 * Whether code lens supports dynamic registration.
	 */
	dynamicRegistration?: boolean;
}

服务端能力(Server capability):

  • 属性路径: codeLensProvider
  • 属性类型: CodeLensOptions, 定义如下:
export interface CodeLensOptions extends WorkDoneProgressOptions {
	/**
	 * Code lens has a resolve provider as well.
	 */
	resolveProvider?: boolean;
}

注册选项(Registration Options): CodeLensRegistrationOptions, 定义如下:

export interface CodeLensRegistrationOptions extends
	TextDocumentRegistrationOptions, CodeLensOptions {
}

请求(Request):

  • method: "textDocument/codeLens"
  • params: CodeLensParams, 定义如下:
interface CodeLensParams extends WorkDoneProgressParams, PartialResultParams {
	/**
	 * The document to request code lens for.
	 */
	textDocument: TextDocumentIdentifier;
}

响应(Response):

  • result: CodeLens[] | null, 定义如下:
/**
 * A code lens represents a command that should be shown along with
 * source text, like the number of references, a way to run tests, etc.
 *
 * A code lens is _unresolved_ when no command is associated to it. For
 * performance reasons the creation of a code lens and resolving should be done
 * in two stages.
 */
interface CodeLens {
	/**
	 * The range in which this code lens is valid. Should only span a single
	 * line.
	 */
	range: Range;

	/**
	 * The command this code lens represents.
	 */
	command?: Command;

	/**
	 * A data entry field that is preserved on a code lens item between
	 * a code lens and a code lens resolve request.
	 */
	data?: LSPAny;
}
  • partial result: CodeLens[]
  • error: codemessage,以防在请求期间发生异常。

Code Lens Resolve 请求

代码信息指示器解析请求从客户端发送到服务器,以解析给定代码信息指示器项的命令。

请求(Request):

  • method: "codeLens/resolve"
  • params: CodeLens

响应(Response):

  • result: CodeLens, 定义如下:
  • error: codemessage,以防在请求期间发生异常。

Code Lens Refresh 请求

从版本 3.16.0 开始

workspace/codeLens/refresh 请求从服务器发送到客户端。服务器可以使用它来要求客户端刷新当前显示在编辑器中的代码信息指示器。因此,客户端应要求服务器重新计算这些编辑器的代码信息指示器。如果服务器检测到需要重新计算所有代码信息指示器的配置更改,这将非常有用。请注意,例如,如果编辑器当前不可见,客户端仍然可以自由延迟代码信息指示器的重新计算。

客户端能力(Client capability):

  • 属性路径: workspace.codeLens
  • 属性类型: CodeLensWorkspaceClientCapabilities, 定义如下:
export interface CodeLensWorkspaceClientCapabilities {
	/**
	 * Whether the client implementation supports a refresh request sent from the
	 * server to the client.
	 *
	 * Note that this event is global and will force the client to refresh all
	 * code lenses currently shown. It should be used with absolute care and is
	 * useful for situation where a server for example detect a project wide
	 * change that requires such a calculation.
	 */
	refreshSupport?: boolean;
}

请求(Request):

  • method: "workspace/codeLens/refresh"
  • params: none

响应(Response):

  • result: void
  • error: codemessage,以防在请求期间发生异常。

Folding Range 请求

从版本 3.10.0 开始

折叠范围请求从客户端发送到服务器,以返回在给定文本文档中找到的所有折叠范围。

folding-range

客户端能力(Client capability):

  • 属性路径: textDocument.foldingRange
  • 属性类型: FoldingRangeClientCapabilities, 定义如下:
export interface FoldingRangeClientCapabilities {
	/**
	 * Whether implementation supports dynamic registration for folding range
	 * providers. If this is set to `true` the client supports the new
	 * `FoldingRangeRegistrationOptions` return value for the corresponding
	 * server capability as well.
	 */
	dynamicRegistration?: boolean;

	/**
	 * The maximum number of folding ranges that the client prefers to receive
	 * per document. The value serves as a hint, servers are free to follow the
	 * limit.
	 */
	rangeLimit?: uinteger;

	/**
	 * If set, the client signals that it only supports folding complete lines.
	 * If set, client will ignore specified `startCharacter` and `endCharacter`
	 * properties in a FoldingRange.
	 */
	lineFoldingOnly?: boolean;

	/**
	 * Specific options for the folding range kind.
	 *
	 * @since 3.17.0
	 */
	foldingRangeKind? : {
		/**
		 * The folding range kind values the client supports. When this
		 * property exists the client also guarantees that it will
		 * handle values outside its set gracefully and falls back
		 * to a default value when unknown.
		 */
		valueSet?: FoldingRangeKind[];
	};

	/**
	 * Specific options for the folding range.
	 * @since 3.17.0
	 */
	foldingRange?: {
		/**
		* If set, the client signals that it supports setting collapsedText on
		* folding ranges to display custom labels instead of the default text.
		*
		* @since 3.17.0
		*/
		collapsedText?: boolean;
	};
}

服务端能力(Server capability):

  • 属性路径: foldingRangeProvider
  • 属性类型: boolean | FoldingRangeOptions | FoldingRangeRegistrationOptions, 定义如下:
export interface FoldingRangeOptions extends WorkDoneProgressOptions {
}

注册选项(Registration Options): FoldingRangeRegistrationOptions, 定义如下:

export interface FoldingRangeRegistrationOptions extends
	TextDocumentRegistrationOptions, FoldingRangeOptions,
	StaticRegistrationOptions {
}

请求(Request):

  • method: "textDocument/foldingRange"
  • params: FoldingRangeParams, 定义如下:
export interface FoldingRangeParams extends WorkDoneProgressParams,
	PartialResultParams {
	/**
	 * The text document.
	 */
	textDocument: TextDocumentIdentifier;
}

响应(Response):

  • result: FoldingRange[] | null, 定义如下:
/**
 * Represents a folding range. To be valid, start and end line must be bigger
 * than zero and smaller than the number of lines in the document. Clients
 * are free to ignore invalid ranges.
 */
export interface FoldingRange {

	/**
	 * The zero-based start line of the range to fold. The folded area starts
	 * after the line's last character. To be valid, the end must be zero or
	 * larger and smaller than the number of lines in the document.
	 */
	startLine: uinteger;

	/**
	 * The zero-based character offset from where the folded range starts. If
	 * not defined, defaults to the length of the start line.
	 */
	startCharacter?: uinteger;

	/**
	 * The zero-based end line of the range to fold. The folded area ends with
	 * the line's last character. To be valid, the end must be zero or larger
	 * and smaller than the number of lines in the document.
	 */
	endLine: uinteger;

	/**
	 * The zero-based character offset before the folded range ends. If not
	 * defined, defaults to the length of the end line.
	 */
	endCharacter?: uinteger;

	/**
	 * Describes the kind of the folding range such as `comment` or `region`.
	 * The kind is used to categorize folding ranges and used by commands like
	 * 'Fold all comments'. See [FoldingRangeKind](#FoldingRangeKind) for an
	 * enumeration of standardized kinds.
	 */
	kind?: FoldingRangeKind;

	/**
	 * The text that the client should show when the specified range is
	 * collapsed. If not defined or not supported by the client, a default
	 * will be chosen by the client.
	 *
	 * @since 3.17.0 - proposed
	 */
	collapsedText?: string;
}
/**
 * A set of predefined range kinds.
 */
export namespace FoldingRangeKind {
	/**
	 * Folding range for a comment
	 */
	export const Comment = 'comment';

	/**
	 * Folding range for imports or includes
	 */
	export const Imports = 'imports';

	/**
	 * Folding range for a region (e.g. `#region`)
	 */
	export const Region = 'region';
}

/**
 * The type is a string since the value set is extensible
 */
export type FoldingRangeKind = string;
  • partial result: FoldingRange[]
  • error: codemessage,以防在请求期间发生异常。

Selection Range 请求

从版本 3.15.0 开始

选择范围请求从客户端发送到服务器,以返回给定位置数组的建议选择范围。选择范围是用户可能有兴趣选择的光标位置周围的范围。

返回数组中的选择范围用于同一索引处提供的参数中的位置。因此,positions[i] 必须包含在 result[i].range 中。为了允许某些位置具有选择范围而其他位置没有选择范围的结果,result[i].range 可以是位于 positions[i] 的空范围。

通常,选择范围对应于语法树的节点,但不是必需的。

selection-range

客户端能力(Client capability):

  • 属性路径: textDocument.selectionRange
  • 属性类型: SelectionRangeClientCapabilities, 定义如下:
export interface SelectionRangeClientCapabilities {
	/**
	 * Whether implementation supports dynamic registration for selection range
	 * providers. If this is set to `true` the client supports the new
	 * `SelectionRangeRegistrationOptions` return value for the corresponding
	 * server capability as well.
	 */
	dynamicRegistration?: boolean;
}

服务端能力(Server capability):

  • 属性路径: selectionRangeProvider
  • 属性类型: boolean | SelectionRangeOptions | SelectionRangeRegistrationOptions, 定义如下:
export interface SelectionRangeOptions extends WorkDoneProgressOptions {
}

注册选项(Registration Options): SelectionRangeRegistrationOptions, 定义如下:

export interface SelectionRangeRegistrationOptions extends
	SelectionRangeOptions, TextDocumentRegistrationOptions,
	StaticRegistrationOptions {
}

请求(Request):

  • method: "textDocument/selectionRange"
  • params: SelectionRangeParams, 定义如下:
export interface SelectionRangeParams extends WorkDoneProgressParams,
	PartialResultParams {
	/**
	 * The text document.
	 */
	textDocument: TextDocumentIdentifier;

	/**
	 * The positions inside the text document.
	 */
	positions: Position[];
}

响应(Response):

  • result: SelectionRange[] | null, 定义如下:
export interface SelectionRange {
	/**
	 * The [range](#Range) of this selection range.
	 */
	range: Range;
	/**
	 * The parent selection range containing this range. Therefore
	 * `parent.range` must contain `this.range`.
	 */
	parent?: SelectionRange;
}
  • partial result: SelectionRange[]
  • error: codemessage,以防在请求期间发生异常。

Document Symbols 请求

文档符号请求从客户端发送到服务器。返回的结果是下面两者之一:

  • SymbolInformation[] 是在给定文本文档中找到的所有符号的平面列表。然后,不应使用符号的位置范围和符号的容器名称来推断层次结构。

  • DocumentSymbol[] 是在给定文本文档中找到的符号层次结构。

服务器应尽可能返回 DocumentSymbol,因为它是更丰富的数据结构。

document-symbols

客户端能力(Client capability):

  • 属性路径: textDocument.documentSymbol
  • 属性类型: DocumentSymbolClientCapabilities, 定义如下:
export interface DocumentSymbolClientCapabilities {
	/**
	 * Whether document symbol supports dynamic registration.
	 */
	dynamicRegistration?: boolean;

	/**
	 * Specific capabilities for the `SymbolKind` in the
	 * `textDocument/documentSymbol` request.
	 */
	symbolKind?: {
		/**
		 * The symbol kind values the client supports. When this
		 * property exists the client also guarantees that it will
		 * handle values outside its set gracefully and falls back
		 * to a default value when unknown.
		 *
		 * If this property is not present the client only supports
		 * the symbol kinds from `File` to `Array` as defined in
		 * the initial version of the protocol.
		 */
		valueSet?: SymbolKind[];
	};

	/**
	 * The client supports hierarchical document symbols.
	 */
	hierarchicalDocumentSymbolSupport?: boolean;

	/**
	 * The client supports tags on `SymbolInformation`. Tags are supported on
	 * `DocumentSymbol` if `hierarchicalDocumentSymbolSupport` is set to true.
	 * Clients supporting tags have to handle unknown tags gracefully.
	 *
	 * @since 3.16.0
	 */
	tagSupport?: {
		/**
		 * The tags supported by the client.
		 */
		valueSet: SymbolTag[];
	};

	/**
	 * The client supports an additional label presented in the UI when
	 * registering a document symbol provider.
	 *
	 * @since 3.16.0
	 */
	labelSupport?: boolean;
}

服务端能力(Server capability):

  • 属性路径: documentSymbolProvider
  • 属性类型: boolean | DocumentSymbolOptions, 定义如下:
export interface DocumentSymbolOptions extends WorkDoneProgressOptions {
	/**
	 * A human-readable string that is shown when multiple outlines trees
	 * are shown for the same document.
	 *
	 * @since 3.16.0
	 */
	label?: string;
}

注册选项(Registration Options): DocumentSymbolRegistrationOptions, 定义如下:

export interface DocumentSymbolRegistrationOptions extends
	TextDocumentRegistrationOptions, DocumentSymbolOptions {
}

请求(Request):

  • method: "textDocument/documentSymbol"
  • params: DocumentSymbolParams, 定义如下:
export interface DocumentSymbolParams extends WorkDoneProgressParams,
	PartialResultParams {
	/**
	 * The text document.
	 */
	textDocument: TextDocumentIdentifier;
}

响应(Response):

  • result: DocumentSymbol[] | SymbolInformation[] | null, 定义如下:
/**
 * Represents programming constructs like variables, classes, interfaces etc.
 * that appear in a document. Document symbols can be hierarchical and they
 * have two ranges: one that encloses its definition and one that points to its
 * most interesting range, e.g. the range of an identifier.
 */
export interface DocumentSymbol {

	/**
	 * The name of this symbol. Will be displayed in the user interface and
	 * therefore must not be an empty string or a string only consisting of
	 * white spaces.
	 */
	name: string;

	/**
	 * More detail for this symbol, e.g the signature of a function.
	 */
	detail?: string;

	/**
	 * The kind of this symbol.
	 */
	kind: SymbolKind;

	/**
	 * Tags for this document symbol.
	 *
	 * @since 3.16.0
	 */
	tags?: SymbolTag[];

	/**
	 * Indicates if this symbol is deprecated.
	 *
	 * @deprecated Use tags instead
	 */
	deprecated?: boolean;

	/**
	 * The range enclosing this symbol not including leading/trailing whitespace
	 * but everything else like comments. This information is typically used to
	 * determine if the clients cursor is inside the symbol to reveal in the
	 * symbol in the UI.
	 */
	range: Range;

	/**
	 * The range that should be selected and revealed when this symbol is being
	 * picked, e.g. the name of a function. Must be contained by the `range`.
	 */
	selectionRange: Range;

	/**
	 * Children of this symbol, e.g. properties of a class.
	 */
	children?: DocumentSymbol[];
}
/**
 * Represents information about programming constructs like variables, classes,
 * interfaces etc.
 *
 * @deprecated use DocumentSymbol or WorkspaceSymbol instead.
 */
export interface SymbolInformation {
	/**
	 * The name of this symbol.
	 */
	name: string;

	/**
	 * The kind of this symbol.
	 */
	kind: SymbolKind;

	/**
	 * Tags for this symbol.
	 *
	 * @since 3.16.0
	 */
	tags?: SymbolTag[];

	/**
	 * Indicates if this symbol is deprecated.
	 *
	 * @deprecated Use tags instead
	 */
	deprecated?: boolean;

	/**
	 * The location of this symbol. The location's range is used by a tool
	 * to reveal the location in the editor. If the symbol is selected in the
	 * tool the range's start information is used to position the cursor. So
	 * the range usually spans more then the actual symbol's name and does
	 * normally include things like visibility modifiers.
	 *
	 * The range doesn't have to denote a node range in the sense of an abstract
	 * syntax tree. It can therefore not be used to re-construct a hierarchy of
	 * the symbols.
	 */
	location: Location;

	/**
	 * The name of the symbol containing this symbol. This information is for
	 * user interface purposes (e.g. to render a qualifier in the user interface
	 * if necessary). It can't be used to re-infer a hierarchy for the document
	 * symbols.
	 */
	containerName?: string;
}
/**
 * A symbol kind.
 */
export namespace SymbolKind {
	export const File = 1;
	export const Module = 2;
	export const Namespace = 3;
	export const Package = 4;
	export const Class = 5;
	export const Method = 6;
	export const Property = 7;
	export const Field = 8;
	export const Constructor = 9;
	export const Enum = 10;
	export const Interface = 11;
	export const Function = 12;
	export const Variable = 13;
	export const Constant = 14;
	export const String = 15;
	export const Number = 16;
	export const Boolean = 17;
	export const Array = 18;
	export const Object = 19;
	export const Key = 20;
	export const Null = 21;
	export const EnumMember = 22;
	export const Struct = 23;
	export const Event = 24;
	export const Operator = 25;
	export const TypeParameter = 26;
}

export type SymbolKind = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26;
/**
 * Symbol tags are extra annotations that tweak the rendering of a symbol.
 *
 * @since 3.16
 */
export namespace SymbolTag {

	/**
	 * Render a symbol as obsolete, usually using a strike-out.
	 */
	export const Deprecated: 1 = 1;
}

export type SymbolTag = 1;
  • partial result: DocumentSymbol[] | SymbolInformation[]DocumentSymbol[]SymbolInformation[] 不能混合使用。这意味着第一个块定义了所有其他块的类型。
  • error: codemessage,以防在请求期间发生异常。

Semantic Tokens

从版本 3.16.0 开始

请求从客户端发送到服务器,以解析给定文件的语义令牌。语义标记用于向依赖于语言特定符号信息的文件添加其他颜色信息。语义令牌请求通常会产生较大的结果。因此,该协议支持用数字对令牌进行编码。此外,还提供对增量的可选支持。

没有语义突出显示:

no-semantic-highlighting

使用语义突出显示:

with-semantic-highlighting

一般概念

令牌使用一种令牌类型和多个个令牌修饰符表示。令牌类型类似于 classfunction,令牌修饰符类似于 staticasync。该协议定义了一组令牌类型和修饰符,但允许客户端扩展这些标记类型和修饰符,并在相应的客户端功能中宣布它们支持的值。预定义的值为:

export enum SemanticTokenTypes {
	namespace = 'namespace',
	/**
	 * Represents a generic type. Acts as a fallback for types which
	 * can't be mapped to a specific type like class or enum.
	 */
	type = 'type',
	class = 'class',
	enum = 'enum',
	interface = 'interface',
	struct = 'struct',
	typeParameter = 'typeParameter',
	parameter = 'parameter',
	variable = 'variable',
	property = 'property',
	enumMember = 'enumMember',
	event = 'event',
	function = 'function',
	method = 'method',
	macro = 'macro',
	keyword = 'keyword',
	modifier = 'modifier',
	comment = 'comment',
	string = 'string',
	number = 'number',
	regexp = 'regexp',
	operator = 'operator',
	/**
	 * @since 3.17.0
	 */
	decorator = 'decorator'
}
export enum SemanticTokenModifiers {
	declaration = 'declaration',
	definition = 'definition',
	readonly = 'readonly',
	static = 'static',
	deprecated = 'deprecated',
	abstract = 'abstract',
	async = 'async',
	modification = 'modification',
	documentation = 'documentation',
	defaultLibrary = 'defaultLibrary'
}

该协议定义了一个额外的令牌格式功能,以允许将来扩展该格式。当前指定的唯一格式是 relative,表示使用相对位置描述标记(请参阅下面的标记的整数编码)。

export namespace TokenFormat {
	export const Relative: 'relative' = 'relative';
}

export type TokenFormat = 'relative';

令牌的整数编码

在功能级别上,类型和修饰符是使用字符串定义的。但是,真正的编码是使用数字进行的。因此,服务器需要让客户端知道它对哪些类型和修饰符使用了哪些数字。它们使用图例执行此操作,图例定义如下:

export interface SemanticTokensLegend {
	/**
	 * The token types a server uses.
	 */
	tokenTypes: string[];

	/**
	 * The token modifiers a server uses.
	 */
	tokenModifiers: string[];
}

令牌类型按索引查找,因此 tokenType 值为 1 表示 tokenTypes[1]。由于令牌类型可以有 n 个修饰符,因此可以使用位标志设置多个令牌修饰符,因此 tokenModifier 值 3 首先被视为二进制 0b00000011,这意味着 [tokenModifiers[0], tokenModifiers[1]],因为设置了bit位 0 和 1。

在文件中表示令牌的位置有多种方式。绝对位置或相对位置。标记格式相对的协议使用相对位置,因为在文件中进行编辑时,大多数标记彼此保持稳定。如果服务器支持增量,则可以简化增量的计算。因此,每个标记都使用 5 个整数表示。文件中的特定令牌 i 由以下数组索引组成:

  • 在索引 5*ideltaLine: 标记行号,相对于前一个标记
  • 在索引 5*i+1deltaStart: 标记开始字符,相对于前一个标记(相对于 0 或前一个标记的开始,如果它们在同一行上)
  • 在索引 5*i+2length: 令牌的长度。
  • 在索引 5*i+3tokenType:: 将在 SemanticTokensLegend.tokenTypes 中查找。我们目前要求 tokenType < 65536。
  • 在索引 5*i+4 - tokenModifiers:每个设置的位将在 SemanticTokensLegend.tokenModifiers 中查找

deltaStartlength 值必须使用客户端和服务器在初始化请求期间商定的编码进行编码(另请参阅 TextDocuments)。令牌是否可以跨多行由客户端功能 multilineTokenSupport 定义。如果不支持多行标记,并且标记长度使其超过行尾,则应将其视为令牌在行尾结束,并且不会换行到下一行。

客户端功能 overlappingTokenSupport 定义令牌是否可以相互重叠。

示例

让我们看一个具体的例子,它使用没有重叠的单行标记来编码数字数组中具有 3 个标记的文件。我们从绝对位置开始,演示如何轻松地将它们转换为相对位置:

{ line: 2, startChar:  5, length: 3, tokenType: "property",
	tokenModifiers: ["private", "static"]
},
{ line: 2, startChar: 10, length: 4, tokenType: "type", tokenModifiers: [] },
{ line: 5, startChar:  2, length: 7, tokenType: "class", tokenModifiers: [] }

首先,必须设计一个图例。此图例必须在注册时预先提供,并捕获所有可能的令牌类型和修饰符。对于示例,我们使用以下图例:

{
   tokenTypes: ['property', 'type', 'class'],
   tokenModifiers: ['private', 'static']
}

第一个转换步骤是使用图例将 tokenTypetokenModifiers 编码为整数。如前所述,token 类型是按索引查找的,因此 tokenType 值为 1 表示 tokenTypes[1]。可以使用位标志设置多个令牌修饰符,因此 tokenModifier3 首先被视为二进制 0b00000011,这意味着 [tokenModifiers[0]、tokenModifiers[1]],因为设置了bit位 0 和 1。使用此图例,令牌现在为:

{ line: 2, startChar:  5, length: 3, tokenType: 0, tokenModifiers: 3 },
{ line: 2, startChar: 10, length: 4, tokenType: 1, tokenModifiers: 0 },
{ line: 5, startChar:  2, length: 7, tokenType: 2, tokenModifiers: 0 }

下一步是表示相对于文件中前一个标记的每个标记。在本例中,第二个标记与第一个标记在同一行上,因此第二个标记的 startChar 是相对于第一个标记的 startChar 创建的,因此它将是 10 - 5。第三个令牌与第二个令牌位于不同的行上,因此不会更改第三个令牌的 startChar:

{ deltaLine: 2, deltaStartChar: 5, length: 3, tokenType: 0, tokenModifiers: 3 },
{ deltaLine: 0, deltaStartChar: 5, length: 4, tokenType: 1, tokenModifiers: 0 },
{ deltaLine: 3, deltaStartChar: 2, length: 7, tokenType: 2, tokenModifiers: 0 }

最后,最后一步是将令牌的 5 个字段中的每一个都内联到单个数组中,这是一种内存友好的表示形式:

// 1st token,  2nd token,  3rd token
[  2,5,3,0,3,  0,5,4,1,0,  3,2,7,2,0 ]

现在假设用户在文件开头键入一个新的空行,这会导致文件中出现以下标记:

{ line: 3, startChar:  5, length: 3, tokenType: "property",
	tokenModifiers: ["private", "static"]
},
{ line: 3, startChar: 10, length: 4, tokenType: "type", tokenModifiers: [] },
{ line: 6, startChar:  2, length: 7, tokenType: "class", tokenModifiers: [] }

运行与上述相同的转换将生成以下数字数组:

// 1st token,  2nd token,  3rd token
[  3,5,3,0,3,  0,5,4,1,0,  3,2,7,2,0]

现在,增量(delta) 体现在这些数字数组上,没有任何形式的表单去解释这些数字的含义。这类似于从服务器发送到客户端以修改文件内容的文本文档编辑。这些都是基于字符的,不会对字符的含义做出任何假设。因此,[ 2,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ] 可以使用编辑描述 { start: 0, deleteCount: 1, data: [3] } 转换为 [ 3,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0] ,它告诉客户端简单地将数组中的第一个数字(例如 2)替换为 3

语义标记编辑在概念上的行为类似于文档上的 TextEdit:如果编辑描述包含 n 个编辑,则所有 n 个编辑都基于数字数组的相同状态 Sm。他们会将数字数组从状态 Sm 移动到 Sm+1。应用编辑的客户端不得假定它们已排序。将它们应用于数字数组的一种简单算法是对编辑内容进行排序,然后从数字数组的后部到前部应用它们。

消息文档

客户端能力(Client capability):

  • 属性路径: textDocument.semanticTokens
  • 属性类型: SemanticTokensClientCapabilities, 定义如下:
interface SemanticTokensClientCapabilities {
	/**
	 * Whether implementation supports dynamic registration. If this is set to
	 * `true` the client supports the new `(TextDocumentRegistrationOptions &
	 * StaticRegistrationOptions)` return value for the corresponding server
	 * capability as well.
	 */
	dynamicRegistration?: boolean;

	/**
	 * Which requests the client supports and might send to the server
	 * depending on the server's capability. Please note that clients might not
	 * show semantic tokens or degrade some of the user experience if a range
	 * or full request is advertised by the client but not provided by the
	 * server. If for example the client capability `requests.full` and
	 * `request.range` are both set to true but the server only provides a
	 * range provider the client might not render a minimap correctly or might
	 * even decide to not show any semantic tokens at all.
	 */
	requests: {
		/**
		 * The client will send the `textDocument/semanticTokens/range` request
		 * if the server provides a corresponding handler.
		 */
		range?: boolean | {
		};

		/**
		 * The client will send the `textDocument/semanticTokens/full` request
		 * if the server provides a corresponding handler.
		 */
		full?: boolean | {
			/**
			 * The client will send the `textDocument/semanticTokens/full/delta`
			 * request if the server provides a corresponding handler.
			 */
			delta?: boolean;
		};
	};

	/**
	 * The token types that the client supports.
	 */
	tokenTypes: string[];

	/**
	 * The token modifiers that the client supports.
	 */
	tokenModifiers: string[];

	/**
	 * The formats the clients supports.
	 */
	formats: TokenFormat[];

	/**
	 * Whether the client supports tokens that can overlap each other.
	 */
	overlappingTokenSupport?: boolean;

	/**
	 * Whether the client supports tokens that can span multiple lines.
	 */
	multilineTokenSupport?: boolean;

	/**
	 * Whether the client allows the server to actively cancel a
	 * semantic token request, e.g. supports returning
	 * ErrorCodes.ServerCancelled. If a server does the client
	 * needs to retrigger the request.
	 *
	 * @since 3.17.0
	 */
	serverCancelSupport?: boolean;

	/**
	 * Whether the client uses semantic tokens to augment existing
	 * syntax tokens. If set to `true` client side created syntax
	 * tokens and semantic tokens are both used for colorization. If
	 * set to `false` the client only uses the returned semantic tokens
	 * for colorization.
	 *
	 * If the value is `undefined` then the client behavior is not
	 * specified.
	 *
	 * @since 3.17.0
	 */
	augmentsSyntaxTokens?: boolean;
}

服务端能力(Server capability):

  • 属性路径: semanticTokensProvider
  • 属性类型: SemanticTokensOptions | SemanticTokensRegistrationOptions, 定义如下:
export interface SemanticTokensOptions extends WorkDoneProgressOptions {
	/**
	 * The legend used by the server
	 */
	legend: SemanticTokensLegend;

	/**
	 * Server supports providing semantic tokens for a specific range
	 * of a document.
	 */
	range?: boolean | {
	};

	/**
	 * Server supports providing semantic tokens for a full document.
	 */
	full?: boolean | {
		/**
		 * The server supports deltas for full documents.
		 */
		delta?: boolean;
	};
}

注册选项(Registration Options): SemanticTokensRegistrationOptions, 定义如下:

export interface SemanticTokensRegistrationOptions extends
	TextDocumentRegistrationOptions, SemanticTokensOptions,
	StaticRegistrationOptions {
}

由于注册选项处理范围、完整和增量请求,因此用于注册语义令牌请求的方法是 textDocument/semanticTokens,而不是下面描述的特定方法之一。

请求整个文件的语义令牌

请求(Request):

  • method: "textDocument/semanticTokens/full"
  • params: SemanticTokensParams, 定义如下:
export interface SemanticTokensParams extends WorkDoneProgressParams,
	PartialResultParams {
	/**
	 * The text document.
	 */
	textDocument: TextDocumentIdentifier;
}

响应(Response):

  • result: SemanticTokens | null, 定义如下:
export interface SemanticTokens {
	/**
	 * An optional result id. If provided and clients support delta updating
	 * the client will include the result id in the next semantic token request.
	 * A server can then instead of computing all semantic tokens again simply
	 * send a delta.
	 */
	resultId?: string;

	/**
	 * The actual tokens.
	 */
	data: uinteger[];
}
  • partial result: SemanticTokensPartialResult, 定义如下:
export interface SemanticTokensPartialResult {
	data: uinteger[];
}
  • error: codemessage,以防在请求期间发生异常。

请求整个文件的语义令牌增量

请求(Request):

  • method: "textDocument/semanticTokens/full/delta"
  • params: SemanticTokensDeltaParams, 定义如下:
export interface SemanticTokensDeltaParams extends WorkDoneProgressParams,
	PartialResultParams {
	/**
	 * The text document.
	 */
	textDocument: TextDocumentIdentifier;

	/**
	 * The result id of a previous response. The result Id can either point to
	 * a full response or a delta response depending on what was received last.
	 */
	previousResultId: string;
}

响应(Response):

  • result: SemanticTokens | SemanticTokensDelta | null, 定义如下:
export interface SemanticTokensDelta {
	readonly resultId?: string;
	/**
	 * The semantic token edits to transform a previous result into a new
	 * result.
	 */
	edits: SemanticTokensEdit[];
}
export interface SemanticTokensEdit {
	/**
	 * The start offset of the edit.
	 */
	start: uinteger;

	/**
	 * The count of elements to remove.
	 */
	deleteCount: uinteger;

	/**
	 * The elements to insert.
	 */
	data?: uinteger[];
}
  • partial result: SemanticTokensDeltaPartialResult, 定义如下:
export interface SemanticTokensDeltaPartialResult {
	edits: SemanticTokensEdit[];
}
  • error: codemessage,以防在请求期间发生异常。

请求范围的语义令牌

在两种用例中,仅计算可见范围的语义标记可能会有所帮助:

  • 以便在用户打开文件时在用户界面中更快地呈现令牌。在此用例中,服务器还应该实现 textDocument/semanticTokens/full 请求,以允许小地图的闪烁自由滚动和语义着色。

  • 如果为完整文档计算语义标记的成本太高,服务器只能提供范围调用。在这种情况下,客户端可能无法正确呈现小地图,甚至可能决定根本不显示任何语义标记。

允许服务器计算比客户端请求的范围更广的语义令牌。但是,如果服务器这样做,则更广泛范围的语义标记必须完整且正确。

请求(Request):

  • method: "textDocument/semanticTokens/range"
  • params: SemanticTokensRangeParams, 定义如下:
export interface SemanticTokensRangeParams extends WorkDoneProgressParams,
	PartialResultParams {
	/**
	 * The text document.
	 */
	textDocument: TextDocumentIdentifier;

	/**
	 * The range the semantic tokens are requested for.
	 */
	range: Range;
}

响应(Response):

  • result: SemanticTokens | null
  • partial result: SemanticTokensPartialResult
  • error: codemessage,以防在请求期间发生异常。

请求刷新所有语义标记

workspace/semanticTokens/refresh 请求从服务器发送到客户端。服务器可以使用它来要求客户端刷新此服务器为其提供语义标记的编辑器。因此,客户端应要求服务器重新计算这些编辑器的语义标记。如果服务器检测到需要重新计算所有语义标记的项目范围的配置更改,这将非常有用。请注意,例如,如果编辑器当前不可见,客户端仍然可以自由延迟语义标记的重新计算。

客户端能力(Client capability):

  • 属性路径: workspace.semanticTokens
  • 属性类型: SemanticTokensWorkspaceClientCapabilities, 定义如下:
export interface SemanticTokensWorkspaceClientCapabilities {
	/**
	 * Whether the client implementation supports a refresh request sent from
	 * the server to the client.
	 *
	 * Note that this event is global and will force the client to refresh all
	 * semantic tokens currently shown. It should be used with absolute care
	 * and is useful for situation where a server for example detect a project
	 * wide change that requires such a calculation.
	 */
	refreshSupport?: boolean;
}

请求(Request):

  • method: "workspace/semanticTokens/refresh"
  • params: none

响应(Response):

  • result: none
  • error: codemessage,以防在请求期间发生异常。

Inlay Hint

Inlay Hint 请求

镶嵌提示请求从客户端发送到服务器,以计算给定 [text document, range] 元组的镶嵌提示,这些元组可以在编辑器中与其他文本一起呈现。

inlay-hint

客户端能力(Client capability):

  • 属性路径: textDocument.inlayHint
  • 属性类型: InlayHintClientCapabilities, 定义如下:
/**
 * Inlay hint client capabilities.
 *
 * @since 3.17.0
 */
export interface InlayHintClientCapabilities {

	/**
	 * Whether inlay hints support dynamic registration.
	 */
	dynamicRegistration?: boolean;

	/**
	 * Indicates which properties a client can resolve lazily on an inlay
	 * hint.
	 */
	resolveSupport?: {

		/**
		 * The properties that a client can resolve lazily.
		 */
		properties: string[];
	};
}

服务端能力(Server capability):

  • 属性路径: inlayHintProvider
  • 属性类型: InlayHintOptions, 定义如下:
/**
 * Inlay hint options used during static registration.
 *
 * @since 3.17.0
 */
export interface InlayHintOptions extends WorkDoneProgressOptions {
	/**
	 * The server provides support to resolve additional
	 * information for an inlay hint item.
	 */
	resolveProvider?: boolean;
}

注册选项(Registration Options): InlayHintRegistrationOptions, 定义如下:

/**
 * Inlay hint options used during static or dynamic registration.
 *
 * @since 3.17.0
 */
export interface InlayHintRegistrationOptions extends InlayHintOptions,
	TextDocumentRegistrationOptions, StaticRegistrationOptions {
}

请求(Request):

  • method: "textDocument/inlayHint"
  • params: InlayHintParams, 定义如下:
/**
 * A parameter literal used in inlay hint requests.
 *
 * @since 3.17.0
 */
export interface InlayHintParams extends WorkDoneProgressParams {
	/**
	 * The text document.
	 */
	textDocument: TextDocumentIdentifier;

	/**
	 * The visible document range for which inlay hints should be computed.
	 */
	range: Range;
}

响应(Response):

  • result: InlayHint[] | null, 定义如下:
/**
 * Inlay hint information.
 *
 * @since 3.17.0
 */
export interface InlayHint {

	/**
	 * The position of this hint.
	 * 
	 * If multiple hints have the same position, they will be shown in the order
	 * they appear in the response.
	 */
	position: Position;

	/**
	 * The label of this hint. A human readable string or an array of
	 * InlayHintLabelPart label parts.
	 *
	 * *Note* that neither the string nor the label part can be empty.
	 */
	label: string | InlayHintLabelPart[];

	/**
	 * The kind of this hint. Can be omitted in which case the client
	 * should fall back to a reasonable default.
	 */
	kind?: InlayHintKind;

	/**
	 * Optional text edits that are performed when accepting this inlay hint.
	 *
	 * *Note* that edits are expected to change the document so that the inlay
	 * hint (or its nearest variant) is now part of the document and the inlay
	 * hint itself is now obsolete.
	 *
	 * Depending on the client capability `inlayHint.resolveSupport` clients
	 * might resolve this property late using the resolve request.
	 */
	textEdits?: TextEdit[];

	/**
	 * The tooltip text when you hover over this item.
	 *
	 * Depending on the client capability `inlayHint.resolveSupport` clients
	 * might resolve this property late using the resolve request.
	 */
	tooltip?: string | MarkupContent;

	/**
	 * Render padding before the hint.
	 *
	 * Note: Padding should use the editor's background color, not the
	 * background color of the hint itself. That means padding can be used
	 * to visually align/separate an inlay hint.
	 */
	paddingLeft?: boolean;

	/**
	 * Render padding after the hint.
	 *
	 * Note: Padding should use the editor's background color, not the
	 * background color of the hint itself. That means padding can be used
	 * to visually align/separate an inlay hint.
	 */
	paddingRight?: boolean;


	/**
	 * A data entry field that is preserved on an inlay hint between
	 * a `textDocument/inlayHint` and a `inlayHint/resolve` request.
	 */
	data?: LSPAny;
}
/**
 * An inlay hint label part allows for interactive and composite labels
 * of inlay hints.
 *
 * @since 3.17.0
 */
export interface InlayHintLabelPart {

	/**
	 * The value of this label part.
	 */
	value: string;

	/**
	 * The tooltip text when you hover over this label part. Depending on
	 * the client capability `inlayHint.resolveSupport` clients might resolve
	 * this property late using the resolve request.
	 */
	tooltip?: string | MarkupContent;

	/**
	 * An optional source code location that represents this
	 * label part.
	 *
	 * The editor will use this location for the hover and for code navigation
	 * features: This part will become a clickable link that resolves to the
	 * definition of the symbol at the given location (not necessarily the
	 * location itself), it shows the hover that shows at the given location,
	 * and it shows a context menu with further code navigation commands.
	 *
	 * Depending on the client capability `inlayHint.resolveSupport` clients
	 * might resolve this property late using the resolve request.
	 */
	location?: Location;

	/**
	 * An optional command for this label part.
	 *
	 * Depending on the client capability `inlayHint.resolveSupport` clients
	 * might resolve this property late using the resolve request.
	 */
	command?: Command;
}
/**
 * Inlay hint kinds.
 *
 * @since 3.17.0
 */
export namespace InlayHintKind {

	/**
	 * An inlay hint that for a type annotation.
	 */
	export const Type = 1;

	/**
	 * An inlay hint that is for a parameter.
	 */
	export const Parameter = 2;
}

export type InlayHintKind = 1 | 2;
  • error: codemessage,以防在请求期间发生异常。

Inlay Hint Resolve 请求

从版本 3.17.0 开始

请求从客户端发送到服务器,以解析给定镶嵌提示的附加信息。这通常用于计算镶嵌提示的 label 部分的 tooltiplocationcommand 属性,以避免在 textDocument/inlayHint 请求期间进行不必要的计算。

假设客户端将 label.location 属性宣布为可以使用客户端能力延迟解析的属性:

textDocument.inlayHint.resolveSupport = { properties: ['label.location'] };

然后,需要使用 inlayHint/resolve 请求解析带有 没有 location 的 label 部分的镶嵌提示,然后才能使用。

客户端能力(Client capability):

  • 属性路径: textDocument.inlayHint.resolveSupport
  • 属性类型: { properties: string[]; }

请求(Request):

  • method: "inlayHint/resolve"
  • params: InlayHint

响应(Response):

  • result: InlayHint
  • error: codemessage,以防在请求期间发生异常。

Inlay Hint Refresh 请求

从版本 3.17.0 开始

请求从服务器发送到客户端。服务器可以使用它来要求客户端刷新当前显示在编辑器中的镶嵌提示。因此,客户端应要求服务器重新计算这些编辑器的镶嵌提示。如果服务器检测到需要重新计算所有镶嵌提示的配置更改,这将非常有用。请注意,例如,如果编辑器当前不可见,客户端仍然可以自由延迟镶嵌提示的重新计算。

客户端能力(Client capability):

  • 属性路径: workspace.inlayHint
  • 属性类型: InlayHintWorkspaceClientCapabilities, 定义如下:
/**
 * Client workspace capabilities specific to inlay hints.
 *
 * @since 3.17.0
 */
export interface InlayHintWorkspaceClientCapabilities {
	/**
	 * Whether the client implementation supports a refresh request sent from
	 * the server to the client.
	 *
	 * Note that this event is global and will force the client to refresh all
	 * inlay hints currently shown. It should be used with absolute care and
	 * is useful for situation where a server for example detects a project wide
	 * change that requires such a calculation.
	 */
	refreshSupport?: boolean;
}

请求(Request):

  • method: "workspace/inlayHint/refresh"
  • params: none

响应(Response):

  • result: void
  • error: codemessage,以防在请求期间发生异常。

Inline Value

Inline Value 请求

从版本 3.17.0 开始

内联值请求从客户端发送到服务器,以计算给定文本文档的内联值,该文本文档可能在编辑器中的行尾呈现。

inline-value

客户端能力(Client capability):

  • 属性路径: textDocument.inlineValue
  • 属性类型: InlineValueClientCapabilities, 定义如下:
/**
 * Client capabilities specific to inline values.
 *
 * @since 3.17.0
 */
export interface InlineValueClientCapabilities {
	/**
	 * Whether implementation supports dynamic registration for inline
	 * value providers.
	 */
	dynamicRegistration?: boolean;
}

服务端能力(Server capability):

  • 属性路径: inlineValueProvider
  • 属性类型: InlineValueOptions, 定义如下:
/**
 * Inline value options used during static registration.
 *
 * @since 3.17.0
 */
export interface InlineValueOptions extends WorkDoneProgressOptions {
}

注册选项(Registration Options): InlineValueRegistrationOptions, 定义如下:

/**
 * Inline value options used during static or dynamic registration.
 *
 * @since 3.17.0
 */
export interface InlineValueRegistrationOptions extends InlineValueOptions,
	TextDocumentRegistrationOptions, StaticRegistrationOptions {
}

请求(Request):

  • method: "textDocument/inlineValue"
  • params: InlineValueParams, 定义如下:
/**
 * A parameter literal used in inline value requests.
 *
 * @since 3.17.0
 */
export interface InlineValueParams extends WorkDoneProgressParams {
	/**
	 * The text document.
	 */
	textDocument: TextDocumentIdentifier;

	/**
	 * The document range for which inline values should be computed.
	 */
	range: Range;

	/**
	 * Additional information about the context in which inline values were
	 * requested.
	 */
	context: InlineValueContext;
}
/**
 * @since 3.17.0
 */
export interface InlineValueContext {
	/**
	 * The stack frame (as a DAP Id) where the execution has stopped.
	 */
	frameId: integer;

	/**
	 * The document range where execution has stopped.
	 * Typically the end position of the range denotes the line where the
	 * inline values are shown.
	 */
	stoppedLocation: Range;
}

响应(Response):

  • result: InlineValue[] | null, 定义如下:
/**
 * Inline value information can be provided by different means:
 * - directly as a text value (class InlineValueText).
 * - as a name to use for a variable lookup (class InlineValueVariableLookup)
 * - as an evaluatable expression (class InlineValueEvaluatableExpression)
 * The InlineValue types combines all inline value types into one type.
 *
 * @since 3.17.0
 */
export type InlineValue = InlineValueText | InlineValueVariableLookup
	| InlineValueEvaluatableExpression;
/**
 * Provide inline value as text.
 *
 * @since 3.17.0
 */
export interface InlineValueText {
	/**
	 * The document range for which the inline value applies.
	 */
	range: Range;

	/**
	 * The text of the inline value.
	 */
	text: string;
}
/**
 * Provide inline value through a variable lookup.
 *
 * If only a range is specified, the variable name will be extracted from
 * the underlying document.
 *
 * An optional variable name can be used to override the extracted name.
 *
 * @since 3.17.0
 */
export interface InlineValueVariableLookup {
	/**
	 * The document range for which the inline value applies.
	 * The range is used to extract the variable name from the underlying
	 * document.
	 */
	range: Range;

	/**
	 * If specified the name of the variable to look up.
	 */
	variableName?: string;

	/**
	 * How to perform the lookup.
	 */
	caseSensitiveLookup: boolean;
}
/**
 * Provide an inline value through an expression evaluation.
 *
 * If only a range is specified, the expression will be extracted from the
 * underlying document.
 *
 * An optional expression can be used to override the extracted expression.
 *
 * @since 3.17.0
 */
export interface InlineValueEvaluatableExpression {
	/**
	 * The document range for which the inline value applies.
	 * The range is used to extract the evaluatable expression from the
	 * underlying document.
	 */
	range: Range;

	/**
	 * If specified the expression overrides the extracted expression.
	 */
	expression?: string;
}

Inline Value Refresh 请求

从版本 3.17.0 开始

请求从服务器发送到客户端。服务器可以使用它来要求客户端刷新当前显示在编辑器中的内联值。因此,客户端应要求服务器重新计算这些编辑器的内联值。如果服务器检测到需要重新计算所有内联值的配置更改,这将非常有用。请注意,例如,如果编辑器当前不可见,客户端仍然可以自由延迟内联值的重新计算。

客户端能力(Client capability):

  • 属性路径: workspace.inlineValue
  • 属性类型: InlineValueWorkspaceClientCapabilities, 定义如下:
/**
 * Client workspace capabilities specific to inline values.
 *
 * @since 3.17.0
 */
export interface InlineValueWorkspaceClientCapabilities {
	/**
	 * Whether the client implementation supports a refresh request sent from
	 * the server to the client.
	 *
	 * Note that this event is global and will force the client to refresh all
	 * inline values currently shown. It should be used with absolute care and
	 * is useful for situation where a server for example detect a project wide
	 * change that requires such a calculation.
	 */
	refreshSupport?: boolean;
}

请求(Request):

  • method: "workspace/inlineValue/refresh"
  • params: none

响应(Response):

  • result: void
  • error: codemessage,以防在请求期间发生异常。

Monikers

从版本 3.16.0 开始

语言服务器索引格式(LSIF) 引入了 符号名称对象 的概念,以帮助跨不同索引关联符号。此请求为 LSP 服务器实现添加了功能,以便在给定文本文档位置的情况下提供相同的符号名称对象信息。客户端可以利用此方法获取用户正在编辑的文件的当前位置的名称,并在依赖于 LSIF 索引和链接符号的其他服务中执行进一步的代码导航查询。

请求从客户端发送到服务器,以获取给定文本文档位置的符号名称对象。将返回一个符号名称对象类型的数组作为响应,以指示给定位置可能的符合名称对象。如果无法计算符号名称对象,则应返回空数组或 null。

客户端能力(Client capability):

  • 属性路径: textDocument.moniker
  • 属性类型: MonikerClientCapabilities, 定义如下:
interface MonikerClientCapabilities {
	/**
	 * Whether implementation supports dynamic registration. If this is set to
	 * `true` the client supports the new `(TextDocumentRegistrationOptions &
	 * StaticRegistrationOptions)` return value for the corresponding server
	 * capability as well.
	 */
	dynamicRegistration?: boolean;
}

服务端能力(Server capability):

  • 属性路径: monikerProvider
  • 属性类型: boolean | MonikerOptions | MonikerRegistrationOptions, 定义如下:
export interface MonikerOptions extends WorkDoneProgressOptions {
}

注册选项(Registration Options): MonikerRegistrationOptions, 定义如下:

export interface MonikerRegistrationOptions extends
	TextDocumentRegistrationOptions, MonikerOptions {
}

请求(Request):

  • method: "textDocument/moniker"
  • params: MonikerParams, 定义如下:
export interface MonikerParams extends TextDocumentPositionParams,
	WorkDoneProgressParams, PartialResultParams {
}

响应(Response):

  • result: Moniker[] | null, 定义如下:
/**
 * Moniker definition to match LSIF 0.5 moniker definition.
 */
export interface Moniker {
	/**
	 * The scheme of the moniker. For example tsc or .Net
	 */
	scheme: string;

	/**
	 * The identifier of the moniker. The value is opaque in LSIF however
	 * schema owners are allowed to define the structure if they want.
	 */
	identifier: string;

	/**
	 * The scope in which the moniker is unique
	 */
	unique: UniquenessLevel;

	/**
	 * The moniker kind if known.
	 */
	kind?: MonikerKind;
}
/**
 * Moniker uniqueness level to define scope of the moniker.
 */
export enum UniquenessLevel {
	/**
	 * The moniker is only unique inside a document
	 */
	document = 'document',

	/**
	 * The moniker is unique inside a project for which a dump got created
	 */
	project = 'project',

	/**
	 * The moniker is unique inside the group to which a project belongs
	 */
	group = 'group',

	/**
	 * The moniker is unique inside the moniker scheme.
	 */
	scheme = 'scheme',

	/**
	 * The moniker is globally unique
	 */
	global = 'global'
}
/**
 * The moniker kind.
 */
export enum MonikerKind {
	/**
	 * The moniker represent a symbol that is imported into a project
	 */
	import = 'import',

	/**
	 * The moniker represents a symbol that is exported from a project
	 */
	export = 'export',

	/**
	 * The moniker represents a symbol that is local to a project (e.g. a local
	 * variable of a function, a class not visible outside the project, ...)
	 */
	local = 'local'
}
  • partial result: Moniker[]
  • error: codemessage,以防在请求期间发生异常。

提示:

此方法的服务器实现应确保名称对象计算与相应 LSIF 实现中使用的名称计算匹配,以确保符号可以在 IDE 会话和 LSIF 索引之间正确关联。

Completion

Completion 请求

完成请求从客户端发送到服务器,以计算给定光标位置的完成项。完成项显示在 IntelliSense 用户界面中。如果计算所以完成项的成本很高,服务器还可以为完成项解析请求提供处理程序(completionItem/resolve)。在用户界面中选择完成项时,将发送此请求。例如,一个典型的用例是:textDocument/completion 请求不会填充返回的完成项的 documentation 属性,因为它的计算成本很高。在用户界面中选择项目时,将发送completionItem/resolve 请求,并将选定的完成项目作为参数。返回的完成项应填写 documentation 属性。默认情况下,请求只能延迟详细信息和文档属性的计算。从 3.16.0 开始,客户端可以发出信号,表明它可以延迟解析更多属性。这是使用 completionItem.resolveSupport 客户端能力完成的,该能力列出了在 completionItem/resolve 请求期间可以填写的所有属性。所有其他属性(通常为 sortTextfilterTextinsertTexttextEdit)必须在 textDocument/completion 响应中提供,并且在解析期间不得更改。

completion

语言服务器协议围绕完成使用以下模型:

  • 为了实现跨语言的一致性并尊重不同的客户端,通常客户端负责过滤和排序。这也有一个优点,即客户可以尝试不同的过滤和排序模型。但是,服务器可以通过设置 filterText / sortText 来强制执行不同的行为

  • 对于速度,如果用户继续键入,客户端应该能够过滤已收到的完成列表。服务器可以使用将 CompletionList 标记为 isIncomplete 来选择退出此操作。

完成项提供了影响筛选和排序的其他方法。它们通过使用 insertTexttextEdit 创建 CompletionItem 来表示。这两种模式的区别如下:

  • 完成项提供插入 insertText / label,无需 textEdit: 在此模型中,客户端应使用语言的单词边界规则过滤用户已经输入的内容(例如,在光标位置下解析单词)。这种模式的原因是,它使服务器非常容易实现基本完成列表并在客户端上对其进行筛选。

  • 带有 textEdit 的完成项目: 在这种模式下,服务器告诉客户端它实际上知道它在做什么。如果创建在当前光标位置进行文本编辑的完成项,则不会进行单词猜测,也不应进行筛选。此模式可以与排序文本和筛选文本结合使用,以自定义两件事:如果文本编辑是替换编辑,则范围表示用于过滤的单词;如果替换更改了文本,则指定要使用的筛选器文本很可能是有意义的。

客户端能力(Client capability):

  • 属性路径: textDocument.completion
  • 属性类型: CompletionClientCapabilities, 定义如下:
export interface CompletionClientCapabilities {
	/**
	 * Whether completion supports dynamic registration.
	 */
	dynamicRegistration?: boolean;

	/**
	 * The client supports the following `CompletionItem` specific
	 * capabilities.
	 */
	completionItem?: {
		/**
		 * Client supports snippets as insert text.
		 *
		 * A snippet can define tab stops and placeholders with `$1`, `$2`
		 * and `${3:foo}`. `$0` defines the final tab stop, it defaults to
		 * the end of the snippet. Placeholders with equal identifiers are
		 * linked, that is typing in one will update others too.
		 */
		snippetSupport?: boolean;

		/**
		 * Client supports commit characters on a completion item.
		 */
		commitCharactersSupport?: boolean;

		/**
		 * Client supports the follow content formats for the documentation
		 * property. The order describes the preferred format of the client.
		 */
		documentationFormat?: MarkupKind[];

		/**
		 * Client supports the deprecated property on a completion item.
		 */
		deprecatedSupport?: boolean;

		/**
		 * Client supports the preselect property on a completion item.
		 */
		preselectSupport?: boolean;

		/**
		 * Client supports the tag property on a completion item. Clients
		 * supporting tags have to handle unknown tags gracefully. Clients
		 * especially need to preserve unknown tags when sending a completion
		 * item back to the server in a resolve call.
		 *
		 * @since 3.15.0
		 */
		tagSupport?: {
			/**
			 * The tags supported by the client.
			 */
			valueSet: CompletionItemTag[];
		};

		/**
		 * Client supports insert replace edit to control different behavior if
		 * a completion item is inserted in the text or should replace text.
		 *
		 * @since 3.16.0
		 */
		insertReplaceSupport?: boolean;

		/**
		 * Indicates which properties a client can resolve lazily on a
		 * completion item. Before version 3.16.0 only the predefined properties
		 * `documentation` and `detail` could be resolved lazily.
		 *
		 * @since 3.16.0
		 */
		resolveSupport?: {
			/**
			 * The properties that a client can resolve lazily.
			 */
			properties: string[];
		};

		/**
		 * The client supports the `insertTextMode` property on
		 * a completion item to override the whitespace handling mode
		 * as defined by the client (see `insertTextMode`).
		 *
		 * @since 3.16.0
		 */
		insertTextModeSupport?: {
			valueSet: InsertTextMode[];
		};

		/**
		 * The client has support for completion item label
		 * details (see also `CompletionItemLabelDetails`).
		 *
		 * @since 3.17.0
		 */
		labelDetailsSupport?: boolean;
	};

	completionItemKind?: {
		/**
		 * The completion item kind values the client supports. When this
		 * property exists the client also guarantees that it will
		 * handle values outside its set gracefully and falls back
		 * to a default value when unknown.
		 *
		 * If this property is not present the client only supports
		 * the completion items kinds from `Text` to `Reference` as defined in
		 * the initial version of the protocol.
		 */
		valueSet?: CompletionItemKind[];
	};

	/**
	 * The client supports to send additional context information for a
	 * `textDocument/completion` request.
	 */
	contextSupport?: boolean;

	/**
	 * The client's default when the completion item doesn't provide a
	 * `insertTextMode` property.
	 *
	 * @since 3.17.0
	 */
	insertTextMode?: InsertTextMode;

	/**
	 * The client supports the following `CompletionList` specific
	 * capabilities.
	 *
	 * @since 3.17.0
	 */
	completionList?: {
		/**
		 * The client supports the following itemDefaults on
		 * a completion list.
		 *
		 * The value lists the supported property names of the
		 * `CompletionList.itemDefaults` object. If omitted
		 * no properties are supported.
		 *
		 * @since 3.17.0
		 */
		itemDefaults?: string[];
	}
}

服务端能力(Server capability):

  • 属性路径: completionProvider
  • 属性类型: CompletionOptions, 定义如下:
/**
 * Completion options.
 */
export interface CompletionOptions extends WorkDoneProgressOptions {
	/**
	 * The additional characters, beyond the defaults provided by the client (typically
	 * [a-zA-Z]), that should automatically trigger a completion request. For example
	 * `.` in JavaScript represents the beginning of an object property or method and is
	 * thus a good candidate for triggering a completion request.
	 *
	 * Most tools trigger a completion request automatically without explicitly
	 * requesting it using a keyboard shortcut (e.g. Ctrl+Space). Typically they
	 * do so when the user starts to type an identifier. For example if the user
	 * types `c` in a JavaScript file code complete will automatically pop up
	 * present `console` besides others as a completion item. Characters that
	 * make up identifiers don't need to be listed here.
	 */
	triggerCharacters?: string[];

	/**
	 * The list of all possible characters that commit a completion. This field
	 * can be used if clients don't support individual commit characters per
	 * completion item. See client capability
	 * `completion.completionItem.commitCharactersSupport`.
	 *
	 * If a server provides both `allCommitCharacters` and commit characters on
	 * an individual completion item the ones on the completion item win.
	 *
	 * @since 3.2.0
	 */
	allCommitCharacters?: string[];

	/**
	 * The server provides support to resolve additional
	 * information for a completion item.
	 */
	resolveProvider?: boolean;

	/**
	 * The server supports the following `CompletionItem` specific
	 * capabilities.
	 *
	 * @since 3.17.0
	 */
	completionItem?: {
		/**
		 * The server has support for completion item label
		 * details (see also `CompletionItemLabelDetails`) when receiving
		 * a completion item in a resolve call.
		 *
		 * @since 3.17.0
		 */
		labelDetailsSupport?: boolean;
	}
}

注册选项(Registration Options): CompletionRegistrationOptions, 定义如下:

export interface CompletionRegistrationOptions
	extends TextDocumentRegistrationOptions, CompletionOptions {
}

请求(Request):

  • method: "textDocument/completion"
  • params: CompletionParams, 定义如下:
export interface CompletionParams extends TextDocumentPositionParams,
	WorkDoneProgressParams, PartialResultParams {
	/**
	 * The completion context. This is only available if the client specifies
	 * to send this using the client capability
	 * `completion.contextSupport === true`
	 */
	context?: CompletionContext;
}
/**
 * How a completion was triggered
 */
export namespace CompletionTriggerKind {
	/**
	 * Completion was triggered by typing an identifier (24x7 code
	 * complete), manual invocation (e.g Ctrl+Space) or via API.
	 */
	export const Invoked: 1 = 1;

	/**
	 * Completion was triggered by a trigger character specified by
	 * the `triggerCharacters` properties of the
	 * `CompletionRegistrationOptions`.
	 */
	export const TriggerCharacter: 2 = 2;

	/**
	 * Completion was re-triggered as the current completion list is incomplete.
	 */
	export const TriggerForIncompleteCompletions: 3 = 3;
}
export type CompletionTriggerKind = 1 | 2 | 3;
/**
 * Contains additional information about the context in which a completion
 * request is triggered.
 */
export interface CompletionContext {
	/**
	 * How the completion was triggered.
	 */
	triggerKind: CompletionTriggerKind;

	/**
	 * The trigger character (a single character) that has trigger code
	 * complete. Is undefined if
	 * `triggerKind !== CompletionTriggerKind.TriggerCharacter`
	 */
	triggerCharacter?: string;
}

响应(Response):

  • result: CompletionItem[] | CompletionList | null, 如果提供了 CompletionItem[],则将其解释为完成。所以它和 { isIncomplete: false, items } 是一样的
/**
 * Represents a collection of [completion items](#CompletionItem) to be
 * presented in the editor.
 */
export interface CompletionList {
	/**
	 * This list is not complete. Further typing should result in recomputing
	 * this list.
	 *
	 * Recomputed lists have all their items replaced (not appended) in the
	 * incomplete completion sessions.
	 */
	isIncomplete: boolean;

	/**
	 * In many cases the items of an actual completion result share the same
	 * value for properties like `commitCharacters` or the range of a text
	 * edit. A completion list can therefore define item defaults which will
	 * be used if a completion item itself doesn't specify the value.
	 *
	 * If a completion list specifies a default value and a completion item
	 * also specifies a corresponding value the one from the item is used.
	 *
	 * Servers are only allowed to return default values if the client
	 * signals support for this via the `completionList.itemDefaults`
	 * capability.
	 *
	 * @since 3.17.0
	 */
	itemDefaults?: {
		/**
		 * A default commit character set.
		 *
		 * @since 3.17.0
		 */
		commitCharacters?: string[];

		/**
		 * A default edit range
		 *
		 * @since 3.17.0
		 */
		editRange?: Range | {
			insert: Range;
			replace: Range;
		};

		/**
		 * A default insert text format
		 *
		 * @since 3.17.0
		 */
		insertTextFormat?: InsertTextFormat;

		/**
		 * A default insert text mode
		 *
		 * @since 3.17.0
		 */
		insertTextMode?: InsertTextMode;

		/**
		 * A default data value.
		 *
		 * @since 3.17.0
		 */
		data?: LSPAny;
	}

	/**
	 * The completion items.
	 */
	items: CompletionItem[];
}
/**
 * Defines whether the insert text in a completion item should be interpreted as
 * plain text or a snippet.
 */
export namespace InsertTextFormat {
	/**
	 * The primary text to be inserted is treated as a plain string.
	 */
	export const PlainText = 1;

	/**
	 * The primary text to be inserted is treated as a snippet.
	 *
	 * A snippet can define tab stops and placeholders with `$1`, `$2`
	 * and `${3:foo}`. `$0` defines the final tab stop, it defaults to
	 * the end of the snippet. Placeholders with equal identifiers are linked,
	 * that is typing in one will update others too.
	 */
	export const Snippet = 2;
}

export type InsertTextFormat = 1 | 2;
/**
 * Completion item tags are extra annotations that tweak the rendering of a
 * completion item.
 *
 * @since 3.15.0
 */
export namespace CompletionItemTag {
	/**
	 * Render a completion as obsolete, usually using a strike-out.
	 */
	export const Deprecated = 1;
}

export type CompletionItemTag = 1;
/**
 * A special text edit to provide an insert and a replace operation.
 *
 * @since 3.16.0
 */
export interface InsertReplaceEdit {
	/**
	 * The string to be inserted.
	 */
	newText: string;

	/**
	 * The range if the insert is requested
	 */
	insert: Range;

	/**
	 * The range if the replace is requested.
	 */
	replace: Range;
}
/**
 * How whitespace and indentation is handled during completion
 * item insertion.
 *
 * @since 3.16.0
 */
export namespace InsertTextMode {
	/**
	 * The insertion or replace strings is taken as it is. If the
	 * value is multi line the lines below the cursor will be
	 * inserted using the indentation defined in the string value.
	 * The client will not apply any kind of adjustments to the
	 * string.
	 */
	export const asIs: 1 = 1;

	/**
	 * The editor adjusts leading whitespace of new lines so that
	 * they match the indentation up to the cursor of the line for
	 * which the item is accepted.
	 *
	 * Consider a line like this: <2tabs><cursor><3tabs>foo. Accepting a
	 * multi line completion item is indented using 2 tabs and all
	 * following lines inserted will be indented using 2 tabs as well.
	 */
	export const adjustIndentation: 2 = 2;
}

export type InsertTextMode = 1 | 2;
/**
 * Additional details for a completion item label.
 *
 * @since 3.17.0
 */
export interface CompletionItemLabelDetails {

	/**
	 * An optional string which is rendered less prominently directly after
	 * {@link CompletionItem.label label}, without any spacing. Should be
	 * used for function signatures or type annotations.
	 */
	detail?: string;

	/**
	 * An optional string which is rendered less prominently after
	 * {@link CompletionItemLabelDetails.detail}. Should be used for fully qualified
	 * names or file path.
	 */
	description?: string;
}
export interface CompletionItem {

	/**
	 * The label of this completion item.
	 *
	 * The label property is also by default the text that
	 * is inserted when selecting this completion.
	 *
	 * If label details are provided the label itself should
	 * be an unqualified name of the completion item.
	 */
	label: string;

	/**
	 * Additional details for the label
	 *
	 * @since 3.17.0
	 */
	labelDetails?: CompletionItemLabelDetails;


	/**
	 * The kind of this completion item. Based of the kind
	 * an icon is chosen by the editor. The standardized set
	 * of available values is defined in `CompletionItemKind`.
	 */
	kind?: CompletionItemKind;

	/**
	 * Tags for this completion item.
	 *
	 * @since 3.15.0
	 */
	tags?: CompletionItemTag[];

	/**
	 * A human-readable string with additional information
	 * about this item, like type or symbol information.
	 */
	detail?: string;

	/**
	 * A human-readable string that represents a doc-comment.
	 */
	documentation?: string | MarkupContent;

	/**
	 * Indicates if this item is deprecated.
	 *
	 * @deprecated Use `tags` instead if supported.
	 */
	deprecated?: boolean;

	/**
	 * Select this item when showing.
	 *
	 * *Note* that only one completion item can be selected and that the
	 * tool / client decides which item that is. The rule is that the *first*
	 * item of those that match best is selected.
	 */
	preselect?: boolean;

	/**
	 * A string that should be used when comparing this item
	 * with other items. When omitted the label is used
	 * as the sort text for this item.
	 */
	sortText?: string;

	/**
	 * A string that should be used when filtering a set of
	 * completion items. When omitted the label is used as the
	 * filter text for this item.
	 */
	filterText?: string;

	/**
	 * A string that should be inserted into a document when selecting
	 * this completion. When omitted the label is used as the insert text
	 * for this item.
	 *
	 * The `insertText` is subject to interpretation by the client side.
	 * Some tools might not take the string literally. For example
	 * VS Code when code complete is requested in this example
	 * `con<cursor position>` and a completion item with an `insertText` of
	 * `console` is provided it will only insert `sole`. Therefore it is
	 * recommended to use `textEdit` instead since it avoids additional client
	 * side interpretation.
	 */
	insertText?: string;

	/**
	 * The format of the insert text. The format applies to both the
	 * `insertText` property and the `newText` property of a provided
	 * `textEdit`. If omitted defaults to `InsertTextFormat.PlainText`.
	 *
	 * Please note that the insertTextFormat doesn't apply to
	 * `additionalTextEdits`.
	 */
	insertTextFormat?: InsertTextFormat;

	/**
	 * How whitespace and indentation is handled during completion
	 * item insertion. If not provided the client's default value depends on
	 * the `textDocument.completion.insertTextMode` client capability.
	 *
	 * @since 3.16.0
	 * @since 3.17.0 - support for `textDocument.completion.insertTextMode`
	 */
	insertTextMode?: InsertTextMode;

	/**
	 * An edit which is applied to a document when selecting this completion.
	 * When an edit is provided the value of `insertText` is ignored.
	 *
	 * *Note:* The range of the edit must be a single line range and it must
	 * contain the position at which completion has been requested.
	 *
	 * Most editors support two different operations when accepting a completion
	 * item. One is to insert a completion text and the other is to replace an
	 * existing text with a completion text. Since this can usually not be
	 * predetermined by a server it can report both ranges. Clients need to
	 * signal support for `InsertReplaceEdit`s via the
	 * `textDocument.completion.completionItem.insertReplaceSupport` client
	 * capability property.
	 *
	 * *Note 1:* The text edit's range as well as both ranges from an insert
	 * replace edit must be a [single line] and they must contain the position
	 * at which completion has been requested.
	 * *Note 2:* If an `InsertReplaceEdit` is returned the edit's insert range
	 * must be a prefix of the edit's replace range, that means it must be
	 * contained and starting at the same position.
	 *
	 * @since 3.16.0 additional type `InsertReplaceEdit`
	 */
	textEdit?: TextEdit | InsertReplaceEdit;

	/**
	 * The edit text used if the completion item is part of a CompletionList and
	 * CompletionList defines an item default for the text edit range.
	 *
	 * Clients will only honor this property if they opt into completion list
	 * item defaults using the capability `completionList.itemDefaults`.
	 *
	 * If not provided and a list's default range is provided the label
	 * property is used as a text.
	 *
	 * @since 3.17.0
	 */
	textEditText?: string;

	/**
	 * An optional array of additional text edits that are applied when
	 * selecting this completion. Edits must not overlap (including the same
	 * insert position) with the main edit nor with themselves.
	 *
	 * Additional text edits should be used to change text unrelated to the
	 * current cursor position (for example adding an import statement at the
	 * top of the file if the completion item will insert an unqualified type).
	 */
	additionalTextEdits?: TextEdit[];

	/**
	 * An optional set of characters that when pressed while this completion is
	 * active will accept it first and then type that character. *Note* that all
	 * commit characters should have `length=1` and that superfluous characters
	 * will be ignored.
	 */
	commitCharacters?: string[];

	/**
	 * An optional command that is executed *after* inserting this completion.
	 * *Note* that additional modifications to the current document should be
	 * described with the additionalTextEdits-property.
	 */
	command?: Command;

	/**
	 * A data entry field that is preserved on a completion item between
	 * a completion and a completion resolve request.
	 */
	data?: LSPAny;
}
/**
 * The kind of a completion entry.
 */
export namespace CompletionItemKind {
	export const Text = 1;
	export const Method = 2;
	export const Function = 3;
	export const Constructor = 4;
	export const Field = 5;
	export const Variable = 6;
	export const Class = 7;
	export const Interface = 8;
	export const Module = 9;
	export const Property = 10;
	export const Unit = 11;
	export const Value = 12;
	export const Enum = 13;
	export const Keyword = 14;
	export const Snippet = 15;
	export const Color = 16;
	export const File = 17;
	export const Reference = 18;
	export const Folder = 19;
	export const EnumMember = 20;
	export const Constant = 21;
	export const Struct = 22;
	export const Event = 23;
	export const Operator = 24;
	export const TypeParameter = 25;
}

export type CompletionItemKind = 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25;
  • partial result: 部分结果:CompletionItem[]CompletionList 后跟 CompletionItem[]。如果提供的第一个结果项的类型为 CompletionList,则 CompletionItem[] 的后续部分结果将添加到 CompletionListitems 属性中。
  • error: codemessage,以防在请求期间发生异常。

完成项支持代码段(请参阅 InsertTextFormat.Snippet)。代码段格式如下:

Snippet Syntax(代码段语法)

代码段的 body 可以使用特殊构造来控制光标和插入的文本。以下是支持的功能及其语法:

Tab stops(制表位)

使用制表位,可以使编辑器光标在代码段内移动。使用 $1$2 指定光标位置。数字是访问制表位的顺序,而 $0 表示最终光标位置。多个制表位会同步链接和更新。

Placeholders(占位符)

占位符是带有值的制表位,例如 ${1:foo}。将插入并选择占位符文本,以便可以轻松更改。占位符可以嵌套,例如 ${1:another ${2:placeholder}}

Choice(选项)

占位符可以将选项作为值。语法是以逗号分隔的值枚举,用竖线字符括起来,例如 ${1|one,two,three|}。插入代码段并选择占位符后,选项将提示用户选择其中一个值。

Variables(变量)

使用 $name${name:default},您可以插入变量的值。如果未设置变量,则插入其默认值或空字符串。当变量未知(即未定义其名称)时,将插入变量的名称并将其转换为占位符。

可以使用以下变量:

  • TM_SELECTED_TEXT 当前选定的文本或空字符串
  • TM_CURRENT_LINE 当前行的内容
  • TM_CURRENT_WORD 光标串下的单词的内容或空字符
  • TM_LINE_INDEX 基于零索引的行号
  • TM_LINE_NUMBER 基于数字索引的行号
  • TM_FILENAME 当前文档的文件名
  • TM_FILENAME_BASE 当前文档的文件名(不带扩展名)
  • TM_DIRECTORY 当前文档的目录
  • TM_FILEPATH 当前文档的完整文件路径

Variable Transforms(变量转换)

转换允许您在插入变量之前修改变量的值。转换的定义由三个部分组成:

  1. 与变量的值匹配的正则表达式,或者在变量无法解析时与空字符串匹配。

  2. 一个“格式字符串”,允许从正则表达式引用匹配的组。格式字符串允许条件插入和简单修改。

  3. 传递给正则表达式的选项。

下面的示例插入当前文件的名称而不带其结尾,因此从 foo.txt 开始,它生成 foo

${TM_FILENAME/(.*)\..+$/$1/}
  |           |         | |
  |           |         | |-> no options
  |           |         |
  |           |         |-> references the contents of the first
  |           |             capture group
  |           |
  |           |-> regex to capture everything before
  |               the final `.suffix`
  |
  |-> resolves to the filename

Grammar(语法)

以下是片段的 EBNF(extended Backus-Naur form)。使用 \(反斜杠),可以转义 $}\。在选择元素中,反斜杠还转义了逗号和竖线字符。

any         ::= tabstop | placeholder | choice | variable | text
tabstop     ::= '$' int | '${' int '}'
placeholder ::= '${' int ':' any '}'
choice      ::= '${' int '|' text (',' text)* '|}'
variable    ::= '$' var | '${' var }'
                | '${' var ':' any '}'
                | '${' var '/' regex '/' (format | text)+ '/' options '}'
format      ::= '$' int | '${' int '}'
                | '${' int ':' '/upcase' | '/downcase' | '/capitalize' '}'
                | '${' int ':+' if '}'
                | '${' int ':?' if ':' else '}'
                | '${' int ':-' else '}' | '${' int ':' else '}'
regex       ::= Regular Expression value (ctor-string)
options     ::= Regular Expression option (ctor-options)
var         ::= [_a-zA-Z] [_a-zA-Z0-9]*
int         ::= [0-9]+
text        ::= .*
if			::= text
else		::= text

Completion Item Resolve 请求

请求从客户端发送到服务器,以解析给定完成项的其他信息。

请求(Request):

  • method: "completionItem/resolve"
  • params: CompletionItem

响应(Response):

  • result: CompletionItem
  • error: codemessage,以防在请求期间发生异常。

PublishDiagnostics 通知

诊断通知从服务器发送到客户端,以指示诊断运行的结果。

diagnostics

诊断由服务器“拥有”,因此服务器有责任在必要时清除它们。以下规则被 VS Code 服务器用于生成诊断:

  • 如果一种语言仅是单个文件(例如 HTML),则服务器会在关闭文件时清除诊断。请注意,打开/关闭事件不一定反映用户在用户界面中看到的内容。这些事件是所有权事件。因此,对于当前版本的规范,尽管文件在用户界面中不可见,但问题可能未清除,因为客户端尚未关闭文件。

  • 如果语言具有项目系统(例如 C#),则在文件关闭时不会清除诊断。打开项目时,将重新计算所有文件的所有诊断(或从缓存中读取)。

当文件发生更改时,服务器负责重新计算诊断并将其推送到客户端。如果计算集为空,则必须推送空数组以清除以前的诊断。新推送的诊断始终会替换以前推送的诊断。客户端不会发生合并。

客户端能力(Client capability):

  • 属性路径: textDocument.publishDiagnostics
  • 属性类型: PublishDiagnosticsClientCapabilities, 定义如下:
export interface PublishDiagnosticsClientCapabilities {
	/**
	 * Whether the clients accepts diagnostics with related information.
	 */
	relatedInformation?: boolean;

	/**
	 * Client supports the tag property to provide meta data about a diagnostic.
	 * Clients supporting tags have to handle unknown tags gracefully.
	 *
	 * @since 3.15.0
	 */
	tagSupport?: {
		/**
		 * The tags supported by the client.
		 */
		valueSet: DiagnosticTag[];
	};

	/**
	 * Whether the client interprets the version property of the
	 * `textDocument/publishDiagnostics` notification's parameter.
	 *
	 * @since 3.15.0
	 */
	versionSupport?: boolean;

	/**
	 * Client supports a codeDescription property
	 *
	 * @since 3.16.0
	 */
	codeDescriptionSupport?: boolean;

	/**
	 * Whether code action supports the `data` property which is
	 * preserved between a `textDocument/publishDiagnostics` and
	 * `textDocument/codeAction` request.
	 *
	 * @since 3.16.0
	 */
	dataSupport?: boolean;
}

通知(Notification):

  • method: "textDocument/publishDiagnostics"
  • params: PublishDiagnosticsParams, 定义如下:
interface PublishDiagnosticsParams {
	/**
	 * The URI for which diagnostic information is reported.
	 */
	uri: DocumentUri;

	/**
	 * Optional the version number of the document the diagnostics are published
	 * for.
	 *
	 * @since 3.15.0
	 */
	version?: integer;

	/**
	 * An array of diagnostic information items.
	 */
	diagnostics: Diagnostic[];
}

Pull Diagnostics

当前,服务器使用通知将诊断信息发布到客户端。此模型的优点是,对于工作区范围的诊断,服务器可以自由地在服务器首选的时间点计算它们。另一方面,这种方法的缺点是服务器无法对用户键入的文件或在编辑器中可见的文件进行计算。从 textDocument/didOpentextDocument/didChange 通知推断客户端的 UI 状态可能会导致误报,因为这些通知是所有权转移通知。

因此,该规范引入了诊断拉取请求的概念,使客户端能够更好地控制应为其计算诊断的文档以及在哪个时间点进行诊断。

客户端能力(Client capability):

  • 属性路径: textDocument.diagnostic
  • 属性类型: DiagnosticClientCapabilities, 定义如下:
/**
 * Client capabilities specific to diagnostic pull requests.
 *
 * @since 3.17.0
 */
export interface DiagnosticClientCapabilities {
	/**
	 * Whether implementation supports dynamic registration. If this is set to
	 * `true` the client supports the new
	 * `(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
	 * return value for the corresponding server capability as well.
	 */
	dynamicRegistration?: boolean;

	/**
	 * Whether the clients supports related documents for document diagnostic
	 * pulls.
	 */
	relatedDocumentSupport?: boolean;
}

服务端能力(Server capability):

  • 属性路径: diagnosticProvider
  • 属性类型: DiagnosticOptions, 定义如下:
/**
 * Diagnostic options.
 *
 * @since 3.17.0
 */
export interface DiagnosticOptions extends WorkDoneProgressOptions {
	/**
	 * An optional identifier under which the diagnostics are
	 * managed by the client.
	 */
	identifier?: string;

	/**
	 * Whether the language has inter file dependencies meaning that
	 * editing code in one file can result in a different diagnostic
	 * set in another file. Inter file dependencies are common for
	 * most programming languages and typically uncommon for linters.
	 */
	interFileDependencies: boolean;

	/**
	 * The server provides support for workspace diagnostics as well.
	 */
	workspaceDiagnostics: boolean;
}

注册选项(Registration Options): DiagnosticRegistrationOptions, 定义如下:

/**
 * Diagnostic registration options.
 *
 * @since 3.17.0
 */
export interface DiagnosticRegistrationOptions extends
	TextDocumentRegistrationOptions, DiagnosticOptions,
	StaticRegistrationOptions {
}

Document Diagnostics

文本文档诊断请求从客户端发送到服务器,以要求服务器计算给定文档的诊断。与其他拉取请求一样,系统要求服务器计算当前同步的文档版本的诊断。

请求(Request):

  • method: "textDocument/diagnostic"
  • params: DocumentDiagnosticParams, 定义如下:
/**
 * Parameters of the document diagnostic request.
 *
 * @since 3.17.0
 */
export interface DocumentDiagnosticParams extends WorkDoneProgressParams,
	PartialResultParams {
	/**
	 * The text document.
	 */
	textDocument: TextDocumentIdentifier;

	/**
	 * The additional identifier  provided during registration.
	 */
	identifier?: string;

	/**
	 * The result id of a previous response if provided.
	 */
	previousResultId?: string;
}

响应(Response):

  • result: DocumentDiagnosticReport, 定义如下:
/**
 * The result of a document diagnostic pull request. A report can
 * either be a full report containing all diagnostics for the
 * requested document or a unchanged report indicating that nothing
 * has changed in terms of diagnostics in comparison to the last
 * pull request.
 *
 * @since 3.17.0
 */
export type DocumentDiagnosticReport = RelatedFullDocumentDiagnosticReport
	| RelatedUnchangedDocumentDiagnosticReport;
/**
 * The document diagnostic report kinds.
 *
 * @since 3.17.0
 */
export namespace DocumentDiagnosticReportKind {
	/**
	 * A diagnostic report with a full
	 * set of problems.
	 */
	export const Full = 'full';

	/**
	 * A report indicating that the last
	 * returned report is still accurate.
	 */
	export const Unchanged = 'unchanged';
}

export type DocumentDiagnosticReportKind = 'full' | 'unchanged';
/**
 * A diagnostic report with a full set of problems.
 *
 * @since 3.17.0
 */
export interface FullDocumentDiagnosticReport {
	/**
	 * A full document diagnostic report.
	 */
	kind: DocumentDiagnosticReportKind.Full;

	/**
	 * An optional result id. If provided it will
	 * be sent on the next diagnostic request for the
	 * same document.
	 */
	resultId?: string;

	/**
	 * The actual items.
	 */
	items: Diagnostic[];
}
/**
 * A diagnostic report indicating that the last returned
 * report is still accurate.
 *
 * @since 3.17.0
 */
export interface UnchangedDocumentDiagnosticReport {
	/**
	 * A document diagnostic report indicating
	 * no changes to the last result. A server can
	 * only return `unchanged` if result ids are
	 * provided.
	 */
	kind: DocumentDiagnosticReportKind.Unchanged;

	/**
	 * A result id which will be sent on the next
	 * diagnostic request for the same document.
	 */
	resultId: string;
}
/**
 * A full diagnostic report with a set of related documents.
 *
 * @since 3.17.0
 */
export interface RelatedFullDocumentDiagnosticReport extends
	FullDocumentDiagnosticReport {
	/**
	 * Diagnostics of related documents. This information is useful
	 * in programming languages where code in a file A can generate
	 * diagnostics in a file B which A depends on. An example of
	 * such a language is C/C++ where marco definitions in a file
	 * a.cpp and result in errors in a header file b.hpp.
	 *
	 * @since 3.17.0
	 */
	relatedDocuments?: {
		[uri: string /** DocumentUri */]:
			FullDocumentDiagnosticReport | UnchangedDocumentDiagnosticReport;
	};
}
/**
 * An unchanged diagnostic report with a set of related documents.
 *
 * @since 3.17.0
 */
export interface RelatedUnchangedDocumentDiagnosticReport extends
	UnchangedDocumentDiagnosticReport {
	/**
	 * Diagnostics of related documents. This information is useful
	 * in programming languages where code in a file A can generate
	 * diagnostics in a file B which A depends on. An example of
	 * such a language is C/C++ where marco definitions in a file
	 * a.cpp and result in errors in a header file b.hpp.
	 *
	 * @since 3.17.0
	 */
	relatedDocuments?: {
		[uri: string /** DocumentUri */]:
			FullDocumentDiagnosticReport | UnchangedDocumentDiagnosticReport;
	};
}
  • partial result: 第一个文本发送需要是 DocumentDiagnosticReport,后跟 n 个 DocumentDiagnosticReportPartialResult 文本,定义如下:
/**
 * A partial result for a document diagnostic report.
 *
 * @since 3.17.0
 */
export interface DocumentDiagnosticReportPartialResult {
	relatedDocuments: {
		[uri: string /** DocumentUri */]:
			FullDocumentDiagnosticReport | UnchangedDocumentDiagnosticReport;
	};
}
  • error: codemessage,以防在请求期间发生异常。还允许服务器返回代码为 ServerCancelled 的错误,指示服务器现在无法计算结果。服务器可以返回 DiagnosticServerCancellationData 数据,以指示客户端是否应重新触发请求。如果未提供任何数据,则默认为 { retriggerRequest: true }
/**
 * Cancellation data returned from a diagnostic request.
 *
 * @since 3.17.0
 */
export interface DiagnosticServerCancellationData {
	retriggerRequest: boolean;
}

Workspace Diagnostics

工作区诊断请求从客户端发送到服务器,要求服务器计算工作区范围的诊断,以前从服务器推送到客户端。与文档诊断请求相比,工作区请求可以长时间运行,并且不绑定到特定的工作区或文档状态。如果客户端支持对工作区诊断拉取进行流式处理,则为同一文档 URI 多次提供文档诊断报告是合法的。最后一份报告将胜过以前的报告。

如果客户端收到工作区诊断请求中文档的诊断报告,而客户端还为其发出单个文档诊断拉取请求,则客户端需要决定哪个诊断胜出并应显示哪个诊断。通常:

  • 较高文档版本的诊断应胜过较低文档版本的诊断(例如,请注意文档版本正在稳步增加)

  • 来自文档拉取的诊断应胜过来自工作区拉取的诊断。

请求(Request):

  • method: "workspace/diagnostic’"
  • params: WorkspaceDiagnosticParams, 定义如下:
/**
 * Parameters of the workspace diagnostic request.
 *
 * @since 3.17.0
 */
export interface WorkspaceDiagnosticParams extends WorkDoneProgressParams,
	PartialResultParams {
	/**
	 * The additional identifier provided during registration.
	 */
	identifier?: string;

	/**
	 * The currently known diagnostic reports with their
	 * previous result ids.
	 */
	previousResultIds: PreviousResultId[];
}
/**
 * A previous result id in a workspace pull request.
 *
 * @since 3.17.0
 */
export interface PreviousResultId {
	/**
	 * The URI for which the client knows a
	 * result id.
	 */
	uri: DocumentUri;

	/**
	 * The value of the previous result id.
	 */
	value: string;
}

响应(Response):

  • result: WorkspaceDiagnosticReport, 定义如下:
/**
 * A workspace diagnostic report.
 *
 * @since 3.17.0
 */
export interface WorkspaceDiagnosticReport {
	items: WorkspaceDocumentDiagnosticReport[];
}
/**
 * A full document diagnostic report for a workspace diagnostic result.
 *
 * @since 3.17.0
 */
export interface WorkspaceFullDocumentDiagnosticReport extends
	FullDocumentDiagnosticReport {

	/**
	 * The URI for which diagnostic information is reported.
	 */
	uri: DocumentUri;

	/**
	 * The version number for which the diagnostics are reported.
	 * If the document is not marked as open `null` can be provided.
	 */
	version: integer | null;
}
/**
 * An unchanged document diagnostic report for a workspace diagnostic result.
 *
 * @since 3.17.0
 */
export interface WorkspaceUnchangedDocumentDiagnosticReport extends
	UnchangedDocumentDiagnosticReport {

	/**
	 * The URI for which diagnostic information is reported.
	 */
	uri: DocumentUri;

	/**
	 * The version number for which the diagnostics are reported.
	 * If the document is not marked as open `null` can be provided.
	 */
	version: integer | null;
}
/**
 * A workspace diagnostic document report.
 *
 * @since 3.17.0
 */
export type WorkspaceDocumentDiagnosticReport =
	WorkspaceFullDocumentDiagnosticReport
	| WorkspaceUnchangedDocumentDiagnosticReport;
  • partial result: 第一个文本发送需要是 WorkspaceDiagnosticReport,后跟 n 个 WorkspaceDiagnosticReportPartialResult 文本,定义如下:
/**
 * A partial result for a workspace diagnostic report.
 *
 * @since 3.17.0
 */
export interface WorkspaceDiagnosticReportPartialResult {
	items: WorkspaceDocumentDiagnosticReport[];
}
  • error: codemessage,以防在请求期间发生异常。还允许服务器返回错误,代码为 ServerCancelled,指示服务器现在无法计算结果。服务器可以返回 DiagnosticServerCancellationData 数据,以指示客户端是否应重新触发请求。如果未提供任何数据,则默认为 { retriggerRequest: true }

Diagnostics Refresh

请求从服务器发送到客户端。服务器可以使用它来要求客户端刷新所有需要的文档和工作区诊断。如果服务器检测到需要重新计算所有诊断的项目范围的配置更改,这将非常有用。

客户端能力(Client capability):

  • 属性路径: workspace.diagnostics
  • 属性类型: DiagnosticWorkspaceClientCapabilities, 定义如下:
/**
 * Workspace client capabilities specific to diagnostic pull requests.
 *
 * @since 3.17.0
 */
export interface DiagnosticWorkspaceClientCapabilities {
	/**
	 * Whether the client implementation supports a refresh request sent from
	 * the server to the client.
	 *
	 * Note that this event is global and will force the client to refresh all
	 * pulled diagnostics currently shown. It should be used with absolute care
	 * and is useful for situation where a server for example detects a project
	 * wide change that requires such a calculation.
	 */
	refreshSupport?: boolean;
}

请求(Request):

  • method: "workspace/diagnostic/refresh"
  • params: none

响应(Response):

  • result: void
  • error: codemessage,以防在请求期间发生异常。

实现注意事项

通常,语言服务器规范不会强制执行任何特定的客户端实现,因为这些实现通常取决于客户端 UI 的行为方式。但是,由于可以在文档和工作区级别提供诊断,因此以下是一些提示:

  • 客户端应主动拉取用户键入的文档。

  • 如果服务器发出文件间依赖关系的信号,客户端还应拉取可见文档以确保准确的诊断。但是,拉动的发生频率应该较低。

  • 如果服务器支持工作区拉取,则客户端还应拉取工作区诊断。建议客户端实现工作区拉取的部分结果进度,以允许服务器长时间保持请求打开状态。如果服务器关闭工作区诊断拉取请求,则客户端应重新触发该请求。

Signature Help 请求

签名帮助请求从客户端发送到服务器,以请求给定光标位置的签名信息。

当用户键入参数列表起始字符(通常是左括号)时,签名帮助(也称为 参数信息)在工具提示中显示方法的签名。 作为参数和参数分隔符(通常为逗号),工具提示将更新为以粗体显示下一个参数。

客户端能力(Client capability):

  • 属性路径: textDocument.signatureHelp
  • 属性类型: SignatureHelpClientCapabilities, 定义如下:
export interface SignatureHelpClientCapabilities {
	/**
	 * Whether signature help supports dynamic registration.
	 */
	dynamicRegistration?: boolean;

	/**
	 * The client supports the following `SignatureInformation`
	 * specific properties.
	 */
	signatureInformation?: {
		/**
		 * Client supports the follow content formats for the documentation
		 * property. The order describes the preferred format of the client.
		 */
		documentationFormat?: MarkupKind[];

		/**
		 * Client capabilities specific to parameter information.
		 */
		parameterInformation?: {
			/**
			 * The client supports processing label offsets instead of a
			 * simple label string.
			 *
			 * @since 3.14.0
			 */
			labelOffsetSupport?: boolean;
		};

		/**
		 * The client supports the `activeParameter` property on
		 * `SignatureInformation` literal.
		 *
		 * @since 3.16.0
		 */
		activeParameterSupport?: boolean;
	};

	/**
	 * The client supports to send additional context information for a
	 * `textDocument/signatureHelp` request. A client that opts into
	 * contextSupport will also support the `retriggerCharacters` on
	 * `SignatureHelpOptions`.
	 *
	 * @since 3.15.0
	 */
	contextSupport?: boolean;
}

服务端能力(Server capability):

  • 属性路径: signatureHelpProvider
  • 属性类型: SignatureHelpOptions, 定义如下:
export interface SignatureHelpOptions extends WorkDoneProgressOptions {
	/**
	 * The characters that trigger signature help
	 * automatically.
	 */
	triggerCharacters?: string[];

	/**
	 * List of characters that re-trigger signature help.
	 *
	 * These trigger characters are only active when signature help is already
	 * showing. All trigger characters are also counted as re-trigger
	 * characters.
	 *
	 * @since 3.15.0
	 */
	retriggerCharacters?: string[];
}

注册选项(Registration Options): SignatureHelpRegistrationOptions, 定义如下:

export interface SignatureHelpRegistrationOptions
	extends TextDocumentRegistrationOptions, SignatureHelpOptions {
}

请求(Request):

  • method: "textDocument/signatureHelp"
  • params: SignatureHelpParams, 定义如下:
export interface SignatureHelpParams extends TextDocumentPositionParams,
	WorkDoneProgressParams {
	/**
	 * The signature help context. This is only available if the client
	 * specifies to send this using the client capability
	 * `textDocument.signatureHelp.contextSupport === true`
	 *
	 * @since 3.15.0
	 */
	context?: SignatureHelpContext;
}
/**
 * Additional information about the context in which a signature help request
 * was triggered.
 *
 * @since 3.15.0
 */
export interface SignatureHelpContext {
	/**
	 * Action that caused signature help to be triggered.
	 */
	triggerKind: SignatureHelpTriggerKind;

	/**
	 * Character that caused signature help to be triggered.
	 *
	 * This is undefined when triggerKind !==
	 * SignatureHelpTriggerKind.TriggerCharacter
	 */
	triggerCharacter?: string;

	/**
	 * `true` if signature help was already showing when it was triggered.
	 *
	 * Retriggers occur when the signature help is already active and can be
	 * caused by actions such as typing a trigger character, a cursor move, or
	 * document content changes.
	 */
	isRetrigger: boolean;

	/**
	 * The currently active `SignatureHelp`.
	 *
	 * The `activeSignatureHelp` has its `SignatureHelp.activeSignature` field
	 * updated based on the user navigating through available signatures.
	 */
	activeSignatureHelp?: SignatureHelp;
}
/**
 * How a signature help was triggered.
 *
 * @since 3.15.0
 */
export namespace SignatureHelpTriggerKind {
	/**
	 * Signature help was invoked manually by the user or by a command.
	 */
	export const Invoked: 1 = 1;
	/**
	 * Signature help was triggered by a trigger character.
	 */
	export const TriggerCharacter: 2 = 2;
	/**
	 * Signature help was triggered by the cursor moving or by the document
	 * content changing.
	 */
	export const ContentChange: 3 = 3;
}
export type SignatureHelpTriggerKind = 1 | 2 | 3;

响应(Response):

  • result: SignatureHelp | null, 定义如下:
/**
 * Signature help represents the signature of something
 * callable. There can be multiple signature but only one
 * active and only one active parameter.
 */
export interface SignatureHelp {
	/**
	 * One or more signatures. If no signatures are available the signature help
	 * request should return `null`.
	 */
	signatures: SignatureInformation[];

	/**
	 * The active signature. If omitted or the value lies outside the
	 * range of `signatures` the value defaults to zero or is ignore if
	 * the `SignatureHelp` as no signatures.
	 *
	 * Whenever possible implementors should make an active decision about
	 * the active signature and shouldn't rely on a default value.
	 *
	 * In future version of the protocol this property might become
	 * mandatory to better express this.
	 */
	activeSignature?: uinteger;

	/**
	 * The active parameter of the active signature. If omitted or the value
	 * lies outside the range of `signatures[activeSignature].parameters`
	 * defaults to 0 if the active signature has parameters. If
	 * the active signature has no parameters it is ignored.
	 * In future version of the protocol this property might become
	 * mandatory to better express the active parameter if the
	 * active signature does have any.
	 */
	activeParameter?: uinteger;
}
/**
 * Represents the signature of something callable. A signature
 * can have a label, like a function-name, a doc-comment, and
 * a set of parameters.
 */
export interface SignatureInformation {
	/**
	 * The label of this signature. Will be shown in
	 * the UI.
	 */
	label: string;

	/**
	 * The human-readable doc-comment of this signature. Will be shown
	 * in the UI but can be omitted.
	 */
	documentation?: string | MarkupContent;

	/**
	 * The parameters of this signature.
	 */
	parameters?: ParameterInformation[];

	/**
	 * The index of the active parameter.
	 *
	 * If provided, this is used in place of `SignatureHelp.activeParameter`.
	 *
	 * @since 3.16.0
	 */
	activeParameter?: uinteger;
}
/**
 * Represents a parameter of a callable-signature. A parameter can
 * have a label and a doc-comment.
 */
export interface ParameterInformation {

	/**
	 * The label of this parameter information.
	 *
	 * Either a string or an inclusive start and exclusive end offsets within
	 * its containing signature label. (see SignatureInformation.label). The
	 * offsets are based on a UTF-16 string representation as `Position` and
	 * `Range` does.
	 *
	 * *Note*: a label of type string should be a substring of its containing
	 * signature label. Its intended use case is to highlight the parameter
	 * label part in the `SignatureInformation.label`.
	 */
	label: string | [uinteger, uinteger];

	/**
	 * The human-readable doc-comment of this parameter. Will be shown
	 * in the UI but can be omitted.
	 */
	documentation?: string | MarkupContent;
}
  • error: codemessage,以防在请求期间发生异常。

Code Action

Code Action 请求

代码操作请求从客户端发送到服务器,以计算给定文本文档和范围的命令。这些命令通常是代码修复,用于修复问题或美化/重构代码。textDocument/codeAction 请求的结果是命令文本数组,这些文本通常显示在用户界面中。若要确保服务器在许多客户端中有用,代码操作中指定的命令应由服务器处理,而不是由客户端处理(请参阅 workspace/executeCommandServerCapabilities.executeCommandProvider)。如果客户端支持通过代码操作提供编辑,则应使用该模式。

code-action

从版本 3.16.0 开始:客户端可以提供在 textDocument/codeAction 请求期间服务器延迟 Code Action 计算的属性:

这对于计算属性(例如编辑属性)的值成本高昂的情况非常有用。客户端通过 codeAction.resolveSupport 功能发出此信号,该功能列出了客户端可以延迟解析的所有属性。服务器功能 codeActionProvider.resolveProvider 表示服务器将提供 codeAction/resolve 路由。为了帮助服务器在解析请求中唯一标识代码操作,代码操作文本可以选择携带数据属性。这也由其他客户端功能 codeAction.dataSupport 守护。通常,如果客户端提供解析支持,则应提供数据支持。还应注意,服务器不应更改 codeAction/resolve 请求中代码操作的现有属性。

从版本 3.8.0 开始:支持 CodeAction 以启用以下方案:

  • 能够直接从代码操作请求返回工作区编辑。这样可以避免另一个服务器往返来执行实际的代码操作。但是,服务器提供商应该意识到,如果代码操作的计算成本很高,或者编辑量很大,那么如果结果只是一个命令,并且仅在需要时计算实际编辑,则仍然可能是有益的。

  • 使用 kind 对代码操作进行分组的能力。允许客户端忽略该信息。但是,它允许他们更好地将代码操作分组到相应的菜单中(例如,将所有重构代码操作都分组到重构菜单中)。

客户端需要通过相应的客户端功能 codeAction.codeActionLiteralSupport 声明其对代码操作文本(例如 CodeAction 类型)和 codeActionKind 的支持。

客户端能力(Client capability):

  • 属性路径: textDocument.codeAction
  • 属性类型: CodeActionClientCapabilities, 定义如下:
export interface CodeActionClientCapabilities {
	/**
	 * Whether code action supports dynamic registration.
	 */
	dynamicRegistration?: boolean;

	/**
	 * The client supports code action literals as a valid
	 * response of the `textDocument/codeAction` request.
	 *
	 * @since 3.8.0
	 */
	codeActionLiteralSupport?: {
		/**
		 * The code action kind is supported with the following value
		 * set.
		 */
		codeActionKind: {

			/**
			 * The code action kind values the client supports. When this
			 * property exists the client also guarantees that it will
			 * handle values outside its set gracefully and falls back
			 * to a default value when unknown.
			 */
			valueSet: CodeActionKind[];
		};
	};

	/**
	 * Whether code action supports the `isPreferred` property.
	 *
	 * @since 3.15.0
	 */
	isPreferredSupport?: boolean;

	/**
	 * Whether code action supports the `disabled` property.
	 *
	 * @since 3.16.0
	 */
	disabledSupport?: boolean;

	/**
	 * Whether code action supports the `data` property which is
	 * preserved between a `textDocument/codeAction` and a
	 * `codeAction/resolve` request.
	 *
	 * @since 3.16.0
	 */
	dataSupport?: boolean;


	/**
	 * Whether the client supports resolving additional code action
	 * properties via a separate `codeAction/resolve` request.
	 *
	 * @since 3.16.0
	 */
	resolveSupport?: {
		/**
		 * The properties that a client can resolve lazily.
		 */
		properties: string[];
	};

	/**
	 * Whether the client honors the change annotations in
	 * text edits and resource operations returned via the
	 * `CodeAction#edit` property by for example presenting
	 * the workspace edit in the user interface and asking
	 * for confirmation.
	 *
	 * @since 3.16.0
	 */
	honorsChangeAnnotations?: boolean;
}

服务端能力(Server capability):

  • 属性路径: codeActionProvider
  • 属性类型: boolean | CodeActionOptions, 定义如下:
export interface CodeActionOptions extends WorkDoneProgressOptions {
	/**
	 * CodeActionKinds that this server may return.
	 *
	 * The list of kinds may be generic, such as `CodeActionKind.Refactor`,
	 * or the server may list out every specific kind they provide.
	 */
	codeActionKinds?: CodeActionKind[];

	/**
	 * The server provides support to resolve additional
	 * information for a code action.
	 *
	 * @since 3.16.0
	 */
	resolveProvider?: boolean;
}

注册选项(Registration Options): CodeActionRegistrationOptions, 定义如下:

export interface CodeActionRegistrationOptions extends
	TextDocumentRegistrationOptions, CodeActionOptions {
}

请求(Request):

  • method: "textDocument/codeAction"
  • params: CodeActionParams, 定义如下:
/**
 * Params for the CodeActionRequest
 */
export interface CodeActionParams extends WorkDoneProgressParams,
	PartialResultParams {
	/**
	 * The document in which the command was invoked.
	 */
	textDocument: TextDocumentIdentifier;

	/**
	 * The range for which the command was invoked.
	 */
	range: Range;

	/**
	 * Context carrying additional information.
	 */
	context: CodeActionContext;
}
/**
 * The kind of a code action.
 *
 * Kinds are a hierarchical list of identifiers separated by `.`,
 * e.g. `"refactor.extract.function"`.
 *
 * The set of kinds is open and client needs to announce the kinds it supports
 * to the server during initialization.
 */
export type CodeActionKind = string;

/**
 * A set of predefined code action kinds.
 */
export namespace CodeActionKind {

	/**
	 * Empty kind.
	 */
	export const Empty: CodeActionKind = '';

	/**
	 * Base kind for quickfix actions: 'quickfix'.
	 */
	export const QuickFix: CodeActionKind = 'quickfix';

	/**
	 * Base kind for refactoring actions: 'refactor'.
	 */
	export const Refactor: CodeActionKind = 'refactor';

	/**
	 * Base kind for refactoring extraction actions: 'refactor.extract'.
	 *
	 * Example extract actions:
	 *
	 * - Extract method
	 * - Extract function
	 * - Extract variable
	 * - Extract interface from class
	 * - ...
	 */
	export const RefactorExtract: CodeActionKind = 'refactor.extract';

	/**
	 * Base kind for refactoring inline actions: 'refactor.inline'.
	 *
	 * Example inline actions:
	 *
	 * - Inline function
	 * - Inline variable
	 * - Inline constant
	 * - ...
	 */
	export const RefactorInline: CodeActionKind = 'refactor.inline';

	/**
	 * Base kind for refactoring rewrite actions: 'refactor.rewrite'.
	 *
	 * Example rewrite actions:
	 *
	 * - Convert JavaScript function to class
	 * - Add or remove parameter
	 * - Encapsulate field
	 * - Make method static
	 * - Move method to base class
	 * - ...
	 */
	export const RefactorRewrite: CodeActionKind = 'refactor.rewrite';

	/**
	 * Base kind for source actions: `source`.
	 *
	 * Source code actions apply to the entire file.
	 */
	export const Source: CodeActionKind = 'source';

	/**
	 * Base kind for an organize imports source action:
	 * `source.organizeImports`.
	 */
	export const SourceOrganizeImports: CodeActionKind =
		'source.organizeImports';

	/**
	 * Base kind for a 'fix all' source action: `source.fixAll`.
	 *
	 * 'Fix all' actions automatically fix errors that have a clear fix that
	 * do not require user input. They should not suppress errors or perform
	 * unsafe fixes such as generating new types or classes.
	 *
	 * @since 3.17.0
	 */
	export const SourceFixAll: CodeActionKind = 'source.fixAll';
}
/**
 * Contains additional diagnostic information about the context in which
 * a code action is run.
 */
export interface CodeActionContext {
	/**
	 * An array of diagnostics known on the client side overlapping the range
	 * provided to the `textDocument/codeAction` request. They are provided so
	 * that the server knows which errors are currently presented to the user
	 * for the given range. There is no guarantee that these accurately reflect
	 * the error state of the resource. The primary parameter
	 * to compute code actions is the provided range.
	 */
	diagnostics: Diagnostic[];

	/**
	 * Requested kind of actions to return.
	 *
	 * Actions not of this kind are filtered out by the client before being
	 * shown. So servers can omit computing them.
	 */
	only?: CodeActionKind[];

	/**
	 * The reason why code actions were requested.
	 *
	 * @since 3.17.0
	 */
	triggerKind?: CodeActionTriggerKind;
}
/**
 * The reason why code actions were requested.
 *
 * @since 3.17.0
 */
export namespace CodeActionTriggerKind {
	/**
	 * Code actions were explicitly requested by the user or by an extension.
	 */
	export const Invoked: 1 = 1;

	/**
	 * Code actions were requested automatically.
	 *
	 * This typically happens when current selection in a file changes, but can
	 * also be triggered when file content changes.
	 */
	export const Automatic: 2 = 2;
}

export type CodeActionTriggerKind = 1 | 2;

响应(Response):

  • result: (Command | CodeAction)[] | null, 定义如下:
/**
 * A code action represents a change that can be performed in code, e.g. to fix
 * a problem or to refactor code.
 *
 * A CodeAction must set either `edit` and/or a `command`. If both are supplied,
 * the `edit` is applied first, then the `command` is executed.
 */
export interface CodeAction {

	/**
	 * A short, human-readable, title for this code action.
	 */
	title: string;

	/**
	 * The kind of the code action.
	 *
	 * Used to filter code actions.
	 */
	kind?: CodeActionKind;

	/**
	 * The diagnostics that this code action resolves.
	 */
	diagnostics?: Diagnostic[];

	/**
	 * Marks this as a preferred action. Preferred actions are used by the
	 * `auto fix` command and can be targeted by keybindings.
	 *
	 * A quick fix should be marked preferred if it properly addresses the
	 * underlying error. A refactoring should be marked preferred if it is the
	 * most reasonable choice of actions to take.
	 *
	 * @since 3.15.0
	 */
	isPreferred?: boolean;

	/**
	 * Marks that the code action cannot currently be applied.
	 *
	 * Clients should follow the following guidelines regarding disabled code
	 * actions:
	 *
	 * - Disabled code actions are not shown in automatic lightbulbs code
	 *   action menus.
	 *
	 * - Disabled actions are shown as faded out in the code action menu when
	 *   the user request a more specific type of code action, such as
	 *   refactorings.
	 *
	 * - If the user has a keybinding that auto applies a code action and only
	 *   a disabled code actions are returned, the client should show the user
	 *   an error message with `reason` in the editor.
	 *
	 * @since 3.16.0
	 */
	disabled?: {

		/**
		 * Human readable description of why the code action is currently
		 * disabled.
		 *
		 * This is displayed in the code actions UI.
		 */
		reason: string;
	};

	/**
	 * The workspace edit this code action performs.
	 */
	edit?: WorkspaceEdit;

	/**
	 * A command this code action executes. If a code action
	 * provides an edit and a command, first the edit is
	 * executed and then the command.
	 */
	command?: Command;

	/**
	 * A data entry field that is preserved on a code action between
	 * a `textDocument/codeAction` and a `codeAction/resolve` request.
	 *
	 * @since 3.16.0
	 */
	data?: LSPAny;
}
  • partial result: (Command | CodeAction)[]
  • error: codemessage,以防在请求期间发生异常。

Code Action Resolve 请求

请求从客户端发送到服务器,以解析给定代码操作的其他信息。这通常用于计算代码操作的编辑属性,以避免在 textDocument/codeAction 请求期间进行不必要的计算。

假设客户端将 edit 属性宣布为可以使用客户端功能延迟解析的属性

textDocument.codeAction.resolveSupport = { properties: ['edit'] };

一个 CodeAction 是:

{
    "title": "Do Foo"
}

需要先使用 codeAction/resolve 请求进行解析,然后才能应用它。

客户端能力(Client capability):

  • 属性路径: textDocument.codeAction.resolveSupport
  • 属性类型: { properties: string[]; }

请求(Request):

  • method: "codeAction/resolve"
  • params: CodeAction

响应(Response):

  • result: CodeAction
  • error: codemessage,以防在请求期间发生异常。

Document Color

Document Color 请求

从版本 3.6.0 开始

文档颜色请求从客户端发送到服务器,以列出在给定文本文档中找到的所有颜色参考。与范围一起,返回 RGB 中的颜色值。

客户端可以使用结果在编辑器中修饰颜色参考。例如:

  • 在参照旁边显示实际颜色的颜色框

  • 编辑颜色参考时显示颜色选取器

document-color

客户端能力(Client capability):

  • 属性路径: textDocument.colorProvider
  • 属性类型: DocumentColorClientCapabilities, 定义如下:
export interface DocumentColorClientCapabilities {
	/**
	 * Whether document color supports dynamic registration.
	 */
	dynamicRegistration?: boolean;
}

服务端能力(Server capability):

  • 属性路径: colorProvider
  • 属性类型: boolean | DocumentColorOptions | DocumentColorRegistrationOptions, 定义如下:
export interface DocumentColorOptions extends WorkDoneProgressOptions {
}

注册选项(Registration Options): DocumentColorRegistrationOptions, 定义如下:

export interface DocumentColorRegistrationOptions extends
	TextDocumentRegistrationOptions, StaticRegistrationOptions,
	DocumentColorOptions {
}

请求(Request):

  • method: "textDocument/documentColor"
  • params: DocumentColorParams, 定义如下:
interface DocumentColorParams extends WorkDoneProgressParams,
	PartialResultParams {
	/**
	 * The text document.
	 */
	textDocument: TextDocumentIdentifier;
}

响应(Response):

  • result: ColorInformation[], 定义如下:
interface ColorInformation {
	/**
	 * The range in the document where this color appears.
	 */
	range: Range;

	/**
	 * The actual color value for this color range.
	 */
	color: Color;
}
/**
 * Represents a color in RGBA space.
 */
interface Color {

	/**
	 * The red component of this color in the range [0-1].
	 */
	readonly red: decimal;

	/**
	 * The green component of this color in the range [0-1].
	 */
	readonly green: decimal;

	/**
	 * The blue component of this color in the range [0-1].
	 */
	readonly blue: decimal;

	/**
	 * The alpha component of this color in the range [0-1].
	 */
	readonly alpha: decimal;
}
  • partial result: ColorInformation[]
  • error: codemessage,以防在请求期间发生异常。

Color Presentation 请求

从版本 3.6.0 开始

颜色表示请求从客户端发送到服务器,以获取给定位置颜色值的表示列表。客户端可以使用结果来

  • 修改颜色参考。
  • 在颜色选取器中显示,并允许用户选择其中一个演示文稿

此请求没有特殊功能和注册选项,因为它是作为 textDocument/documentColor 请求的解析请求发送的。

请求(Request):

  • method: "textDocument/colorPresentation"
  • params: ColorPresentationParams, 定义如下:
interface ColorPresentationParams extends WorkDoneProgressParams,
	PartialResultParams {
	/**
	 * The text document.
	 */
	textDocument: TextDocumentIdentifier;

	/**
	 * The color information to request presentations for.
	 */
	color: Color;

	/**
	 * The range where the color would be inserted. Serves as a context.
	 */
	range: Range;
}

响应(Response):

  • result: ColorPresentation[], 定义如下:
interface ColorPresentation {
	/**
	 * The label of this color presentation. It will be shown on the color
	 * picker header. By default this is also the text that is inserted when
	 * selecting this color presentation.
	 */
	label: string;
	/**
	 * An [edit](#TextEdit) which is applied to a document when selecting
	 * this presentation for the color. When omitted the
	 * [label](#ColorPresentation.label) is used.
	 */
	textEdit?: TextEdit;
	/**
	 * An optional array of additional [text edits](#TextEdit) that are applied
	 * when selecting this color presentation. Edits must not overlap with the
	 * main [edit](#ColorPresentation.textEdit) nor with themselves.
	 */
	additionalTextEdits?: TextEdit[];
}
  • partial result: ColorPresentation[]
  • error: codemessage,以防在请求期间发生异常。

Document Formatting

Document Formatting 请求

文档格式化请求从客户端发送到服务器,以格式化整个文档。

document-formatting

客户端能力(Client capability):

  • 属性路径: textDocument.formatting
  • 属性类型: DocumentFormattingClientCapabilities, 定义如下:
export interface DocumentFormattingClientCapabilities {
	/**
	 * Whether formatting supports dynamic registration.
	 */
	dynamicRegistration?: boolean;
}

服务端能力(Server capability):

  • 属性路径: documentFormattingProvider
  • 属性类型: boolean | DocumentFormattingOptions, 定义如下:
export interface DocumentFormattingOptions extends WorkDoneProgressOptions {
}

注册选项(Registration Options): DocumentFormattingRegistrationOptions, 定义如下:

export interface DocumentFormattingRegistrationOptions extends
	TextDocumentRegistrationOptions, DocumentFormattingOptions {
}

请求(Request):

  • method: "textDocument/formatting"
  • params: DocumentFormattingParams, 定义如下:
interface DocumentFormattingParams extends WorkDoneProgressParams {
	/**
	 * The document to format.
	 */
	textDocument: TextDocumentIdentifier;

	/**
	 * The format options.
	 */
	options: FormattingOptions;
}
/**
 * Value-object describing what options formatting should use.
 */
interface FormattingOptions {
	/**
	 * Size of a tab in spaces.
	 */
	tabSize: uinteger;

	/**
	 * Prefer spaces over tabs.
	 */
	insertSpaces: boolean;

	/**
	 * Trim trailing whitespace on a line.
	 *
	 * @since 3.15.0
	 */
	trimTrailingWhitespace?: boolean;

	/**
	 * Insert a newline character at the end of the file if one does not exist.
	 *
	 * @since 3.15.0
	 */
	insertFinalNewline?: boolean;

	/**
	 * Trim all newlines after the final newline at the end of the file.
	 *
	 * @since 3.15.0
	 */
	trimFinalNewlines?: boolean;

	/**
	 * Signature for further properties.
	 */
	[key: string]: boolean | integer | string;
}

响应(Response):

  • result: TextEdit[] | null, 描述对要格式化的文档的修改。
  • error: codemessage,以防在请求期间发生异常。

Document Range Formatting 请求

文档范围格式设置请求从客户端发送到服务器,以设置文档中给定范围的格式。

客户端能力(Client capability):

  • 属性路径: textDocument.rangeFormatting
  • 属性类型: DocumentRangeFormattingClientCapabilities, 定义如下:
export interface DocumentRangeFormattingClientCapabilities {
	/**
	 * Whether formatting supports dynamic registration.
	 */
	dynamicRegistration?: boolean;
}

服务端能力(Server capability):

  • 属性路径: documentRangeFormattingProvider
  • 属性类型: boolean | DocumentRangeFormattingOptions, 定义如下:
export interface DocumentRangeFormattingOptions extends
	WorkDoneProgressOptions {
}

注册选项(Registration Options): DocumentFormattingRegistrationOptions, 定义如下:

export interface DocumentRangeFormattingRegistrationOptions extends
	TextDocumentRegistrationOptions, DocumentRangeFormattingOptions {
}

请求(Request):

  • method: "textDocument/rangeFormatting"
  • params: DocumentRangeFormattingParams, 定义如下:
interface DocumentRangeFormattingParams extends WorkDoneProgressParams {
	/**
	 * The document to format.
	 */
	textDocument: TextDocumentIdentifier;

	/**
	 * The range to format
	 */
	range: Range;

	/**
	 * The format options
	 */
	options: FormattingOptions;
}

响应(Response):

  • result: TextEdit[] | null, 描述对要格式化的文档的修改。
  • error: codemessage,以防在请求期间发生异常。

Document on Type Formatting 请求

在键入过程中,文档格式化请求从客户端发送到服务器,以设置文档部分的格式。

客户端能力(Client capability):

  • 属性路径: textDocument.onTypeFormatting
  • 属性类型: DocumentOnTypeFormattingClientCapabilities, 定义如下:
export interface DocumentOnTypeFormattingClientCapabilities {
	/**
	 * Whether on type formatting supports dynamic registration.
	 */
	dynamicRegistration?: boolean;
}

服务端能力(Server capability):

  • 属性路径: documentOnTypeFormattingProvider
  • 属性类型: DocumentOnTypeFormattingOptions, 定义如下:
export interface DocumentOnTypeFormattingOptions {
	/**
	 * A character on which formatting should be triggered, like `{`.
	 */
	firstTriggerCharacter: string;

	/**
	 * More trigger characters.
	 */
	moreTriggerCharacter?: string[];
}

注册选项(Registration Options): DocumentOnTypeFormattingRegistrationOptions, 定义如下:

export interface DocumentOnTypeFormattingRegistrationOptions extends
	TextDocumentRegistrationOptions, DocumentOnTypeFormattingOptions {
}

请求(Request):

  • method: "textDocument/onTypeFormatting"
  • params: DocumentOnTypeFormattingParams, 定义如下:
interface DocumentOnTypeFormattingParams {

	/**
	 * The document to format.
	 */
	textDocument: TextDocumentIdentifier;

	/**
	 * The position around which the on type formatting should happen.
	 * This is not necessarily the exact position where the character denoted
	 * by the property `ch` got typed.
	 */
	position: Position;

	/**
	 * The character that has been typed that triggered the formatting
	 * on type request. That is not necessarily the last character that
	 * got inserted into the document since the client could auto insert
	 * characters as well (e.g. like automatic brace completion).
	 */
	ch: string;

	/**
	 * The formatting options.
	 */
	options: FormattingOptions;
}

响应(Response):

  • result: TextEdit[] | null
  • error: codemessage,以防在请求期间发生异常。

Rename

Rename 请求

重命名请求从客户端发送到服务器,以要求服务器计算工作区更改,以便客户端可以对符号执行工作区范围的重命名。

rename

客户端能力(Client capability):

  • 属性路径: textDocument.rename
  • 属性类型: RenameClientCapabilities, 定义如下:
export interface RenameClientCapabilities {
	/**
	 * Whether rename supports dynamic registration.
	 */
	dynamicRegistration?: boolean;

	/**
	 * Client supports testing for validity of rename operations
	 * before execution.
	 *
	 * @since version 3.12.0
	 */
	prepareSupport?: boolean;

	/**
	 * Client supports the default behavior result
	 * (`{ defaultBehavior: boolean }`).
	 *
	 * The value indicates the default behavior used by the
	 * client.
	 *
	 * @since version 3.16.0
	 */
	prepareSupportDefaultBehavior?: PrepareSupportDefaultBehavior;

	/**
	 * Whether the client honors the change annotations in
	 * text edits and resource operations returned via the
	 * rename request's workspace edit by for example presenting
	 * the workspace edit in the user interface and asking
	 * for confirmation.
	 *
	 * @since 3.16.0
	 */
	honorsChangeAnnotations?: boolean;
}
export namespace PrepareSupportDefaultBehavior {
	/**
	 * The client's default behavior is to select the identifier
	 * according to the language's syntax rule.
	 */
	 export const Identifier: 1 = 1;
}

export type PrepareSupportDefaultBehavior = 1;

服务端能力(Server capability):

  • 属性路径: renameProvider
  • 属性类型: boolean | RenameOptions, 定义如下:
export interface RenameOptions extends WorkDoneProgressOptions {
	/**
	 * Renames should be checked and tested before being executed.
	 */
	prepareProvider?: boolean;
}

注册选项(Registration Options): RenameRegistrationOptions, 定义如下:

export interface RenameRegistrationOptions extends
	TextDocumentRegistrationOptions, RenameOptions {
}

请求(Request):

  • method: "textDocument/rename"
  • params: RenameParams, 定义如下:
interface RenameParams extends TextDocumentPositionParams,
	WorkDoneProgressParams {
	/**
	 * The new name of the symbol. If the given name is not valid the
	 * request must return a [ResponseError](#ResponseError) with an
	 * appropriate message set.
	 */
	newName: string;
}

响应(Response):

  • result: WorkspaceEdit | null, 描述对工作区的修改。null 应与 WorkspaceEdit 同样对待,表示无需更改。
  • error: codemessage,以防因任何原因无法执行重命名。示例包括:给定位置没有任何东西可以重命名(如空格),给定符号不支持服务器重命名或代码无效(例如不编译)。

Prepare Rename 请求

从版本 3.12.0 开始

准备重命名请求从客户端发送到服务器,以在给定位置设置和测试重命名操作的有效性。

请求(Request):

  • method: "textDocument/prepareRename"
  • params: PrepareRenameParams, 定义如下:
export interface PrepareRenameParams extends TextDocumentPositionParams, WorkDoneProgressParams {
}

响应(Response):

  • result: Range | { range: Range, placeholder: string } | { defaultBehavior: boolean } | null, 描述要重命名的字符串的范围和要重命名的字符串内容的占位符文本(可选)。如果返回 { defaultBehavior: boolean }(从 3.16 开始),则重命名位置有效,客户端应使用其默认行为来计算重命名范围。如果返回 null,则认为 textDocument/rename 请求在给定位置无效。
  • error: codemessage,以防元素无法重命名。客户端应在其用户界面中显示该信息。

Linked Editing Range

从版本 3.16.0 开始

链接的编辑请求从客户端发送到服务器,以返回文档中给定位置的符号范围以及具有相同内容的所有范围。(可选)可以返回单词模式来描述有效内容。如果新内容有效,则可以将其中一个范围的重命名应用于所有其他范围。如果未提供特定于结果的词模式,则使用客户端语言配置中的字模式。

linked-editing-range

客户端能力(Client capability):

  • 属性路径: textDocument.linkedEditingRange
  • 属性类型: LinkedEditingRangeClientCapabilities, 定义如下:
export interface LinkedEditingRangeClientCapabilities {
	/**
	 * Whether the implementation supports dynamic registration.
	 * If this is set to `true` the client supports the new
	 * `(TextDocumentRegistrationOptions & StaticRegistrationOptions)`
	 * return value for the corresponding server capability as well.
	 */
	dynamicRegistration?: boolean;
}

服务端能力(Server capability):

  • 属性路径: linkedEditingRangeProvider
  • 属性类型: boolean | LinkedEditingRangeOptions | LinkedEditingRangeRegistrationOptions, 定义如下:
export interface LinkedEditingRangeOptions extends WorkDoneProgressOptions {
}

注册选项(Registration Options): LinkedEditingRangeRegistrationOptions, 定义如下:

export interface LinkedEditingRangeRegistrationOptions extends
	TextDocumentRegistrationOptions, LinkedEditingRangeOptions,
	StaticRegistrationOptions {
}

请求(Request):

  • method: "textDocument/linkedEditingRange"
  • params: LinkedEditingRangeParams, 定义如下:
export interface LinkedEditingRangeParams extends TextDocumentPositionParams,
	WorkDoneProgressParams {
}

响应(Response):

  • result: LinkedEditingRanges | null, 定义如下:
export interface LinkedEditingRanges {
	/**
	 * A list of ranges that can be renamed together. The ranges must have
	 * identical length and contain identical text content. The ranges cannot
	 * overlap.
	 */
	ranges: Range[];

	/**
	 * An optional word pattern (regular expression) that describes valid
	 * contents for the given ranges. If no pattern is provided, the client
	 * configuration's word pattern will be used.
	 */
	wordPattern?: string;
}
  • error: codemessage,以防在请求期间发生异常。

工作空间功能

Workspace Symbols

Workspace Symbols 请求

工作区符号请求从客户端发送到服务器,以列出与查询字符串匹配的项目范围符号。从 3.17.0 开始,服务器还可以为 workspaceSymbol/resolve 请求提供处理程序。这允许服务器返回工作区符号,而不带 workspace/symbol 请求范围。然后,客户端需要在必要时使用 workspaceSymbol/resolve 请求解析范围。只有当客户端通过 workspace.symbol.resolveSupport 功能通告支持此新模型时,服务器才能使用此新模型。

客户端能力(Client capability):

  • 属性路径: workspace.symbol
  • 属性类型: WorkspaceSymbolClientCapabilities, 定义如下:
interface WorkspaceSymbolClientCapabilities {
	/**
	 * Symbol request supports dynamic registration.
	 */
	dynamicRegistration?: boolean;

	/**
	 * Specific capabilities for the `SymbolKind` in the `workspace/symbol`
	 * request.
	 */
	symbolKind?: {
		/**
		 * The symbol kind values the client supports. When this
		 * property exists the client also guarantees that it will
		 * handle values outside its set gracefully and falls back
		 * to a default value when unknown.
		 *
		 * If this property is not present the client only supports
		 * the symbol kinds from `File` to `Array` as defined in
		 * the initial version of the protocol.
		 */
		valueSet?: SymbolKind[];
	};

	/**
	 * The client supports tags on `SymbolInformation` and `WorkspaceSymbol`.
	 * Clients supporting tags have to handle unknown tags gracefully.
	 *
	 * @since 3.16.0
	 */
	tagSupport?: {
		/**
		 * The tags supported by the client.
		 */
		valueSet: SymbolTag[];
	};

	/**
	 * The client support partial workspace symbols. The client will send the
	 * request `workspaceSymbol/resolve` to the server to resolve additional
	 * properties.
	 *
	 * @since 3.17.0 - proposedState
	 */
	resolveSupport?: {
		/**
		 * The properties that a client can resolve lazily. Usually
		 * `location.range`
		 */
		properties: string[];
	};
}

服务端能力(Server capability):

  • 属性路径: workspaceSymbolProvider
  • 属性类型: boolean | WorkspaceSymbolOptions, 定义如下:
export interface WorkspaceSymbolOptions extends WorkDoneProgressOptions {
	/**
	 * The server provides support to resolve additional
	 * information for a workspace symbol.
	 *
	 * @since 3.17.0
	 */
	resolveProvider?: boolean;
}

注册选项(Registration Options): WorkspaceSymbolRegistrationOptions, 定义如下:

export interface WorkspaceSymbolRegistrationOptions
	extends WorkspaceSymbolOptions {
}

请求(Request):

  • method: "workspace/symbol"
  • params: WorkspaceSymbolParams, 定义如下:
/**
 * The parameters of a Workspace Symbol Request.
 */
interface WorkspaceSymbolParams extends WorkDoneProgressParams,
	PartialResultParams {
	/**
	 * A query string to filter symbols by. Clients may send an empty
	 * string here to request all symbols.
	 */
	query: string;
}

响应(Response):

  • result: SymbolInformation[] | WorkspaceSymbol[] | null, 有关 SymbolInformation 的定义,请参阅上文。建议您使用新的 WorkspaceSymbol。但是,工作区符号是否可以返回没有范围的位置取决于客户端功能 workspace.symbol.resolveSupportWorkspaceSymbol,定义如下:
/**
 * A special workspace symbol that supports locations without a range
 *
 * @since 3.17.0
 */
export interface WorkspaceSymbol {
	/**
	 * The name of this symbol.
	 */
	name: string;

	/**
	 * The kind of this symbol.
	 */
	kind: SymbolKind;

	/**
	 * Tags for this completion item.
	 */
	tags?: SymbolTag[];

	/**
	 * The name of the symbol containing this symbol. This information is for
	 * user interface purposes (e.g. to render a qualifier in the user interface
	 * if necessary). It can't be used to re-infer a hierarchy for the document
	 * symbols.
	 */
	containerName?: string;

	/**
	 * The location of this symbol. Whether a server is allowed to
	 * return a location without a range depends on the client
	 * capability `workspace.symbol.resolveSupport`.
	 *
	 * See also `SymbolInformation.location`.
	 */
	location: Location | { uri: DocumentUri };

	/**
	 * A data entry field that is preserved on a workspace symbol between a
	 * workspace symbol request and a workspace symbol resolve request.
	 */
	data?: LSPAny;
}
  • partial result: SymbolInformation[] | WorkspaceSymbol[]
  • error: codemessage,以防在请求期间发生异常。

Workspace Symbol Resolve 请求

请求从客户端发送到服务器,以解析给定工作区符号的其他信息。

请求(Request):

  • method: "workspaceSymbol/resolve"
  • params: WorkspaceSymbol

响应(Response):

  • result: WorkspaceSymbol
  • error: codemessage,以防在请求期间发生异常。

Configuration

Configuration 请求

从版本 3.6.0 开始

请求从服务器发送到客户端,以从客户端获取配置设置。该请求可以在一次往返中获取多个配置设置。返回的配置设置的顺序对应于传递的 ConfigurationItems 的顺序(例如,响应中的第一项是参数中第一个配置项的结果)。

ConfigurationItem 由要请求的配置节和其他范围 URI 组成。请求的配置部分由服务器定义,不一定需要与客户端使用的配置存储相对应。因此,服务器可能会要求提供配置 cpp.formatterOptions,但客户端以不同的方式将配置存储在 XML 存储布局中。由客户端进行必要的转换。如果提供了作用域 URI,则客户端应返回作用域为所提供资源的设置。例如,如果客户端使用 EditorConfig 管理其设置,则应为传递的资源 URI 返回配置。如果客户端无法为给定范围提供配置设置,则返回的数组中需要存在 null

此拉取模型取代了旧的推送模型,客户端通过事件发出配置更改信号。如果服务器仍需要对配置更改做出反应(因为服务器缓存 workspace/configuration 请求的结果),则服务器应使用注册模式注册空配置更改。

客户端能力(Client capability):

  • 属性路径: workspace.configuration
  • 属性类型: boolean

请求(Request):

  • method: "workspace/configuration"
  • params: ConfigurationParams, 定义如下:
export interface ConfigurationParams {
	items: ConfigurationItem[];
}
export interface ConfigurationItem {
	/**
	 * The scope to get the configuration section for.
	 */
	scopeUri?: URI;

	/**
	 * The configuration section asked for.
	 */
	section?: string;
}

响应(Response):

  • result: LSPAny[]
  • error: codemessage,以防在请求期间发生异常。

DidChangeConfiguration 通知

从客户端发送到服务器的通知,以发出配置设置更改的信号。要让客户端发送此通知,则必须动态注册此通知。

客户端能力(Client capability):

  • 属性路径: workspace.didChangeConfiguration
  • 属性类型: DidChangeConfigurationClientCapabilities, 定义如下:
export interface DidChangeConfigurationClientCapabilities {
	/**
	 * Did change configuration notification supports dynamic registration.
	 *
	 * @since 3.6.0 to support the new pull model.
	 */
	dynamicRegistration?: boolean;
}

注册选项(Registration Options): undefined,因为只有动态注册了此通知,此通知才会被客户端发送,如果不注册此通知,那么服务器不应该缓存配置设置

通知(Notification):

  • method: "workspace/didChangeConfiguration"
  • params: DidChangeConfigurationParams, 定义如下:
interface DidChangeConfigurationParams {
	/**
	 * The actual changed settings
	 */
	settings: LSPAny;
}

Workspace folders

Workspace folders 请求

从版本 3.6.0 开始

许多工具支持每个工作区使用多个根文件夹。例如,VS Code 的多根支持、Atom 的项目文件夹支持或 Sublime 的项目支持。如果客户端工作区由多个根组成,则服务器通常需要了解这一点。到目前为止,该协议假定一个根文件夹,该文件夹由 InitializeParamsrootUri 属性向服务器宣布。如果客户端支持工作区文件夹,并通过相应的 workspaceFolders 客户端功能宣布它们,则 InitializeParams 在服务器启动时包含一个附加属性 workspaceFolders,其中包含配置的工作区文件夹。

请求从服务器发送到客户端,以获取当前打开的工作区文件夹列表。如果在工具中仅打开单个文件,则在响应中返回 null。如果工作区处于打开状态但未配置文件夹,则返回空数组。

客户端能力(Client capability):

  • 属性路径: workspace.workspaceFolders
  • 属性类型: boolean

服务端能力(Server capability):

  • 属性路径: workspace.workspaceFolders
  • 属性类型: WorkspaceFoldersServerCapabilities, 定义如下:
export interface WorkspaceFoldersServerCapabilities {
	/**
	 * The server has support for workspace folders
	 */
	supported?: boolean;

	/**
	 * Whether the server wants to receive workspace folder
	 * change notifications.
	 *
	 * If a string is provided, the string is treated as an ID
	 * under which the notification is registered on the client
	 * side. The ID can be used to unregister for these events
	 * using the `client/unregisterCapability` request.
	 */
	changeNotifications?: string | boolean;
}

请求(Request):

  • method: "workspace/workspaceFolders"
  • params: none

响应(Response):

  • result: WorkspaceFolder[] | null, 定义如下:
export interface WorkspaceFolder {
	/**
	 * The associated URI for this workspace folder.
	 */
	uri: URI;

	/**
	 * The name of the workspace folder. Used to refer to this
	 * workspace folder in the user interface.
	 */
	name: string;
}
  • error: codemessage,以防在请求期间发生异常。

DidChangeWorkspaceFolders 通知

从版本 3.6.0 开始

workspace/didChangeWorkspaceFolders 通知从客户端发送到服务器,以通知服务器有关工作区文件夹配置更改的信息。

服务端能力(Server capability):

  • 属性路径: workspace.workspaceFolders.changeNotifications
  • 属性类型: string | boolean, 如果提供了字符串,则该字符串将被视为在客户端注册通知的 ID。该 ID 可用于通过 client/unregisterCapability 请求取消注册这些事件。

注册选项(Registration Options): undefined

通知(Notification):

  • method: "workspace/didChangeWorkspaceFolders"
  • params: DidChangeWorkspaceFoldersParams, 定义如下:
export interface DidChangeWorkspaceFoldersParams {
	/**
	 * The actual workspace folder change event.
	 */
	event: WorkspaceFoldersChangeEvent;
}
/**
 * The workspace folder change event.
 */
export interface WorkspaceFoldersChangeEvent {
	/**
	 * The array of added workspace folders
	 */
	added: WorkspaceFolder[];

	/**
	 * The array of the removed workspace folders
	 */
	removed: WorkspaceFolder[];
}

Workspace fileOperations

WillCreateFiles 请求

在实际创建文件之前,将创建文件请求从客户端发送到服务器,只要创建是从客户端内部触发的,无论是通过用户操作还是应用工作区编辑。该请求可以返回一个 WorkspaceEdit,该 WorkspaceEdit 将在创建文件之前应用于工作区。因此,WorkspaceEdit 无法操作要创建的文件的内容。请注意,如果计算编辑时间过长或服务器在此请求上不断失败,客户端可能会丢弃结果。这样做是为了保持创建的快速和可靠。

客户端能力(Client capability):

  • 属性路径: workspace.fileOperations.willCreate
  • 属性类型: boolean

服务端能力(Server capability):

  • 属性路径: workspace.fileOperations.willCreate
  • 属性类型: FileOperationRegistrationOptions, 定义如下:
/**
 * The options to register for file operations.
 *
 * @since 3.16.0
 */
interface FileOperationRegistrationOptions {
	/**
	 * The actual filters.
	 */
	filters: FileOperationFilter[];
}
/**
 * A pattern kind describing if a glob pattern matches a file a folder or
 * both.
 *
 * @since 3.16.0
 */
export namespace FileOperationPatternKind {
	/**
	 * The pattern matches a file only.
	 */
	export const file: 'file' = 'file';

	/**
	 * The pattern matches a folder only.
	 */
	export const folder: 'folder' = 'folder';
}

export type FileOperationPatternKind = 'file' | 'folder';
/**
 * Matching options for the file operation pattern.
 *
 * @since 3.16.0
 */
export interface FileOperationPatternOptions {

	/**
	 * The pattern should be matched ignoring casing.
	 */
	ignoreCase?: boolean;
}
/**
 * A pattern to describe in which file operation requests or notifications
 * the server is interested in.
 *
 * @since 3.16.0
 */
interface FileOperationPattern {
	/**
	 * The glob pattern to match. Glob patterns can have the following syntax:
	 * - `*` to match one or more characters in a path segment
	 * - `?` to match on one character in a path segment
	 * - `**` to match any number of path segments, including none
	 * - `{}` to group sub patterns into an OR expression. (e.g. `**​/*.{ts,js}`
	 *   matches all TypeScript and JavaScript files)
	 * - `[]` to declare a range of characters to match in a path segment
	 *   (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
	 * - `[!...]` to negate a range of characters to match in a path segment
	 *   (e.g., `example.[!0-9]` to match on `example.a`, `example.b`, but
	 *   not `example.0`)
	 */
	glob: string;

	/**
	 * Whether to match files or folders with this pattern.
	 *
	 * Matches both if undefined.
	 */
	matches?: FileOperationPatternKind;

	/**
	 * Additional options used during matching.
	 */
	options?: FileOperationPatternOptions;
}

注册选项(Registration Options): undefined

请求(Request):

  • method: "workspace/willCreateFiles"
  • params: CreateFilesParams, 定义如下:
/**
 * The parameters sent in notifications/requests for user-initiated creation
 * of files.
 *
 * @since 3.16.0
 */
export interface CreateFilesParams {

	/**
	 * An array of all files/folders created in this operation.
	 */
	files: FileCreate[];
}
/**
 * Represents information on a file/folder create.
 *
 * @since 3.16.0
 */
export interface FileCreate {

	/**
	 * A file:// URI for the location of the file/folder being created.
	 */
	uri: string;
}

响应(Response):

  • result: WorkspaceEdit | null
  • error: codemessage,以防在请求期间发生异常。

DidCreateFiles 通知

从客户端内创建文件完成后,会从客户端向服务器发送此通知。

客户端能力(Client capability):

  • 属性路径: workspace.fileOperations.didCreate
  • 属性类型: boolean

服务端能力(Server capability):

  • 属性路径: workspace.fileOperations.didCreate
  • 属性类型: FileOperationRegistrationOptions

通知(Notification):

  • method: "workspace/didCreateFiles"
  • params: CreateFilesParams

WillRenameFiles 请求

在实际重命名文件之前,将此请求从客户端发送到服务器,只要重命名是从客户端内部触发的,无论是通过用户操作还是应用工作区编辑。该请求可以返回一个 WorkspaceEdit,该 WorkspaceEdit 将在重命名文件之前应用于工作区。请注意,如果计算编辑时间过长或服务器在此请求上不断失败,客户端可能会丢弃结果。这样做是为了保持重命名的快速和可靠。

客户端能力(Client capability):

  • 属性路径: workspace.fileOperations.willRename
  • 属性类型: boolean

服务端能力(Server capability):

  • 属性路径: workspace.fileOperations.willRename
  • 属性类型: FileOperationRegistrationOptions

注册选项(Registration Options): undefined

请求(Request):

  • method: "workspace/willRenameFiles"
  • params: RenameFilesParams, 定义如下:
/**
 * The parameters sent in notifications/requests for user-initiated renames
 * of files.
 *
 * @since 3.16.0
 */
export interface RenameFilesParams {

	/**
	 * An array of all files/folders renamed in this operation. When a folder
	 * is renamed, only the folder will be included, and not its children.
	 */
	files: FileRename[];
}
/**
 * Represents information on a file/folder rename.
 *
 * @since 3.16.0
 */
export interface FileRename {

	/**
	 * A file:// URI for the original location of the file/folder being renamed.
	 */
	oldUri: string;

	/**
	 * A file:// URI for the new location of the file/folder being renamed.
	 */
	newUri: string;
}

响应(Response):

  • result: WorkspaceEdit | null
  • error: codemessage,以防在请求期间发生异常。

DidRenameFiles 通知

从客户端内部重命名文件完成后,会从客户端向服务器发送此通知。

客户端能力(Client capability):

  • 属性路径: workspace.fileOperations.didRename
  • 属性类型: boolean

服务端能力(Server capability):

  • 属性路径: workspace.fileOperations.didRename
  • 属性类型: FileOperationRegistrationOptions

通知(Notification):

  • method: "workspace/didRenameFiles"
  • params: RenameFilesParams

WillDeleteFiles 请求

在实际删除文件之前,将此请求从客户端发送到服务器,只要删除是从客户端内部触发的,无论是通过用户操作还是应用工作区编辑。该请求可以返回一个 WorkspaceEdit,该 WorkspaceEdit 将在删除文件之前应用于工作区。请注意,如果计算编辑时间过长或服务器在此请求上不断失败,客户端可能会丢弃结果。这样做是为了保持删除的快速和可靠。

客户端能力(Client capability):

  • 属性路径: workspace.fileOperations.willDelete
  • 属性类型: boolean

服务端能力(Server capability):

  • 属性路径: workspace.fileOperations.willDelete
  • 属性类型: FileOperationRegistrationOptions

注册选项(Registration Options): undefined

请求(Request):

  • method: "workspace/willDeleteFiles"
  • params: DeleteFilesParams, 定义如下:
/**
 * The parameters sent in notifications/requests for user-initiated deletes
 * of files.
 *
 * @since 3.16.0
 */
export interface DeleteFilesParams {

	/**
	 * An array of all files/folders deleted in this operation.
	 */
	files: FileDelete[];
}
/**
 * Represents information on a file/folder delete.
 *
 * @since 3.16.0
 */
export interface FileDelete {

	/**
	 * A file:// URI for the location of the file/folder being deleted.
	 */
	uri: string;
}

响应(Response):

  • result: WorkspaceEdit | null, 定义如下:
  • error: codemessage,以防在请求期间发生异常。

DidDeleteFiles 通知

当从客户端中删除文件完成后,此请求会从客户端发送到服务器。

客户端能力(Client capability):

  • 属性路径: workspace.fileOperations.didDelete
  • 属性类型: boolean

服务端能力(Server capability):

  • 属性路径: workspace.fileOperations.didDelete
  • 属性类型: FileOperationRegistrationOptions

注册选项(Registration Options): undefined

通知(Notification):

  • method: "workspace/didDeleteFiles"
  • params: DeleteFilesParams

DidChangeWatchedFiles 通知

当客户端检测到语言客户端监视的文件和文件夹的更改时,此通知将从客户端发送到服务器(注意,尽管名称表明仅发送文件事件,但它与文件系统事件有关,其中也包括文件夹)。建议服务器使用注册机制注册这些文件系统事件。在以前的实现中,客户端在没有服务器主动请求的情况下推送文件事件。

允许服务器运行自己的文件系统监视机制,而不依赖客户端提供文件系统事件。但是,由于以下原因,不建议这样做:

  • 根据我们的经验,在磁盘上正确监视文件系统是具有挑战性的,特别是如果它需要跨多个操作系统支持。

  • 文件系统监视不是免费的,特别是如果实现使用某种轮询并将文件系统树保留在内存中以比较时间戳(例如,某些节点模块这样做)

  • 一个客户端通常会启动多个服务器。如果每个服务器都运行自己的文件系统,监视它可能会成为 CPU 或内存问题。

  • 通常,服务器实现多于客户端实现。所以这个问题最好在客户端解决。

客户端能力(Client capability):

  • 属性路径: workspace.didChangeWatchedFiles
  • 属性类型: DidChangeWatchedFilesClientCapabilities, 定义如下:
export interface DidChangeWatchedFilesClientCapabilities {
	/**
	 * Did change watched files notification supports dynamic registration.
	 * Please note that the current protocol doesn't support static
	 * configuration for file changes from the server side.
	 */
	dynamicRegistration?: boolean;

	/**
	 * Whether the client has support for relative patterns
	 * or not.
	 *
	 * @since 3.17.0
	 */
	relativePatternSupport?: boolean;
}

注册选项(Registration Options): DidChangeWatchedFilesRegistrationOptions, 定义如下:

/**
 * Describe options to be used when registering for file system change events.
 */
export interface DidChangeWatchedFilesRegistrationOptions {
	/**
	 * The watchers to register.
	 */
	watchers: FileSystemWatcher[];
}
/**
 * The glob pattern to watch relative to the base path. Glob patterns can have
 * the following syntax:
 * - `*` to match one or more characters in a path segment
 * - `?` to match on one character in a path segment
 * - `**` to match any number of path segments, including none
 * - `{}` to group conditions (e.g. `**​/*.{ts,js}` matches all TypeScript
 *   and JavaScript files)
 * - `[]` to declare a range of characters to match in a path segment
 *   (e.g., `example.[0-9]` to match on `example.0`, `example.1`, …)
 * - `[!...]` to negate a range of characters to match in a path segment
 *   (e.g., `example.[!0-9]` to match on `example.a`, `example.b`,
 *   but not `example.0`)
 *
 * @since 3.17.0
 */
export type Pattern = string;
/**
 * A relative pattern is a helper to construct glob patterns that are matched
 * relatively to a base URI. The common value for a `baseUri` is a workspace
 * folder root, but it can be another absolute URI as well.
 *
 * @since 3.17.0
 */
export interface RelativePattern {
	/**
	 * A workspace folder or a base URI to which this pattern will be matched
	 * against relatively.
	 */
	baseUri: WorkspaceFolder | URI;

	/**
	 * The actual glob pattern;
	 */
	pattern: Pattern;
}
/**
 * The glob pattern. Either a string pattern or a relative pattern.
 *
 * @since 3.17.0
 */
export type GlobPattern = Pattern | RelativePattern;
export interface FileSystemWatcher {
	/**
	 * The glob pattern to watch. See {@link GlobPattern glob pattern}
	 * for more detail.
	 *
 	 * @since 3.17.0 support for relative patterns.
	 */
	globPattern: GlobPattern;

	/**
	 * The kind of events of interest. If omitted it defaults
	 * to WatchKind.Create | WatchKind.Change | WatchKind.Delete
	 * which is 7.
	 */
	kind?: WatchKind;
}
export namespace WatchKind {
	/**
	 * Interested in create events.
	 */
	export const Create = 1;

	/**
	 * Interested in change events
	 */
	export const Change = 2;

	/**
	 * Interested in delete events
	 */
	export const Delete = 4;
}
export type WatchKind = uinteger;

通知(Notification):

  • method: "workspace/didChangeWatchedFiles"
  • params: DidChangeWatchedFilesParams, 定义如下:
interface DidChangeWatchedFilesParams {
	/**
	 * The actual file events.
	 */
	changes: FileEvent[];
}
/**
 * An event describing a file change.
 */
interface FileEvent {
	/**
	 * The file's URI.
	 */
	uri: DocumentUri;
	/**
	 * The change type.
	 */
	type: FileChangeType;
}
/**
 * The file event type.
 */
export namespace FileChangeType {
	/**
	 * The file got created.
	 */
	export const Created = 1;
	/**
	 * The file got changed.
	 */
	export const Changed = 2;
	/**
	 * The file got deleted.
	 */
	export const Deleted = 3;
}

export type FileChangeType = 1 | 2 | 3;

Execute command 请求

workspace/executeCommand请求从客户端发送到服务器,以触发服务器上的命令执行。在大多数情况下,服务器会创建一个 WorkspaceEdit 结构,并使用从服务器发送到客户端的请求 workspace/applyEdit 将更改应用于工作区。

客户端能力(Client capability):

  • 属性路径: workspace.executeCommand
  • 属性类型: ExecuteCommandClientCapabilities, 定义如下:
export interface ExecuteCommandClientCapabilities {
	/**
	 * Execute command supports dynamic registration.
	 */
	dynamicRegistration?: boolean;
}

服务端能力(Server capability):

  • 属性路径: executeCommandProvider
  • 属性类型: ExecuteCommandOptions, 定义如下:
export interface ExecuteCommandOptions extends WorkDoneProgressOptions {
	/**
	 * The commands to be executed on the server
	 */
	commands: string[];
}

注册选项(Registration Options): ExecuteCommandRegistrationOptions, 定义如下:

/**
 * Execute command registration options.
 */
export interface ExecuteCommandRegistrationOptions
	extends ExecuteCommandOptions {
}

请求(Request):

  • method: "workspace/executeCommand"
  • params: ExecuteCommandParams, 定义如下:
export interface ExecuteCommandParams extends WorkDoneProgressParams {

	/**
	 * The identifier of the actual command handler.
	 */
	command: string;
	/**
	 * Arguments that the command should be invoked with.
	 */
	arguments?: LSPAny[];
}

当命令从服务器返回到客户端时,通常指定参数。返回命令的示例请求包括 textDocument/codeActiontextDocument/codeLens

响应(Response):

  • result: LSPAny

Applies WorkspaceEdit 请求

workspace/applyEdit 请求从服务器发送到客户端,用于在客户端修改资源。

客户端能力(Client capability):

  • 属性路径: workspace.applyEdit
  • 属性类型: boolean

另请参阅 WorkspaceEditClientCapabilities,了解工作区编辑支持的功能。

请求(Request):

  • method: "workspace/applyEdit"
  • params: ApplyWorkspaceEditParams, 定义如下:
export interface ApplyWorkspaceEditParams {
	/**
	 * An optional label of the workspace edit. This label is
	 * presented in the user interface for example on an undo
	 * stack to undo the workspace edit.
	 */
	label?: string;

	/**
	 * The edits to apply.
	 */
	edit: WorkspaceEdit;
}

响应(Response):

  • result: ApplyWorkspaceEditResult, 定义如下:
export interface ApplyWorkspaceEditResult {
	/**
	 * Indicates whether the edit was applied or not.
	 */
	applied: boolean;

	/**
	 * An optional textual description for why the edit was not applied.
	 * This may be used by the server for diagnostic logging or to provide
	 * a suitable error for a request that triggered the edit.
	 */
	failureReason?: string;

	/**
	 * Depending on the client's failure handling strategy `failedChange`
	 * might contain the index of the change that failed. This property is
	 * only available if the client signals a `failureHandling` strategy
	 * in its client capabilities.
	 */
	failedChange?: uinteger;
}

窗口功能

ShowMessage

ShowMessage 通知

通知从服务器发送到客户端,以要求客户端在用户界面中显示特定消息。

通知(Notification):

  • method: "window/showMessage"
  • params: ShowMessageParams, 定义如下:
interface ShowMessageParams {
	/**
	 * The message type. See {@link MessageType}.
	 */
	type: MessageType;

	/**
	 * The actual message.
	 */
	message: string;
}
export namespace MessageType {
	/**
	 * An error message.
	 */
	export const Error = 1;
	/**
	 * A warning message.
	 */
	export const Warning = 2;
	/**
	 * An information message.
	 */
	export const Info = 3;
	/**
	 * A log message.
	 */
	export const Log = 4;
	/**
	 * A debug message.
	 *
	 * @since 3.18.0
	 * @proposed
	 */
	export const Debug = 5;
}

export type MessageType = 1 | 2 | 3 | 4 | 5;

ShowMessage 请求

请求从服务器发送到客户端,以要求客户端在用户界面中显示特定消息。除了显示消息通知外,该请求还允许传递操作并等待客户端的答复。

客户端能力(Client capability):

  • 属性路径: window.showMessage
  • 属性类型: ShowMessageRequestClientCapabilities, 定义如下:
/**
 * Show message request client capabilities
 */
export interface ShowMessageRequestClientCapabilities {
	/**
	 * Capabilities specific to the `MessageActionItem` type.
	 */
	messageActionItem?: {
		/**
		 * Whether the client supports additional attributes which
		 * are preserved and sent back to the server in the
		 * request's response.
		 */
		additionalPropertiesSupport?: boolean;
	};
}

请求(Request):

  • method: "window/showMessageRequest"
  • params: ShowMessageRequestParams, 定义如下:
interface ShowMessageRequestParams {
	/**
	 * The message type. See {@link MessageType}
	 */
	type: MessageType;

	/**
	 * The actual message
	 */
	message: string;

	/**
	 * The message action items to present.
	 */
	actions?: MessageActionItem[];
}
interface MessageActionItem {
	/**
	 * A short title like 'Retry', 'Open Log' etc.
	 */
	title: string;
}

响应(Response):

  • result: MessageActionItem | null, MessageActionItem 是被选择的项,如果没被选择返回 null
  • error: codemessage,以防在请求期间发生异常。

LogMessage 通知

日志消息通知从服务器发送到客户端,要求客户端记录特定消息。

通知(Notification):

  • method: "window/logMessage"
  • params: LogMessageParams, 定义如下:
interface LogMessageParams {
	/**
	 * The message type. See {@link MessageType}
	 */
	type: MessageType;

	/**
	 * The actual message
	 */
	message: string;
}

Show Document 请求

新版本 3.16.0

请求从服务器发送到客户端,要求客户端在用户界面中显示 URI 引用的特定资源。

客户端能力(Client capability):

  • 属性路径: window.showDocument
  • 属性类型: ShowDocumentClientCapabilities, 定义如下:
/**
 * Client capabilities for the show document request.
 *
 * @since 3.16.0
 */
export interface ShowDocumentClientCapabilities {
	/**
	 * The client has support for the show document
	 * request.
	 */
	support: boolean;
}

请求(Request):

  • method: "window/showDocument"
  • params: ShowDocumentParams, 定义如下:
/**
 * Params to show a resource.
 *
 * @since 3.16.0
 */
export interface ShowDocumentParams {
	/**
	 * The uri to show.
	 */
	uri: URI;

	/**
	 * Indicates to show the resource in an external program.
	 * To show, for example, `https://code.visualstudio.com/`
	 * in the default WEB browser set `external` to `true`.
	 */
	external?: boolean;

	/**
	 * An optional property to indicate whether the editor
	 * showing the document should take focus or not.
	 * Clients might ignore this property if an external
	 * program is started.
	 */
	takeFocus?: boolean;

	/**
	 * An optional selection range if the document is a text
	 * document. Clients might ignore the property if an
	 * external program is started or the file is not a text
	 * file.
	 */
	selection?: Range;
}

响应(Response):

  • result: ShowDocumentResult, 定义如下:
/**
 * The result of an show document request.
 *
 * @since 3.16.0
 */
export interface ShowDocumentResult {
	/**
	 * A boolean indicating if the show was successful.
	 */
	success: boolean;
}
  • error: codemessage,以防在请求期间发生异常。

WorkDoneProgress

Create Work Done Progress 请求

window/workDoneProgress/create 请求从服务器发送到客户端,要求客户端创建工作完成进度。

客户端能力(Client capability):

  • 属性路径: window.workDoneProgress
  • 属性类型: boolean

请求(Request):

  • method: "window/workDoneProgress/create"
  • params: WorkDoneProgressCreateParams, 定义如下:
export interface WorkDoneProgressCreateParams {
	/**
	 * The token to be used to report progress.
	 */
	token: ProgressToken;
}

响应(Response):

  • result: void
  • error: codemessage,以防在请求期间发生异常。如果发生错误,服务器不得使用 WorkDoneProgressCreateParams 中提供的 token 发送任何进度通知。

Cancel a Work Done Progress 通知

window/workDoneProgress/cancel 通知从客户端发送到服务器,以取消使用 window/workDoneProgress/create 在服务器端启动的进度。无需将进度标记为 cancellable 即可取消,客户端可以出于多种原因取消进度:如果出现错误、重新加载工作区等。

通知(Notification):

  • method: "window/workDoneProgress/cancel"
  • params: WorkDoneProgressCancelParams, 定义如下:
export interface WorkDoneProgressCancelParams {
	/**
	 * The token to be used to report progress.
	 */
	token: ProgressToken;
}

Telemetry 通知

遥测通知从服务器发送到客户端,要求客户端记录遥测事件。该协议未指定有效负载,因为协议中不会对数据进行任何解释。大多数客户端甚至不直接处理事件,而是将它们转发到扩展,因为相应的服务器要求转发事件。

通知(Notification):

  • method: "telemetry/event"
  • params: object | array

杂项

实现注意事项

语言服务器通常在单独的进程中运行,客户端以异步方式与它们通信。此外,客户端通常允许用户与源代码进行交互,即使请求结果处于待处理状态。建议采用以下实现模式,以避免客户端应用过时的响应结果:

  • 如果客户端向服务器发送请求,并且客户端状态发生更改,使响应无效,则应执行以下操作:

    • 取消服务器请求,如果结果对客户端不再有用,则忽略该结果。如有必要,客户端应重新发送请求。
    • 如果客户端仍可以使用结果,例如,通过将状态更改应用于结果将其转换为新结果,则保持请求运行。
  • 因此,服务器不应仅仅因为在队列中检测到状态更改通知而自行决定取消请求。如前所述,结果对客户仍然有用。

  • 如果服务器检测到内部状态更改(例如,项目上下文已更改)使执行中的请求结果无效,则服务器可能会使用 ContentModified 错误响应这些请求。如果客户端收到 ContentModified 错误,则通常不应在最终用户的 UI 中显示该错误。如果客户端知道如何重新发送请求,则可以重新发送请求。应该注意的是,对于所有基于位置的请求,客户可能特别难以重新编写请求。

  • 客户端不应发送针对过期对象(例如,Code Lenses等)的解析请求。如果服务器收到过期对象的解析请求,则服务器可能会使用 ContentModified 错误响应这些请求。

  • 如果客户端注意到服务器意外退出,则应尝试重新启动服务器。但是,客户端应注意不要无休止地重新启动崩溃的服务器。例如,VS Code 不会重新启动在过去 180 秒内崩溃 5 次的服务器。

服务器通常支持不同的通信通道(例如 stdio、管道等)。为了简化服务器在不同客户端中的使用,强烈建议服务器实现支持以下命令行参数来选择通信通道:

  • stdio: 使用 stdio 作为通信信道

  • pipe: 使用管道(Windows)或套接字文件(Linux、Mac)作为通信通道。管道/套接字文件名作为下一个参数或带有 --pipe= 传递。

  • socket: 使用套接字作为通信通道。端口作为下一个参数 或 --port= 传递。

  • node-ipc: 在客户端和服务器之间使用 node IPC 通信。仅当客户端和服务器都在 node 下运行时,才支持此功能。

为了支持启动服务器的编辑器崩溃的情况,编辑器还应将其进程 ID 传递给服务器。这允许服务器监视编辑器进程,并在编辑器进程终止时自行关闭。在命令行上传递的进程 ID 应与在 initialize 参数中传递的进程 ID 相同。要使用的命令行参数是 --clientProcessId

元模型

从 3.17 开始,有一个描述 LSP 协议的元模型:

  • metaModel.json: LSP 3.17 规范的实际元模型
  • metaModel.ts: 定义构成元模型的数据类型的 TypeScript 文件。
  • metaModel.schema.json: 定义构成元模型的数据类型的 JSON 架构文件。可用于生成代码以读取元模型 JSON 文件。

语言服务器实现

本章基于语言服务协议实现了一个简单的语言服务器,并将其作为其他语言服务器的调试工具,以帮助深入理解语言服务协议。

最小的语言服务器实现

为了更好的理解语言服务器协议,我们不使用任何第三方库实现一个语言服务器客户端和服务端,并尽可能最小。

语言服务器可以使用多种通信方式,我们采用最常用的一种: stdio, 即基于标准输入输出进行通信

我们创建一个项目,名为 min-lsp-example 的 js 语言项目。

mkdir min-lsp-example
cd min-lsp-example
npm init -y

创建一个 client.js 文件, 并写入以下内容:

const { spawn } = require('child_process');
const ls = spawn('node', ['server.js']);

ls.stdin.write("client init message")

ls.stdout.on('data', (data) => {
    console.log(`stdout: ${data}`);
});

我们在 client.js 中使用 server.js 创建了一个子进程,并向子进程的标准输入中写入了一些字符,并接受来自子进程的输出,然后在收到的消息前加上 stdout: 后将其输出到了控制台。当然,server.js 并不存在,因此,创建一个 server.js 文件,并写入以下内容:

process.stdin.on("data", data => {
    data = data.toString().toUpperCase()
    process.stdout.write(data)
})

我们在 server.js 中我们接受来自标准输入的内容,并将其转换为大写后,写入标准输出。

现在我们运行它:

node client.js

我们看到控制台输出了如下信息:

stdout: CLIENT INIT MESSAGE

这表明,我们成功创建了一个进程,并使用标准输入输出与其通信,下面,我们将他们分别改造为语言服务器的客户端和服务端。

客户端和服务端必须的能力

  • 客户端和服务器都必须支持生命周期消息: initialize 请求, initialized 通知, shutdown 请求, exit 通知

  • 客户端必须实现 textDocument/didOpentextDocument/didChangetextDocument/didClose 通知

我们首先创建一个基础协议模块来处理,请求、响应和通知,创建 protocol.js 文件,并写入以下内容:

let id = 0;

function getId() {
    return id;
}

function getMessage(data) {
    let json = JSON.stringify(data);
    let length = json.length;
    return `Content-Length: ${length}\r\n\r\n${json}`;
}

function getRequest(method, params) {
    id++;
    return getMessage({
        jsonrpc: "2.0",
        id,
        method,
        params
    });
}

function getResponse(id, result, error) {
    return getMessage({
        jsonrpc: "2.0",
        id,
        result,
        error
    })
}

function getNotification(method, params) {
    return getMessage({
        jsonrpc: "2.0",
        method,
        params
    })
}

module.exports = {
    getId,
    getRequest,
    getResponse,
    getNotification,
}

客户端向服务器发送 initialize 请求,根据 initialize 请求 中的 请求(Request) 定义,并统一处理发送数据和处理数据,对 client.js 进行修改:

const { spawn } = require('child_process');
+const protocol = require('./protocol');
const ls = spawn('node', ['server.js']);

-ls.stdin.write("client init message")
+send(protocol.getRequest("initialize", {
+    processId: process.pid,
+    rootUri: null,
+    capabilities: {}
+}))

ls.stdout.on('data', (data) => {
-    console.log(`stdout: ${data}`);
+    dealMessage(data.toString())
});
+
+function send(data) {
+    console.log("===>", data.split("\r\n\r\n")[1]);
+    ls.stdin.write(data)
+}
+
+function dealMessage(data) {
+    console.log(data)
+}

根据 响应(Response) 的定义,对 server.js 进行修改,并且考虑不同客户端的实现导致消息标头和内容不连续的可能:

+const protocol = require('./protocol');
+
+let length = 0
+
process.stdin.on("data", data => {
-    data = data.toString().toUpperCase()
-    process.stdout.write(data)
+    data = data.toString()
+    let arr = data.split("Content-Length: ")
+    if (arr[0] && arr[0].length === length) {
+        dealMessage(arr[0])
+    }
+
+    for (let i = 1; i < arr.length; i++) {
+        let value = arr[i].split("\r\n\r\n")[1]
+        if (value) {
+            dealMessage(value)
+        } else {
+            length = Number(arr[i].split("\r\n\r\n")[0])
+        }
+    }
})
+
+function dealMessage(data) {
+    let message = JSON.parse(data)
+    if (message.method === 'initialize') {
+        process.stdout.write(protocol.getResponse(message.id, {
+            capabilities: {}
+        }))
+    }
+}

现在启动客户端:

node client.js

我们可以看下以下输出:

===> {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"processId":49654,"rootUri":null,"capabilities":{}}}
Content-Length: 53

{"jsonrpc":"2.0","id":1,"result":{"capabilities":{}}}

这是包括发送的 initialize 请求和从服务器返回的对 initialize 请求的响应消息。

根据协议,客户端在收到 initialize 请求的结果之后,向服务器发送 initialized 通知,同样考虑不同服务端的实现导致消息标头和内容不连续的可能,修改 client.js:

const { spawn } = require('child_process');
const protocol = require("./protocol")
const ls = spawn('node', ['server1.js']);

send(protocol.getRequest("initialize", {
    processId: process.pid,
    rootUri: null,
    capabilities: {}
}))

+let length = 0
+
ls.stdout.on('data', (data) => {
-    dealMessage(data.toString())
+    data = data.toString()
+    let arr = data.split("Content-Length: ")
+    if (arr[0] && arr[0].length === length) {
+        dealMessage(arr[0])
+    }
+
+    for (let i = 1; i < arr.length; i++) {
+        let value = arr[i].split("\r\n\r\n")[1]
+        if (value) {
+            dealMessage(value)
+        } else {
+            length = Number(arr[i].split("\r\n\r\n")[0])
+        }
+    }
});

function send(data) {
    console.log("===>", data.split("\r\n\r\n")[1]);
    ls.stdin.write(data)
}

function dealMessage(data) {
-    console.log(data);
+    let response = JSON.parse(data)
+    console.log("<===", data);
+    if (response.id === 1) {
+        send(protocol.getNotification("initialized", {}))
+    }
}

现在,启动客户端,我们可以看到:

===> {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"processId":49975,"rootUri":null,"capabilities":{}}}
<=== {"jsonrpc":"2.0","id":1,"result":{"capabilities":{}}}
===> {"jsonrpc":"2.0","method":"initialized","params":{}}

客户端收到 initialize 的响应之后向服务器发送了 initialized 通知。我们成功完成了客户端和服务器的初始化。

这里,我们客户端不需要做任何操作,因此,我们在等待 1s 后发送 shutdown 请求,修改 client.js:

function dealMessage(data) {
    let response = JSON.parse(data)
    console.log("<===", data);
    if (response.id === 1) {
        send(protocol.getNotification("initialized", {}))
+        setTimeout(() => {
+            send(protocol.getRequest("shutdown"))
+        }, 1000);
    }
}

在服务端接收 shutdown 请求,并设置 shutdown 状态,修改 server.js:

+let shutdown = false;
function dealMessage(data) {
    let message = JSON.parse(data)
    if (message.method === 'initialize') {
        process.stdout.write(protocol.getResponse(message.id, {
            capabilities: {}
        }))
+    } else if (message.method === 'shutdown') {
+        shutdown = true;
+        process.stdout.write(protocol.getResponse(message.id, null))
    }
}

客户端收到 shutdown 请求的响应之后,发送 exit 通知,使服务器退出,修改 client.js:

+ls.on('close', (code) => {
+    console.log(`child process exited with code ${code}`);
+});
+
+let shutdownId = 0;
+
function dealMessage(data) {
    let response = JSON.parse(data)
    console.log("<===", data);
    if (response.id === 1) {
        send(protocol.getNotification("initialized", {}))
        setTimeout(() => {
            send(protocol.getRequest("shutdown"))
+            shutdownId = protocol.getId();
        }, 1000);
+    } else if (response.id === shutdownId) {
+        send(protocol.getNotification("exit"))
    }
}

服务器接收到 exit 通知后退出,修改 server.js:

function dealMessage(data) {
    let message = JSON.parse(data)
    if (message.method === 'initialize') {
        process.stdout.write(protocol.getResponse(message.id, {
            capabilities: {}
        }))
    } else if (message.method === 'shutdown') {
        shutdown = true;
        process.stdout.write(protocol.getResponse(message.id, null))
+    } else if (message.method === 'exit') {
+        if (shutdown) {
+            process.exit(0)
+        } else {
+            process.exit(1)
+        }
    }
}

最终代码如下:

client.js

const { spawn } = require('child_process');
const protocol = require("./protocol")
const ls = spawn('node', ['server.js']);

send(protocol.getRequest("initialize", {
    processId: process.pid,
    rootUri: null,
    capabilities: {}
}))

let length = 0

ls.stdout.on('data', (data) => {
    data = data.toString()
    let arr = data.split("Content-Length: ")
    if (arr[0] && arr[0].length === length) {
        dealMessage(arr[0])
    }

    for (let i = 1; i < arr.length; i++) {
        let value = arr[i].split("\r\n\r\n")[1]
        if (value) {
            dealMessage(value)
        } else {
            length = Number(arr[i].split("\r\n\r\n")[0])
        }
    }
});

ls.on('close', (code) => {
    console.log(`child process exited with code ${code}`);
});

function send(data) {
    console.log("===>", data.split("\r\n\r\n")[1]);
    ls.stdin.write(data)
}

let shutdownId = 0;

function dealMessage(data) {
    let response = JSON.parse(data)
    console.log("<===", data);
    if (response.id === 1) {
        send(protocol.getNotification("initialized", {}))
        setTimeout(() => {
            send(protocol.getRequest("shutdown"))
            shutdownId = protocol.getId();
        }, 1000);
    } else if (response.id === shutdownId) {
        send(protocol.getNotification("exit"))
    }
}

server.js

const protocol = require("./protocol");

let length = 0

process.stdin.on("data", data => {
    data = data.toString()
    let arr = data.split("Content-Length: ")
    if (arr[0] && arr[0].length === length) {
        dealMessage(arr[0])
    }

    for (let i = 1; i < arr.length; i++) {
        let value = arr[i].split("\r\n\r\n")[1]
        if (value) {
            dealMessage(value)
        } else {
            length = Number(arr[i].split("\r\n\r\n")[0])
        }
    }
})

let shutdown = false;

function dealMessage(data) {
    let message = JSON.parse(data)
    if (message.method === 'initialize') {
        process.stdout.write(protocol.getResponse(message.id, {
            capabilities: {}
        }))
    } else if (message.method === 'shutdown') {
        shutdown = true;
        process.stdout.write(protocol.getResponse(message.id, null))
    } else if (message.method === 'exit') {
        if (shutdown) {
            process.exit(0)
        } else {
            process.exit(1)
        }
    }
}

现在,我们运行它,会显示如下消息:

===> {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"processId":51807,"rootUri":null,"capabilities":{}}}
<=== {"jsonrpc":"2.0","id":1,"result":{"capabilities":{}}}
===> {"jsonrpc":"2.0","method":"initialized","params":{}}
===> {"jsonrpc":"2.0","id":2,"method":"shutdown"}
<=== {"jsonrpc":"2.0","id":2,"result":null}
===> {"jsonrpc":"2.0","method":"exit"}
child process exited with code 0

到这里(忽略客户端对 textDocument/* 的处理),我们成功实现了一个语言服务器的客户端和服务端,尽管它们没有任何能力。

这里,特别感谢 connie1451 的帮助!

简单调试语言服务器

在上一章中,我们实现了一个最小的语言服务器的客户端和服务端,它们没有任何功能,但是我们可以用它们来简单调试其他实际的语言服务器。

语言服务器的实现列表 中找到了 typescript-language-server,我们来尝试调试它。

min-lsp-example 项目中,执行:

npm i typescript typescript-language-server

然后修改 client.js:

-const ls = spawn('node', ['server.js']);
+const ls = spawn('node', ['node_modules/typescript-language-server/lib/cli.mjs', '--stdio']);

运行:

node client.js

会产生如下输出:

===> {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"processId":53519,"rootUri":null,"capabilities":{}}}
<=== {"jsonrpc":"2.0","method":"window/logMessage","params":{"type":3,"message":"Using Typescript version (bundled) 5.5.2 from path \"/Users/renwei/Code/min-lsp-example/node_modules/typescript/lib/tsserver.js\""}}
<=== {"jsonrpc":"2.0","id":1,"result":{"capabilities":{"textDocumentSync":2,"completionProvider":{"triggerCharacters":[".","\"","'","/","@","<"],"resolveProvider":true},"codeActionProvider":true,"codeLensProvider":{"resolveProvider":true},"definitionProvider":true,"documentFormattingProvider":true,"documentRangeFormattingProvider":true,"documentHighlightProvider":true,"documentSymbolProvider":true,"executeCommandProvider":{"commands":["_typescript.applyWorkspaceEdit","_typescript.applyCodeAction","_typescript.applyRefactoring","_typescript.configurePlugin","_typescript.organizeImports","_typescript.applyRenameFile","_typescript.goToSourceDefinition"]},"hoverProvider":true,"inlayHintProvider":true,"linkedEditingRangeProvider":false,"renameProvider":true,"referencesProvider":true,"selectionRangeProvider":true,"signatureHelpProvider":{"triggerCharacters":["(",",","<"],"retriggerCharacters":[")"]},"workspaceSymbolProvider":true,"implementationProvider":true,"typeDefinitionProvider":true,"foldingRangeProvider":true,"semanticTokensProvider":{"documentSelector":null,"legend":{"tokenTypes":["class","enum","interface","namespace","typeParameter","type","parameter","variable","enumMember","property","function","member"],"tokenModifiers":["declaration","static","async","readonly","defaultLibrary","local"]},"full":true,"range":true},"workspace":{"fileOperations":{"willRename":{"filters":[{"scheme":"file","pattern":{"glob":"**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}","matches":"file"}},{"scheme":"file","pattern":{"glob":"**","matches":"folder"}}]}}}}}}
===> {"jsonrpc":"2.0","method":"initialized","params":{}}
<=== {"jsonrpc":"2.0","method":"$/typescriptVersion","params":{"version":"5.5.2","source":"bundled"}}
===> {"jsonrpc":"2.0","id":2,"method":"shutdown"}
<=== {"jsonrpc":"2.0","id":2,"result":null}
===> {"jsonrpc":"2.0","method":"exit"}
child process exited with code 0

我们的客户端与 typescript-language-server 服务器成功完成了一个完整的生命周期。

向服务器发送 textDocument/didOpen:

function dealMessage(data) {
    let response = JSON.parse(data)
    console.log("<===", data);
    if (response.id === 1) {
        send(protocol.getNotification("initialized", {}))
        setTimeout(() => {
            send(protocol.getRequest("shutdown"))
            shutdownId = protocol.getId();
        }, 1000);
+        send(protocol.getNotification("textDocument/didOpen", {
+            textDocument: {
+                uri: "file://test.ts",
+                languageId: "typescript",
+                version: 1,
+                text: "/** This is a function */\nfunction test(first: string) {}"
+            }
+        }))
    } else if (response.id === shutdownId) {
        send(protocol.getNotification("exit"))
    }
}

下面我们展示向服务器发送一个 hover 请求并演示如何阅读文档:

  1. 找到 Hover 请求 的文档。

  2. 找到其 客户端能力(Client capability) 部分,我们看到属性路径为 textDocument.hover,属性类型为 HoverClientCapabilities,在初始化请求中加入此能力的支持:

send(protocol.getRequest("initialize", {
    processId: process.pid,
    rootUri: null,
    capabilities: {
+        textDocument: { hover: {} }
    }
}))

如果你要开发的是服务端,那么同样找到 服务端能力(Server capability) 部分,我们看到属性路径为 hoverProvider, 属性类型为 boolean | HoverOptions,在初始化请求的响应中为服务端添加此能力的支持:

    if (message.method === 'initialize') {
        process.stdout.write(protocol.getResponse(message.id, {
            capabilities: {
+                hoverProvider: true
            }
        }))
    }
  1. 请求(Request) 部分,可以找到,方法的参数 textDocument/hover 和类型 HoverParams,此处我们在发送 textDocument/didOpen 通知之后立即发送 hover 请求:
function dealMessage(data) {
    let response = JSON.parse(data)
    console.log("<===", data);
    if (response.id === 1) {
        send(protocol.getNotification("initialized", {}))
        setTimeout(() => {
            send(protocol.getRequest("shutdown"))
            shutdownId = protocol.getId();
        }, 1000);
        send(protocol.getNotification("textDocument/didOpen", {
            textDocument: {
                uri: "file://test.ts",
                languageId: "typescript",
                version: 1,
                text: "/** This is a function */\nfunction test(first: string) {}"
            }
        }))
+        send(protocol.getRequest("textDocument/hover", {
+            textDocument: { uri: "file://test.ts" },
+            position: { line: 1, character: 10 }
+        }))
    } else if (response.id === shutdownId) {
        send(protocol.getNotification("exit"))
    }
}

同样的,对于开发服务器,在 响应(Response) 部分,定义了返回值的类型 Hover | null,在这里,我们直接返回固定值:

function dealMessage(data) {
    let message = JSON.parse(data)
    if (message.method === 'initialize') {
        process.stdout.write(protocol.getResponse(message.id, {
            capabilities: {
                hoverProvider: true
            }
        }))
+    } else if (message.method === 'textDocument/hover') {
+        process.stdout.write(protocol.getResponse(message.id, {
+            contents: {
+                kind: "plaintext",
+                value: "test"
+            },
+        }))
    } else if (message.method === 'shutdown') {
        shutdown = true;
        process.stdout.write(protocol.getResponse(message.id, null))
    } else if (message.method === 'exit') {
        if (shutdown) {
            process.exit(0)
        } else {
            process.exit(1)
        }
    }
}

对于基础类型的定义,可以在 基础 JSON 结构 中找到,例如 TextDocumentPositionParams, MarkupContent

最后,下面是完整的修改后的客户端代码:

const { spawn } = require('child_process');
const protocol = require('./protocol');
const ls = spawn('node', ['node_modules/typescript-language-server/lib/cli.mjs', '--stdio']);

send(protocol.getRequest("initialize", {
    processId: process.pid,
    rootUri: null,
    capabilities: {
        textDocument: { hover: {} }
    }
}))

let length = 0

ls.stdout.on('data', (data) => {
    data = data.toString()
    let arr = data.split("Content-Length: ")
    if (arr[0] && arr[0].length === length) {
        dealMessage(arr[0])
    }

    for (let i = 1; i < arr.length; i++) {
        let value = arr[i].split("\r\n\r\n")[1]
        if (value) {
            dealMessage(value)
        } else {
            length = Number(arr[i].split("\r\n\r\n")[0])
        }
    }
});

ls.stderr.on("data", (data) => {
    console.error(`${data}`);
})

ls.on('close', (code) => {
    console.log(`child process exited with code ${code}`);
});


function send(data) {
    console.log("===>", data.split("\r\n\r\n")[1]);
    ls.stdin.write(data)
}

let shutdownId = 0;

function dealMessage(data) {
    let response = JSON.parse(data)
    console.log("<===", data);
    if (response.id === 1) {
        send(protocol.getNotification("initialized", {}))
        setTimeout(() => {
            send(protocol.getRequest("shutdown"))
            shutdownId = protocol.getId();
        }, 1000);
        send(protocol.getNotification("textDocument/didOpen", {
            textDocument: {
                uri: "file://test.ts",
                languageId: "typescript",
                version: 1,
                text: "/** This is a function */\nfunction test(first: string) {}"
            }
        }))
        send(protocol.getRequest("textDocument/hover", {
            textDocument: { uri: "file://test.ts" },
            position: { line: 1, character: 10 }
        }))
    } else if (response.id === shutdownId) {
        send(protocol.getNotification("exit"))
    }
}

现在,我们可以运行客户端:

node client.js

输出:

===> {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"processId":56553,"rootUri":null,"capabilities":{"textDocument":{"hover":{}}}}}
<=== {"jsonrpc":"2.0","method":"window/logMessage","params":{"type":3,"message":"Using Typescript version (bundled) 5.5.2 from path \"/Users/renwei/Code/min-lsp-example/node_modules/typescript/lib/tsserver.js\""}}
<=== {"jsonrpc":"2.0","id":1,"result":{"capabilities":{"textDocumentSync":2,"completionProvider":{"triggerCharacters":[".","\"","'","/","@","<"],"resolveProvider":true},"codeActionProvider":true,"codeLensProvider":{"resolveProvider":true},"definitionProvider":true,"documentFormattingProvider":true,"documentRangeFormattingProvider":true,"documentHighlightProvider":true,"documentSymbolProvider":true,"executeCommandProvider":{"commands":["_typescript.applyWorkspaceEdit","_typescript.applyCodeAction","_typescript.applyRefactoring","_typescript.configurePlugin","_typescript.organizeImports","_typescript.applyRenameFile","_typescript.goToSourceDefinition"]},"hoverProvider":true,"inlayHintProvider":true,"linkedEditingRangeProvider":false,"renameProvider":true,"referencesProvider":true,"selectionRangeProvider":true,"signatureHelpProvider":{"triggerCharacters":["(",",","<"],"retriggerCharacters":[")"]},"workspaceSymbolProvider":true,"implementationProvider":true,"typeDefinitionProvider":true,"foldingRangeProvider":true,"semanticTokensProvider":{"documentSelector":null,"legend":{"tokenTypes":["class","enum","interface","namespace","typeParameter","type","parameter","variable","enumMember","property","function","member"],"tokenModifiers":["declaration","static","async","readonly","defaultLibrary","local"]},"full":true,"range":true},"workspace":{"fileOperations":{"willRename":{"filters":[{"scheme":"file","pattern":{"glob":"**/*.{ts,js,jsx,tsx,mjs,mts,cjs,cts}","matches":"file"}},{"scheme":"file","pattern":{"glob":"**","matches":"folder"}}]}}}}}}
===> {"jsonrpc":"2.0","method":"initialized","params":{}}
===> {"jsonrpc":"2.0","method":"textDocument/didOpen","params":{"textDocument":{"uri":"file://test.ts","languageId":"typescript","version":1,"text":"/** This is a function */\nfunction test(first: string) {}"}}}
===> {"jsonrpc":"2.0","id":2,"method":"textDocument/hover","params":{"textDocument":{"uri":"file://test.ts"},"position":{"line":1,"character":10}}}
<=== {"jsonrpc":"2.0","method":"$/typescriptVersion","params":{"version":"5.5.2","source":"bundled"}}
<=== {"jsonrpc":"2.0","id":2,"result":{"contents":{"kind":"markdown","value":"\n```typescript\nfunction test(first: string): void\n```\nThis is a function"},"range":{"start":{"line":1,"character":9},"end":{"line":1,"character":13}}}}
===> {"jsonrpc":"2.0","id":3,"method":"shutdown"}
<=== {"jsonrpc":"2.0","id":3,"result":null}
===> {"jsonrpc":"2.0","method":"exit"}
child process exited with code 0

我们在第 6 行,找到了 hover 请求, 在第 8 行可以找到它的响应。

现在,我们完成了一个简单的调试,你也可以为 client.js 增加更多的功能来调试你自己的语言服务器。

你也可以使用现存的库来调试: lsp-tester

语言服务器插件开发

本章介绍应用于实际编辑器的语言服务器插件的开发。

使用 typescript 开发 vscode 插件

这里不讨论常规的插件开发,仅讨论基于语言服务器的插件,对于常规插件的开发,请参考 Extension API。本文基于 VS Code Language Extensions

我们以一个 vue 插件为例来展示插件的开发。

直接基于 lsp-sample 项目进行开发:

git clone --depth=1 https://github.com/microsoft/vscode-extension-samples.git vue-lsp-extension
cd vue-lsp-extension
rm -rf .git

修改 package.json 中的 name, description, author 等信息。

npm i

这样,一个 vscode 的语言服务器插件的基本框架就搭好了。我们关注 client/src/extension.tsserver/src/server.ts ,它们分别是语言服务器的客户端和服务端。客户端使用了 vscode-languageclient 库,服务端使用了 vscode-languageserver 库。

活动事件

插件需要先在根目录的 package.json 文件的扩展字段 activationEvents 中声明激活的条件,只有满足激活条件,插件才会开始工作。下面是所有可选的条件:

对于此项目,我们使用 onLanguage:vue:

    "activationEvents": [
-        "onLanguage:plaintext"
+        "onLanguage:vue"
    ],

客户端文档筛选器

文档筛选器 documentSelector 通过不同的属性(如语言、其资源的方案或应用于路径的 glob-pattern)来表示文档,只有符合筛选的文件的打开,修改和关闭,客户端才会发送对应的 textDocument/* 通知。

修改 extension.ts:

    const clientOptions: LanguageClientOptions = {
        // Register the server for plain text documents
-        documentSelector: [{ scheme: 'file', language: 'plaintext' }],
+        documentSelector: [{ scheme: 'file', language: 'vue' }],
        synchronize: {
            // Notify the server about file changes to '.clientrc files contained in the workspace
            fileEvents: workspace.createFileSystemWatcher('**/.clientrc')
        }
    };

注册语言

如果开发的插件不是语言插件,跳过此步骤。

如果 vscode 之前没有安装过与 vue 相关的语言服务器插件,那么名为 vuelanguage vscode 并不认识,因此,启动调试会发现插件并没有启动。

所以,如果您是为新的语言添加扩展,那么需要声明语言:

    "contributes": {
+        "languages": [
+            {
+                "id": "vue",
+                "aliases": [
+                    "Vue",
+                    "vue"
+                ],
+                "extensions": [
+                    ".vue"
+                ]
+            }
+        ],
    }

注意,这里的语言声明仅仅注册了语言,未包含其他配置,对于 vue 的完整配置,请参考 vuejs/language-tools

F5 或者点击 Launch Client 启动调试,vscode 会打开一个新窗口。我们打开一个 vue 项目,进入某个 vue 文件,此时,插件启动,找到 Output / 输出 面板,从下拉框中选择 Language Server Example,此面板会打印日志。

我们关闭此窗口,进入 extension.ts 文件,修改扩展 id 和名称:

    client = new LanguageClient(
-        'languageServerExample',
-        'Language Server Example',
+        'vue-lsp-extension',
+        'Vue LSP Extension',
        serverOptions,
        clientOptions
    );

重新启动调试,会发现插件名称变成了 Vue LSP Extension

vue-lsp-extension

打印日志

服务端打印日志使用 connection.console,例如:

connection.onInitialized(() => {
    if (hasConfigurationCapability) {
        // Register for all configuration changes.
        connection.client.register(DidChangeConfigurationNotification.type, undefined);
    }
    if (hasWorkspaceFolderCapability) {
        connection.workspace.onDidChangeWorkspaceFolders(_event => {
            connection.console.log('Workspace folder change event received.');
        });
    }
+    connection.console.log("test log");
});

重启插件,打开日志面板:

console-log

我们可以看到成功输出日志。

语言功能

server.ts 中已经存在 Completion 的示例,我们参考 Hover 再增加一个 Hover 的功能。

首先,在初始化请求的响应中,声明支持 hover 能力:

    const result: InitializeResult = {
        capabilities: {
            textDocumentSync: TextDocumentSyncKind.Incremental,
            // Tell the client that this server supports code completion.
            completionProvider: {
                resolveProvider: true
+                hoverProvider: true
            }
        }
    };

然后,处理 textDocument/hover 请求,在 connection.onCompletionResolve 之后,增加:

connection.onHover((item) => {
    return {
        contents: "hover result"
    };
});

然后重启即可。

插件配置

package.json 中的 contributes.configuration 是用来定义插件的配置的。用户将能够使用设置编辑器或直接编辑 JSON 设置文件,将这些配置选项设置为用户设置工作区设置。对于定义配置项,参考 contributes.configuration

此项目已存在设置的示例,要定义和使用一个配置,需要以下步骤: 使用插件配置有两种模式: 拉取模式推送模式,语言服务协议推荐使用前者。

拉取模式

请求从服务器发送到客户端,从客户端获取配置设置。

connection.onInitialized(() => {
    if (hasConfigurationCapability) {
        // Register for all configuration changes.
-        connection.client.register(DidChangeConfigurationNotification.type, undefined);
    }
    if (hasWorkspaceFolderCapability) {
        connection.workspace.onDidChangeWorkspaceFolders(_event => {
            connection.console.log('Workspace folder change event received.');
        });
    }
+    connection.sendRequest("workspace/configuration", { items: [{ section: "languageServerExample.maxNumberOfProblems" }]}).then(result => {
+        connection.console.log(JSON.stringify(result));
+    });
});

推送模式

推送模式是服务器使用注册模式注册空配置更改,客户端通过事件发出配置更改信号。

  1. 注册配置变更请求 connection.client.register(DidChangeConfigurationNotification.type, undefined),如下:
connection.onInitialized(() => {
    if (hasConfigurationCapability) {
        // Register for all configuration changes.
        connection.client.register(DidChangeConfigurationNotification.type, undefined);
    }
    if (hasWorkspaceFolderCapability) {
        connection.workspace.onDidChangeWorkspaceFolders(_event => {
            connection.console.log('Workspace folder change event received.');
        });
    }
});
  1. 初始化或配置更改会被 connection.onDidChangeConfiguration 的回调函数处理,在此处接收并缓存定义的配置。

使用 rust 开发 vscode 插件

由于 node 是单线程架构,对于性能要求较高的插件来说,难以胜任。所以,建议使用 rust 来开发高性能插件。

为了充分利用 rust 的性能,建议采用异步的 tokio 库,使用 tower-lsp 的语言服务器实现来开发。

为了方便搭建项目,我制作了一个模版,使用以下命令:

git clone https://github.com/ren-wei/rust-lsp-extension-template.git <your-project-name>

打开此项目后在 vscode 中搜索并替换 rust-lsp-extension-template.

搜索 rust_lsp_extension_template_server 然后修复它。

然后,执行以下命令:

cd <your-project-name>
rm -rf .git
git init
npm i

活动事件

插件需要先在根目录的 package.json 文件的扩展字段 activationEvents 中声明激活的条件,只有满足激活条件,插件才会开始工作。下面是所有可选的条件:

已配置的活动事件是 "onLanguage:typescript",表示在 typescript 文件被打开时,激活此扩展。

客户端文档筛选器

文档筛选器 documentSelector 通过不同的属性(如语言、其资源的方案或应用于路径的 glob-pattern)来表示文档,只有符合筛选的文件的打开,修改和关闭,客户端才会发送对应的 textDocument/* 通知。

已配置的筛选器是 [{ scheme: "file", language: "typescript" }],表示扩展只关注 typescript 文件的打开、修改和关闭等活动。

打印日志

服务器预设了日志库 tracing 来帮助打印日志,只需要在任何您需要打印日志的函数或方法中使用 debug!("content"), info!("content"), warn!("content")error!("content") 来打印不同等级的日志。具体使用请参考 使用 tracing 记录日志

语言功能

我们参考 Hover 增加一个 Hover 的功能。

首先在初始化请求的响应中,声明支持 hover 能力:

    async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> {
        Ok(InitializeResult {
            capabilities: ServerCapabilities {
                text_document_sync: Some(TextDocumentSyncCapability::Kind(
                    TextDocumentSyncKind::FULL,
                )),
+                hover_provider: Some(HoverProviderCapability::Simple(true)),
                ..Default::default()
            },
            server_info: Some(ServerInfo {
                name: "rust-lsp-extension-template-server".to_string(),
                version: Some("1.0.0".to_string()),
            }),
        })
    }

然后,处理 hover 请求:

    async fn hover(&self, _params: HoverParams) -> Result<Option<Hover>> {
-        error!("method not found");
-        Err(Error::method_not_found())
+        Ok(Some(Hover {
+            contents: HoverContents::Scalar(MarkedString::String("hover result".to_string())),
+            range: None,
+        }))
    }

然后重启即可。

插件配置

package.json 中的 contributes.configuration 是用来定义插件的配置的。用户将能够使用设置编辑器或直接编辑 JSON 设置文件,将这些配置选项设置为用户设置工作区设置。对于定义配置项,参考 contributes.configuration

此项目已存在设置的示例,要定义和使用一个配置,需要以下步骤: 使用插件配置有两种模式: 拉取模式推送模式,语言服务协议推荐使用前者。

拉取模式

请求从服务器发送到客户端,从客户端获取配置设置。

    #[instrument]
    async fn initialized(&self, _params: InitializedParams) {
        info!("start");
+        let config = self
+            ._client
+            .configuration(vec![ConfigurationItem {
+                scope_uri: None,
+                section: Some("languageServerExample.maxNumberOfProblems".to_string()),
+            }])
+            .await;
+        debug!("{:?}", config);
        info!("done");
    }

推送模式

推送模式是服务器使用注册模式注册空配置更改,客户端通过事件发出配置更改信号。

  1. 注册配置变更请求:
    #[instrument]
    async fn initialized(&self, _params: InitializedParams) {
        info!("start");
+        let _ = self
+            ._client
+            .register_capability(vec![Registration {
+                id: "register-did-change-configuration-id".to_string(),
+                method: "workspace/didChangeConfiguration".to_string(),
+                register_options: None,
+            }])
+            .await;
        info!("done");
    }
  1. impl LanguageServer for LspServer 中新增方法处理配置变更请求:
#![allow(unused)]
fn main() {
    async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
        let _ = params;
    }
}

提示: 此处为了避免更改过多代码,直接使用了 _client,实际使用时,应该将其重命名为 client