NeoVim LSP 使用介绍

2021-11-30 vim develop

在 NeoVim 内部已经提供了 Language Server Protocol, LSP 客户端,服务端就需要按照自己的需求进行安装了,目前已经有一些高效的配置插件,可以很简单完成基本环境的配置。

简介

在后台会运行不同语言的 Language Server ,通过 LSP 协议完成常见的操作,而客户端就是类似 NeoVIM、VSCode、Atom 等开发工具了。

NeoVim 支持 LSP 所有特性,通过 vim.lsp 提供 Lua 接口,支持定义跳转 (Definition)、引用查找 (References)、提示信息 (Hover Information)、自动补全 (Autocompletion)、代码重构 (Rename/Refactor)、格式化 (Formatting) 等等。

----- 在浮动窗口显示当前LSP相关的信息,例如文件类型、相关的LanguageServer、根目录等
:LspInfo

常用命令

如果不使用其它插件,可以通过如下函数执行上述提到的特性,也可以简单用来测试。

----- 在 QuickFix 中查看符号,例如变量、函数等
vim.lsp.buf.document_symbol() 
----- 查看光标所在位置的定义,可以跳转到符号的定义位置
vim.lsp.buf.definition()
----- 很多语言是不支持声明的,可以用定义替换
vim.lsp.buf.declaration()
----- 在 QuickFix 显示所有的引用
vim.lsp.buf.references()
----- 在 QuickFix 窗口中显示光标所在位置的实现,并不是所有语言都支持,例如 bash python 就不支持
vim.lsp.buf.implementation()
----- 显示帮助信息
vim.lsp.buf.hover()
----- 重命名当前光标下的符号
vim.lsp.buf.rename()
----- 当遇到报错时,可以用来查看如何进行修复 kosayoda/nvim-lightbulb
vim.lsp.buf.code_action()
----- 显示函数签名信息,需要快捷键,不过 hrsh7th/cmp-nvim-lsp-signature-help 更好用一些
vim.lsp.buf.signature_help()
----- 对文件进行格式化,不过不是所有LanguageServer都支持
vim.lsp.buf.formatting()

简单介绍一些容易混淆的特性:

  • Hover Information 通过悬浮窗口显示光标所在符号的帮助信息,例如如果是 Bash 而且光标在 find 命令处,就会显示 find 的帮助文档;在 Python 的 print() 函数处,则会打印相关的帮助信息。
  • Signature 用来在浮动窗口显示函数/方法的参数信息。

基础配置

NeoVim 仅仅提供了基础的 LSP 客户端,一些常见语言的配置通过 nvim-lspconfig 维护,默认配置已经满足绝大部分场景了,如果需要更详细的配置,可以参考 Server Configurations 中的介绍,而且在官方的介绍中已经包含了很多配置建议。

其中源码的配置保存在 lua/lspconfig/server_configurations 目录下。

Mason.nvim

安装可以使用 mason.nvim 安装 (这是 nvim-lsp-installer 自动完成),提供了不错的 UI、配置、管理等功能,默认会保存在 :echo stdpath("data") 标准目录下,支持的服务端列表以及配置可以直接查看项目的 README.md 文件,可配置自动安装。

默认是保存在 ~/.local/share/nvim/mason 目录下的,如下是常见的命令。

----- 查看状态,支持、已经安装、正在安装的服务端
:Mason

----- 安装、卸载指定的服务
:MasonInstall <package>
:MasonUninstall <package>
:MasonUninstallAll

----- 查看日志
:MasonLog

在配置文件中可以修改按键映射,默认 i 安装、u 升级、X 卸载、U 升级所有,只需要移动到对应的行(插件)位置即可。

语言配置

这里配置起来稍微有点绕,需要先从 mason-lspconfig 查看支持的语言,然后与 mason.nvim 会有个映射关系,详见 Server Mapping 以及相关的配置。

注意,有些工具例如 pyright typescript-language-server 需要依赖 npm 命令,此时需要先安装该命令。

clangd

关键是相关的配置,其中全局参数可以通过 lua/config/mason-lspconfig.lua 中的配置修改。

因为涉及到语法树的解析,该工具会依赖称为 JSON Compilation Database 的文本库,在 Linux 中可以通过 Bear 生成,不过 CMake 从 2.8.5 之后已经开始支持生成,所以,下面使用该工具生成。

很简单,直接添加 set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 选项即可,会自动在编译目录下生成 compile_commands.json 文件。对于简单的项目,也可以直接当前项目下添加 compile_flags.txt 文件,每行包含一个标志。

golang

可以使用官方的 gopls 或者 Github,安装的时候不确定具体原因,如果在任意目录下安装,会无法使用 GOPROXY 的设置,最好在某个 Module 项目目录下。

go install golang.org/x/tools/gopls

如果开启了 module 模式,那么默认保存在 $GOPATH/pkg/mod/cache/download 目录下,而 gopls 不会在该目录下查找,因此会报包不存在的错误,可以通过 go get xxx 命令安装到 $GOPATH 目录下即可。

python

----- 全局安装
# npm install -g pyright

rust

使用 rust_analyzer 插件,如果存在报错可以直接通过 Mason 升级。

常用命令

----- 重新加载LSP服务端
: lua vim.lsp.stop_client(vim.lsp.get_active_clients())
: edit

注意,如果使用的是 Packer.nvim 管理插件,两者会有依赖顺序,配置方法详见官方仓库的 README.md 文件,而每个插件的配置就要参考 lspconfig 仓库了。

基本插件

coc.nvim

基于 Node 的独立进程,对标 VSCode 的实现,而在 NeoVim 中常用的 nvim-cmp 框架是基于 Lua 的。

快捷键

默认的 LSP 的有些功能不是很好看,所以实际上设置的快捷方式会融合其它插件一起使用。

lsp-config

详细的文档可以参考 lsp-config wiki 中的介绍,不同语言设置有所区别。

lspsaga.nvim

基础的 NeoVim 提供了关于 LSP 的实现,不过有些使用不是很方便,该插件可以优化。

常用插件

null-ls.nvim

有些 Language Server 没有提供全部的能力,例如 pyright 就没有提供格式化的能力,所以常规的 vim.lsp.buf.formatting_sync() 就无效,此时就需要使用 null-ls.nvim 提供的绑定能力。

For them to work, you need to install an external formatter called shfmt and hook (merge) shfmt into Neovim LSP client by using 1-2 lines of configuration (I will show you that below). Now, the nvim’s above formatting commands will format your code.

Similarly, I was able to hook Diagnostics and Code-Actions into the Neovim client using shellcheck.

语言配置

因为已经内置了 LSP 的客户端,那么所谓语言配置就是服务端的管理以及部分语言的定制化配置,可以参考官方 Available LSPs(lsp-installer) 以及 Server Configuration(lsp-config) 文档中的介绍,也可以参考 langserver.org 中的介绍。

其它的三方重点配置包括了自动补齐以及代码片段,

不同服务端提高的能力不同,可以通过如下命令查看。

:lua print(vim.inspect(vim.lsp.buf_get_clients()[1].resolved_capabilities))
npm install -g typescript typescript-language-server

vim.api.nvim_set_keymap()

代码诊断

可以通过 Linters 和 LSP 提供诊断功能,原有 diagnostic-nvim 提供的功能已经在 NeoVim 内置实现了,也就是 vim.diagnostic 模块,常见命令有。

vim.diagnostic.goto_prev()
vim.diagnostic.goto_next()
vim.diagnostic.set_loclist()

相关的配置可以通过 lsp-handler 进行配置,详见 :help lsp-handler 的介绍,默认会在异常行后面以虚拟行的形式显示错误信息。

自动补齐

在 NeoVim 中通过 Omni Completion 提供了补全机制,可以在插入模式中通过 <C-X><C-O> 触发补齐操作,通过 <C-X> 会进入到一个特殊的模式,如下是常见的内置补全操作。

快捷键帮助备注
Ctrl-X Ctrl-L:h compl-whole-line整行
Ctrl-X Ctrl-N/P:h compl-current文件内关键字
Ctrl-X Ctrl-K:h compl-dictionary字典
Ctrl-X Ctrl-T:h i_CTRL-X_CTRL-T词典
Ctrl-X Ctrl-I:h compl-keyword当前文件以及包含的文件
Ctrl-X Ctrl-]:h compl-tag标签 Tag
Ctrl-X Ctrl-F:h compl-filename文件名
Ctrl-X Ctrl-D:h compl-define定义或宏
Ctrl-X Ctrl-V:h compl-vimVIM 命令
Ctrl-X Ctrl-U:h compl-function用户自定义
Ctrl-X Ctrl-O:h compl-omni全能 (omni)
Ctrl-X Ctrl-S:h compl-spelling拼写建议
Ctrl-N/P:h compl-generic关键字

而对于语言相关的,实际上是不支持的,所以,需要通过一些插件来实现,如果发现执行慢,通常是因为服务端慢导致的。

completion-nvim

代码跳转

本身 VIM 默认的快捷键就是以 g 开头的,如下是常见的:

  • gd 跳转到本地定义。
  • gD 跳转到全局定义。
  • g* 查找当前字符的定义。
  • g# 与上类似只是反响查找。
  • gg 到首行。
  • gf 跳转到文件定义。
  • g] 跳转到 tag 处。
-- rename
mapbuf('n', '<leader>rn', '<cmd>lua vim.lsp.buf.rename()<CR>', opt)
-- code action
mapbuf('n', '<space>ca', '<cmd>lua vim.lsp.buf.code_action()<CR>', opt)
-- go xx
mapbuf('n', 'gd', '<cmd>lua vim.lsp.buf.definition()<CR>', opt)
mapbuf('n', 'gh', '<cmd>lua vim.lsp.buf.hover()<CR>', opt)
mapbuf('n', 'gD', '<cmd>lua vim.lsp.buf.declaration()<CR>', opt)
mapbuf('n', 'gi', '<cmd>lua vim.lsp.buf.implementation()<CR>', opt)
mapbuf('n', 'gr', '<cmd>lua vim.lsp.buf.references()<CR>', opt)
-- diagnostic
mapbuf('n', 'go', '<cmd>lua vim.diagnostic.open_float()<CR>', opt)
mapbuf('n', 'gp', '<cmd>lua vim.diagnostic.goto_prev()<CR>', opt)
mapbuf('n', 'gn', '<cmd>lua vim.diagnostic.goto_next()<CR>', opt)
-- mapbuf('n', '<leader>q', '<cmd>lua vim.diagnostic.setloclist()<CR>', opt)
-- leader + =
mapbuf('n', '<leader>=', '<cmd>lua vim.lsp.buf.formatting()<CR>', opt)
-- mapbuf('n', '<C-k>', '<cmd>lua vim.lsp.buf.signature_help()<CR>', opt)
-- mapbuf('n', '<space>wa', '<cmd>lua vim.lsp.buf.add_workspace_folder()<CR>', opt)
-- mapbuf('n', '<space>wr', '<cmd>lua vim.lsp.buf.remove_workspace_folder()<CR>', opt)
-- mapbuf('n', '<space>wl', '<cmd>lua print(vim.inspect(vim.lsp.buf.list_workspace_folders()))<CR>', opt)
-- mapbuf('n', '<space>D', '<cmd>lua vim.lsp.buf.type_definition()<CR>', opt)

https://github.com/williamboman/nvim-lsp-installer

https://github.com.cnpmjs.org/clangd/clangd/releases https://github.com/llvm/llvm-project/releases

语法高亮

这里使用的是 nvim-treesitter 插件,其基于 tree-sitter 实现,提供了增量的语法高亮,效率很高,通过 Language Parser 支持多种语言,还支持代码格式化、增量选择以及折叠。

----- 查看支持的语言
:TSInstallInfo
----- 安装指定语言
:TSInstall javascript
----- 确认是否安装成功
:TSModuleInfo
----- 手动打开关闭
:TSBufToggle highlight

因为是实验特性,有些功能可能还有 Bug,可以谨慎使用,或者以后看看代码有可能已经修复。

https://github.com/nvim-treesitter/nvim-treesitter/wiki/Colorschemes

通过 vim.api.nvim_set_keymap() 函数设置快捷键,为了方便使用,可以使用如下方式。

-- 保存本地变量
local map = vim.api.nvim_set_keymap
local opt = {noremap = true, silent = true }

-- 之后就可以这样映射按键了
-- map('模式', '按键', '映射为XX', opt)
<leader>f      :NvimTreeToggle

https://github.com/dstein64/vim-startuptime

调试

使用 nvim-lspconfig 插件时,可以通过 vim.lsp.set_log_level("debug") 打开调试信息,然后通过 vim.cmd("edit"..vim.lsp.get_log_path()) 查看日志文件即可。

可以通过 :checkhealth 检查所有依赖是否满足需求,通常可能是因为部分软件的版本过低导致异常。

对于一些常见

vim.inspect

https://smarttech101.com/nvim-lsp-set-up-null-ls-for-beginners/

https://github.com/RRethy/vim-illuminate https://github.com/folke/noice.nvim

参考