IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    neovim入门指南(三):LSP配置(上)

    youngxhui发表于 2023-09-04 10:10:02
    love 0

    🧩 什么是 LSP

    对于一个编辑器来说,如果要完成例如自动补全,查找相关定义等功能是需要进行大量的开发的。不同的编辑器为了不同的语言都需要进行开发,而 LSP 的存在就是将这个过程检化。LSP 的全称为 Language Server Protocol,定义了编辑器和语言服务之间使用的协议。只要相关语言支持 LSP,那么编辑器只要符合相关要求实现即可完成例如自动补全等功能,而且不同的编辑器使用的体验是一致的。

    目前支持 LSP 的编辑器有很多,例如大名鼎鼎的 Vscode。当然 vim 8 以后版本和 neovim 也都支持,具体支持的编辑器/IDE 列表可以看 LSP 的官方网站,同时支持 LSP 的语言也可以找到 支持语言。

    neovim 已经是支持 LSP 了,具体可以在相关的配置文档看到,该文档详细的描述了如何配置一个 LSP。相对来说,配置过程比较繁琐,所以官方又提供了另一个库 nvim-lspconfig。接下来我们就通过这个插件来配置 neovim 的 lsp。

    nvim-lspconfig

    与安装其他插件是一样的,只需要我们在 plugins_config.lua 中添加相关配置即可,这里不进行赘述了。安装完成后其实进行配置就可以启用 LSP 了,就是这么简单。例如支持 rust 的 LSP,只需要进行简单的配置。

    1
    2
    3
    4
    5
    
    lspconfig.rust_analyzer.setup {
      settings = {
        ['rust-analyzer'] = {},
      },
    }
    

    但是,nvim 只是 lsp 的客户端,那么就存在 lsp 的服务端。上面配置的 rust_analyzer 就是 rust 语言的服务端,就需要我们进行服务端的安装。rust_analyzer 的服务端地址是 rust-lang/rust-analyzer,需要将服务端下载并且安装好,这样每次编写rust的时候就会享受 lsp 的服务加成了。

    但是这样做有几个问题,当然也不能算问题,只是不太方便。

    1. 对于多语言使用者来说,需要手动安装多个 lsp 服务端,目前 lsp 的服务端应该是没有统一的下载安装地址,需要手动寻找;
    2. 每次服务端进行更新,都需要重新下载安装;
    3. 新换设备之后,无法开箱即用,需要重复上述的方式重新开始一次。

    面对上面的不方便,你可能已经想到很多解决方法,例如写个脚本进行一键安装和更新常用的 lsp 服务端。这样基本解决了上面说的所有问题。正如你想的那样,今天的第二位主角 williamboman/mason.nvim。

    🗃️ mason.nvim

    mason 是一个可以方便的管理 LSP 服务端,DAP 服务端,Linter 和 格式化工具的插件。安装它之后,上面所说的问题将不是问题。

    为了让 mason 和 nvim-lspconfig 更好的配合,这里还需要安装另一个插件 williamboman/mason-lspconfig.nvim

    同样的安装这里不多赘述,主要是进行相关的配置。这里为了区别其他的插件,我们在 lua 目录下建立新的文件夹-lsp,用来专门存放 lsp 的配置。

    配置 mason

    首先还是加载我们的插件。在 lsp 文件夹中新建 mason.lua 文件,在文件中新增下面的配置。配置主要是在加载插件。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    -- mason.lua
    local mason_status, mason = pcall(require, "mason")
    if not mason_status then
     vim.notify("没有找到 mason")
     return
    end
    
    local nlsp_status, nvim_lsp = pcall(require, "lspconfig")
    if not nlsp_status then
     vim.notify("没有找到 lspconfig")
     return
    end
    
    local mlsp_status, mason_lspconfig = pcall(require, "mason-lspconfig")
    if not mlsp_status then
     vim.notify("没有找到 mason-lspconfig")
     return
    end
    
    
    mason.setup()
    mason_lspconfig.setup({})
    

    📤 安装 lsp 服务端

    配置完成后,重新启动 nvim,此时就可以采用 mason 进行 LSP 的服务端进行管理了。只需要按下 :Mason 即可。你将会看到如下的界面。

    通过界面上的帮助可以看到如何使用,通过数字可以选择不同的服务端项目,2 为 LSP , 3 为 DSP 等。今天只是使用 LSP,可以直接按 2,选择到 LSP 界面,进行 LSP 安装。仍旧是通过 j 和 k 进行滑动。第一个安装的 lsp 服务端为 lua 语言的服务端:lua-language-server。这个是 lua 语言的语言服务,有 lsp 之后,我们之后无论是配置 nvim 还是编写 lua 都会有 lsp 服务的加持。按下 i 进行安装。

    稍等片刻,安装完成。接下来就是配置,让 nvim 知道我们的 lsp 已经安装,在合适的时候进行启动。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    -- mason.lua
    nvim_lsp.lua_ls.setup({
     on_init = function(client)
      local path = client.workspace_folders[1].name
      if not vim.loop.fs_stat(path .. "/.luarc.json") and not vim.loop.fs_stat(path .. "/.luarc.jsonc") then
       client.config.settings = vim.tbl_deep_extend("force", client.config.settings, {
        Lua = {
         runtime = {
          version = "LuaJIT",
         },
         workspace = {
          checkThirdParty = false,
          library = {
           vim.env.VIMRUNTIME,
          },
         },
        },
       })
    
       client.notify("workspace/didChangeConfiguration", { settings = client.config.settings })
      end
      return true
     end,
    })
    

    这样 lua 的 lsp 就配置成功了,当我们编写 lua 脚本的时候,如果发生错误就会有相关提醒。当然这只是 lsp 最基础的功能,例如代码跳转,代码补全等需要我们进行配置。

    基本所有的 lsp 的配置都可以在 server_configurations.md 中找到,当然 lua_ls 也不例外,上面的配置就是直接从文档中复制的 😄。

    当前的 LSP 配置已经支持代码跳转,code action 等功能。例如查看当前变量或者函数的文档,可以使用这个命令 :lua vim.lsp.buf.hover()。

    相关的命令还有其他

    功能 命令
    文档显示 :lua vim.lsp.buf.hover()
    查看定义 :lua vim.lsp.buf.definition()
    重命名 :lua vim.lsp.buf.rename()
    查询实现 :lua vim.lsp.buf.implementation()
    查询引用 :lua vim.lsp.buf.refreences()
    查询声明 :lua vim.lsp.buf.declaration()
    格式化 :lua vim.lsp.buf.format()
    Code action :lua vim.lsp.buf.code_action()

    对于这些基础功能来说,每次需要的时候都在命令模式下敲一堆,速度的确是很慢的。所以,可以将上述的命令定义为快捷键,这样每次只需要进行快捷键进行完成上述功能。

    ⌨️ 快捷键绑定

    打开我们之前设置快捷键的配置文件 keybinding.lua,新增上述功能的配置。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    
    -- keybinding.lua
    -- lsp 快捷键设置
    pluginKeys.lspKeybinding = function(mapbuf)
     -- rename
     mapbuf("n", "<leader>r", ":lua vim.lsp.buf.rename<CR>", opt)
     -- code action
     mapbuf("n", "<leader>ca", ":lua vim.lsp.buf.code_action()<CR>", opt)
     -- go to definition
     mapbuf("n", "gd", ":lua vim.lsp.buf.definition()<CR>", opt)
     -- show hover
     mapbuf("n", "gh", ":lua vim.lsp.buf.hover()<CR>", opt)
     -- format
     mapbuf("n", "<leader>=", ":lua vim.lsp.buf.format { async = true }<CR>", opt)
    end
    

    完成快捷键的配置,那么就可以将快捷键绑定到刚刚配置的 lsp 服务端了。

    1
    2
    3
    4
    5
    6
    7
    8
    
    -- mason.lua
    function LspKeybind(client, bufnr)
     local function buf_set_keymap(...)
      vim.api.nvim_buf_set_keymap(bufnr, ...)
     end
     -- 绑定快捷键
     require("keybinding").lspKeybinding(buf_set_keymap)
    end
    

    接下来可以完成快捷键的绑定。

    1
    2
    3
    4
    5
    6
    7
    
    -- mason.lua
    nvim_lsp.lua_ls.setup({
     on_attach = LspKeybind,
     on_init = function(client)
        -- 省略其他配置
     end,
    })
    

    这样就完成了 lua 的 lsp 的配置,在编写 lua 的时候就可以使用文档查看,code Action 等功能。

    目前这些 lsp 的服务都要手动下载,对于一些日常使用的服务,我们可以通过配置,在第一次加载配置的时候,当机器上没有相关的服务的时候,自动下载,这样来说,基本实现了我们上述提出的问题。

    1
    2
    3
    4
    5
    
    -- mason.lua
    mason_lspconfig.setup({
     automatic_installation = true,
     ensure_installed = { "lua_ls", "rust_analyzer" },
    })
    

    这样配置,如果我们本地没有安装 lua 和 rust 的 lsp,会自动进行下载安装。

    自动补全

    直到目前,在 lsp 的加持下,其实编辑体验已经变得非常棒了,而且开发速率也会大幅提升,虽然 lsp 是支持自动补全功能的,但是上面其实一直没有提及。主要是单单靠 neovim 的功能还不够强大,需要插件的配置。

    hrsh7th/nvim-cmp 是一个采用 lua 编写的补全引擎,通过 cmp 及 cmp 的相关插件,会将 neovim 的自动补全达到一个新的高度。

    这里除了 nvim-cmp,再推荐几个 cmp 的相关插件。更多的相关插件可以在 wiki 中找到

    • hrsh7th/cmp-nvim-lsp 对 lsp 提供的补全信息进行提示;
    • hrsh7th/cmp-path 对文件内的路径进行补全

    配置补全

    同样不赘述安装。在 lsp 文件夹中新建 cmp.lua 文件夹。

    1
    2
    3
    4
    5
    6
    
    -- cmp.lua
    local status, cmp = pcall(require, "cmp")
    if not status then
        vim.notify("找不到 cmp")
        return
    end
    

    剩下了就可以将之前的补全源进行配置。

    1
    2
    3
    4
    5
    6
    7
    
    cmp.setup({
     sources = cmp.config.sources({
      { name = "nvim_lsp" },
     }, {
      { name = "path" },
     }),
    })
    

    此时当我们进行输入的时候就可以看到自动补全的提示了。对于自动补全的提示,上下选择并且上屏,我们可以设置快捷键,来满足我们的使用习惯。

    和之前设置快捷键一样,在 keybinding.lua 中添加相关配置。

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    -- keybinding.lua
    pluginKeys.cmp = function(cmp)
     return {
      -- 出现补全
      ["<A-.>"] = cmp.mapping(cmp.mapping.complete(), { "i", "c" }),
      -- 取消
      ["<A-,>"] = cmp.mapping({
       i = cmp.mapping.abort(),
       c = cmp.mapping.close(),
      }),
      -- 上一个
      ["<C-k>"] = cmp.mapping.select_prev_item(),
      -- 下一个
      ["<C-j>"] = cmp.mapping.select_next_item(),
      -- 确认
      ["<CR>"] = cmp.mapping.confirm({
       select = true,
       behavior = cmp.ConfirmBehavior.Replace,
      }),
     }
    end
    

    最后在 cmp.lua 中使用这些快捷键即可。

    1
    2
    3
    4
    
    cmp.setup({
        -- 省略其他配置
        mapping = require("keybinding").cmp(cmp),
    })
    

    这样便可以完成自动补全的配置了。

    小结

    目前为止,已经完成 nvim 的 lsp 的相关配置,并且添加了自动补全。篇幅限制,剩下如何美化 lsp 提示,美化自动补全等我们下篇再说。



沪ICP备19023445号-2号
友情链接