lsp.vim 开发者 API 指南:用 rpc() 与 rpc_a() 调用任意 LSP 方法
【免费下载链接】lspLanguage Server Protocol (LSP) plugin for Vim9项目地址: https://gitcode.com/gh_mirrors/lsp/lsp
lsp.vim 是 Vim9 环境下流行的 Language Server Protocol(LSP)客户端插件,内置的补全、跳转、悬停提示等功能覆盖了绝大多数场景。但当你想调用某个语言服务器特有的扩展方法时,就需要直接操作 RPC 接口。本指南将手把手教你使用rpc()与rpc_a()两个核心 API,在 lsp.vim 中发送任意 LSP 请求,让你的 Vim 开发环境解锁更多能力。
rpc() 与 rpc_a() 有什么区别?先看核心概念
lsp.vim 与语言服务器之间通过 JSON-RPC 消息通信。插件把这一层封装成了两个极易上手的接口:
rpc(method, params, opts):同步请求。发送后阻塞等待服务器回复,直接返回结果,适合在函数里顺序取值的场景。rpc_a(method, params, Cbfunc):异步请求。发送后立即返回请求 ID,服务器回复时再回调你的函数,不阻塞Vim 界面,适合交互频繁的操作。
两者的第一个参数都是 LSP 方法名(如textDocument/signatureHelp),第二个参数是请求参数。这就是"调用任意 LSP 方法"的全部秘诀——只要 LSP 规范里存在的方法,你都可以用这两个函数原样发送。
第一步:用 lsp.Server() 拿到服务器对象
调用 RPC 前,必须先获取当前缓冲区对应的服务器对象:
import autoload 'lsp/lsp.vim' def g:MyRpcDemo() var s = lsp.Server() if s->empty() return endif " 现在就可以调用 s.rpc() / s.rpc_a() 了 enddeflsp.Server()返回空字典表示当前缓冲区没有关联语言服务器(定义见 lsp.vim 中的Server()函数),所以做一次判空是良好的防御习惯。此外它还能顺便提供getPosition()、getTextDocPosition()等辅助方法,帮你把光标位置自动转成 LSP 要求的坐标结构,省去手工换算的麻烦。
同步调用实战:用 rpc() 获取函数签名
假设你想实现一个"括号内也能显示签名"的自定义快捷键,用同步rpc()是最直白的写法。官方文档(lsp.txt 的 Custom LSP Requests 章节)给出了完整范例:
def g:FindSig() var s = lsp.Server() if s->empty() return endif var sig = s.rpc('textDocument/signatureHelp', s.getTextDocPosition(false)) if sig.result->empty() var save = winsaveview() normal! F( if getline('.')[charcol('.') - 1] == '(' sig = s.rpc('textDocument/signatureHelp', s.getTextDocPosition(false)) endif winrestview(save) endif echon "\r\r" if sig.result->empty() echon 'No signature found' else echon sig.result.signatures[0].label endif enddef nnoremap <C-k> :call g:FindSig()<CR>三个要点值得注意:
- 返回结构:
rpc()返回整个 LSP 回复字典,结果在result字段里;出错时返回空字典。 - 智能坐标:
getTextDocPosition(false)自动生成TextDocumentPositionParams,无需自己拼 JSON。 - 超时控制:
rpc()还支持可选参数opts,例如s.rpc(method, params, {timeout: 5000})可自定义等待时长(源码见 lspserver.vim 的Rpc()实现)。
异步调用实战:用 rpc_a() 不阻塞界面
同步请求在服务器响应慢时会卡住 Vim,而rpc_a()能优雅解决。看同样的签名查询用异步怎么写:
def FindSigReply(s: dict<any>, sig: any) echon "\r\r" if sig->empty() echon 'No signature found' else echon sig.signatures[0].label endif enddef def g:FindSigAsync() var s = lsp.Server() if s->empty() return endif s.rpc_a('textDocument/signatureHelp', s.getTextDocPosition(false), FindSigReply) enddef nnoremap <C-k> :call g:FindSigAsync()<CR>rpc_a()的回调函数签名为Cbfunc(lspserver, result, error):第一个参数是服务器对象,第二个是请求结果,第三个是错误信息。若旧代码使用了两参数回调Cbfunc(lspserver, result),插件会自动做向后兼容(见 lspserver.vim 的AsyncRpcCb()),迁移成本几乎为零。
进阶技巧:利用返回的请求 ID 取消请求
rpc_a()的返回值是本次请求的消息 ID,出错时返回 -1。这个 ID 并非摆设——它可以在必要时取消请求。设计交互类功能(如高频触发的悬停提示)时,你可以保存 ID,在用户快速移动光标后取消尚未完成的旧请求,避免过期结果覆盖新内容,让插件体验更跟手。
更省事的方案:LspRequestCustom() 一键发自定义请求
如果不想手写回调,插件还内置了g:LspRequestCustom({name}, {method}, {params})函数(定义见 lspserver.vim)。它按服务器名称找到当前缓冲区的实例,异步发送自定义方法并把回复交给统一的处理器,适合快速验证某个服务器扩展接口是否可用。比如:
:call g:LspRequestCustom('tsserver', 'workspace/executeCommand', {'command': 'myCustomCommand'})什么时候该用哪个?选型建议
- 一次性查询、逻辑线性:选
rpc(),代码短、易读,适合脚本和一次性命令。 - 高频触发、需要流畅交互:选
rpc_a(),不阻塞界面,配合请求 ID 可做取消控制。 - 快速验证服务器扩展接口:优先
LspRequestCustom(),无需自己写回调。
掌握rpc()与rpc_a()后,lsp.vim 就不再只是一个"开箱即用"的客户端,而是完全可编程的 LSP 开发平台。无论是调用textDocument/prepareRename、workspace/executeCommand还是各家服务器私有扩展方法,你都能信手拈来,把编辑器真正打造成自己的效率工具。
【免费下载链接】lspLanguage Server Protocol (LSP) plugin for Vim9项目地址: https://gitcode.com/gh_mirrors/lsp/lsp
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考