使用 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
- onCommand
- onDebug
- onDebugInitialConfigurations
- onDebugResolve
- workspaceContains
- onFileSystem
- onView
- onUri
- onWebviewPanel
- onCustomEditor
- onAuthenticationRequest
- onStartupFinished
- *
已配置的活动事件是 "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");
}
推送模式
推送模式是服务器使用注册模式注册空配置更改,客户端通过事件发出配置更改信号。
- 注册配置变更请求:
#[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");
}
- 在
impl LanguageServer for LspServer
中新增方法处理配置变更请求:
#![allow(unused)] fn main() { async fn did_change_configuration(&self, params: DidChangeConfigurationParams) { let _ = params; } }
提示: 此处为了避免更改过多代码,直接使用了
_client
,实际使用时,应该将其重命名为client
。