LSP-language-server-protocol规范学习

avatar
前端工程师 @字节跳动

豆皮粉儿们,又见面了,今天这一期,由字节跳动数据平台的“虫二”,给大家讲一讲LSP-language-server-protocol。

图片

本文作者:虫二

LSP-language-server-protocol规范学习

官方文档: microsoft.github.io/language-se…

vscode LSP文档(可中文阅读): docs.microsoft.com/zh-cn/visua…

yuge最近在学习WebIDE的实现机制,需要实现类似IDE的智能提示、语法检查和解析、悬停文档等交互体验;由于没法直接嵌入进来VSCode,于是想学习相关方面的实现,目标是利用Monaco和LSP实现一个IDE。

LSP(Language Server Protocol) 语言服务协议,该协议定义了在编辑器或IDE与语言服务器之间使用的协议,该语言服务器提供了例如自动补全,转到定义,查找所有引用等的功能;语言服务器索引格式的目标是支持在开发工具中进行丰富的代码导航或者一个无需本地源码副本的WebUI。

分以下几个方面

  • 什么是LSP

  • LSP如何工作

  • IDE 和语言服务器如何交互

  • LSP的功能

  • LSP的提供者和使用者的库(SDK)

LSP概述

什么是LSP

每个开发IDE,都要为语言实现类如自动补全,转动定义,悬停在单词上提供文档的功能,传统上,需要个IDE根据自己的API实现上述工作,即使是相同的功能,也要根据不同IDE实现一遍重复的功能,代码却不同。例如,Eclipse CDT 插件(在 Eclipse IDE 中支持 C/C++)是用 Java 编写的,因为 Eclipse IDE 本身是用 Java 编写的。按照此方法,这意味着在 Visual Studio 代码的 TypeScript 中实现 C/C++ 域模型,在 Visual Studio 的 C# 中实现单独的域模型。

如果开发工具可以重用现有的特定于语言的库,则创建特定于语言的域模型也容易得多。但是,这些库通常在编程语言本身中实现(例如,好的 C/C++域模型在 C/C++中实现)。将 C/C++ 库集成到用 TypeScript 编写的编辑器中,在技术上是可能的,但很难做到。

一个语言服务器旨在提供某个语言的智能化,并通过支持进程间通信的协议与开发工具进行通信。

LSP设计的目标是使该语言服务器和开发工具进行标准化的通信,这个语言服务可以在多个开发工具中重复使用,从而以最小的改动支持多种语言。语言服务器后端可以用PHP,Python或Java编写,LSP可以轻松地将其集成到各种工具中,该协议提供通用抽象级别的协议,以便工具可以提供丰富的语言服务,从而无需完全理解特定于底层域模型的细微差别。

LSP对于语言提供商和工具的提供商都是双赢!

LSP如何工作

语言服务器会作为单独的进程运行,同时开发工具使用基于JSON-RPC的语言协议与服务器进行通信。

以下是一个开发工具和语言服务器在运行编辑期间的通信示例:

图片

  • 用户在IDE中打开文件时: IDE会通知语言服务器文件已打开(textDocument/didOpen),此时文档的内容不会存在于真实的文件系统中,而是保存在IDE内存中。必须在IDE和语言服务器之间同步内容。

  • 用户编辑文件时:IDE会通知语言服务器文件更新(textDocument/didChange),并且语言服务器会更新文件的语言表示。之后语言服务器会分析此消息,并将检测到的错误和警告,响应(textDocument/publishDiagnostics)通知IDE。

  • 用户执行"Goto Definition" 指令时: IDE发送带有参数的请求(textDocument/definition), params: {documentURI, position} 到语言服务器,语言服务器以文档URI和definition的Location作为响应。

以下是一个c++ 语言的例子

图片

用户关闭文件时: IDE通知语言服务器文件关闭(textDocument/didClose), 文件已经不在内存中了,此时当前内容在文件系统中就是最新的。

上述示例说明了协议如何在文档引用(URI)和文档位置级别与语言服务器进行通信。这些数据类型与编程语言无关,适用于所有编程语言。数据类型不在编程语言域模型的级别,后者通常会提供抽象语法树和编译器符号(例如,解析的类型,名称空间等)。事实是,数据类型简单且编程语言无关,从而大大简化了协议。与标准化抽象语法树和跨不同编程语言的编译器符号相比,标准化文本文档URI或光标位置要容易得多。

IDE 和语言服务器如何工作

当用户使用不同的语言时,IDE会为每种编程语言启动语言服务器,以下示例显示IDE如何在一个会话上使用Java和SASS文件。

图片

LSP的 capability

并不是每种语言服务器都可以支持协议定义的所有功能,因此LSP提供了capabilities,一个capabilities可以将一组语言capability组合在一起,IDE和语言服务器使用能力宣布其支持的功能。

例如,语言服务器宣布可以处理textDocument/definition 的请求,但可能不会处理workspace/symbol的请求。同样,IDE宣布在保存文档之前提供 about to save(即将保存)通知功能,以便服务器可以计算文本编辑器在保存之前对已编辑的文档进行格式化。

需要注意:语言服务器到特定IDE的实现集成不是由语言服务器协议定义的,而是由IDE实现者决定

以下是Visual Studio提供的功能

MessageHas Support in Visual StudioSWIDE
initializeyes
initializedyes
shutdownyes
exityes
$/cancelRequestyes
window/showMessageyes
window/showMessageRequestyes
window/logMessageyes
telemetry/event
client/registerCapability
client/unregisterCapability
workspace/didChangeConfigurationyes
workspace/didChangeWatchedFilesyes
workspace/symbolyes
workspace/executeCommandyes
workspace/applyEdityes
textDocument/publishDiagnosticsyes
textDocument/didOpenyes
textDocument/didChangeyes
textDocument/willSave
textDocument/willSaveWaitUntil
textDocument/didSaveyes
textDocument/didCloseyes
textDocument/completionyes
completion/resolveyes
textDocument/hoveryes
textDocument/signatureHelpyes
textDocument/referencesyes
textDocument/documentHighlightyes
textDocument/documentSymbolyes
textDocument/formattingyes
textDocument/rangeFormattingyes
textDocument/onTypeFormatting
textDocument/definitionyes
textDocument/codeActionyes
textDocument/codeLens
codeLens/resolve
textDocument/documentLink
documentLink/resolve
textDocument/renameyes

LSP的提供者和使用者的库(SDK)

为了简化语言服务器和客户端的实现,提供了库或SDK:

  • IDE SDK: 每个开发工具通常都提供一个用于集成语言服务器的库。例如,对于JavaScript / TypeScript,有语言客户端npm模块[1],用于简化语言服务器和vs扩展的集成。

  • 用于实现不同语言的语言服务器SDK: 一个SDK可用于以特定语言实现语言服务器。例如,要使用Node.js实现语言服务器,可以使用语言服务器npm模块[2]。

References

[1] npm模块: www.npmjs.com/package/vsc…

[2] 语言服务器npm模块: www.npmjs.com/package/vsc…

[3] 语LSP-language-server-protocol规范学习: mp.weixin.qq.com/s/leNgwb0fT…

The  End