很抱歉我决定结束这个系列的内容了。原本我打算介绍markdown、orgmode相关的配置，甚至还打算介绍如何在vim 中使用 emacs 的 org-agenda 来进行日常的任务管理。但是出于一些原因我打算放弃了。首先如果将markdown 理解为另一种类似于HTML 的标记语言的话，我们在介绍LSP 的时候已经介绍过该如何新增新的编程语言的支持，再另外介绍Markdown 的配置就显得多余了。而且本系列也并不打算事无巨细的带领大家从零开始配置一套完整的配置，我仅仅希望通过这一系列的内容介绍一下vimscript 或者lua 接口以及vim 的一些特性，让大家看完之后又能力自行动手弄出一套属于自己的配置。至于orgmode 的内容，我发现目前还没有任何插件能完美的模拟emacs 的orgmode 功能。vim 上的插件也仅仅能做到渲染样式，语法高亮而已。也就没有必要单独介绍了。如果后续我能掌握 emacs 的话，再来介绍也不迟总之就是本系列到此结束了。一些建议不知道各位小伙伴在跟着我这一系列文章尝试自己配置vim 的时候有什么感觉？我当初在整理这些配置的时候发现它越来越像vs code ，甚至最近几年新推出的LSP以及 DAP 的一些插件几乎都是原生的用于 vscode 上的或者从它上面移植过来的。有些主题也是照搬 vscode 的。我们发现自己费劲心力终于将vim 变成的 vscode 。有没有觉得在做无用功？既然要将它变成 vscode 那为何不直接使用 vscode 呢？可能有人会说， vscode 对于vim的一些模式和 ex 命令的支持并不好。我想这就是我们使用 vim 的理由，也是vim 比其他编辑器强的地方。我们仅仅是在使用工具而已，哪个工具好用，哪个工具能帮助我们快速完成工作，那就用哪个。工具本身没有高低贵贱之分，只有合适与否的差异。作为程序员要拥抱新技术，千万不要抱着某个技术某个工具不放。也不要觉得用vim 的比用 vscode 或者其他编辑器的高级，就高人一等。vim自身也在吸收其他技术不断的成长，例如它从 vscode 那边学来了LSP 和 DAP 。这就有点像武侠小说中的吸功大法，集万物所长为我所用。另外一条建议就是千万不要拿我给出的配置直接来进行使用。这一套配置仅仅是为了教学使用，很多地方没有进行深度定制，并且基本采用白话的写法，完全不考虑封装性和程序设计，另外我也没有考虑通用性，很多小伙伴评论出现了各种各样的问题，最后就是它的效率也不算高。我也不希望自己的文章仅仅给各位小伙伴提供了一套配置。我更希望小伙伴们能通过这一系列文章学到一点东西，从这套配置中衍生出一套适合自己的内容。若干年以后，各位小伙伴在对vim有更深的理解回过头来看到这套配置时可能发出这样的声音：“这是什么破烂配置，连 xxx 的支持都没有；有些功能有时候会报错，我看看把它改好；启动时间咋这么慢，我能把它优化到xx毫秒；现在还在用xx技术早就落伍了，看我把它改成用xx技术”。（我自认为本系列最有价值的是开始配置之前，vim相关特性的介绍）最后的一条建议就是，如果各位小伙伴未来将长时间使用vim 进行代码的编写和日常的开发。那么我推荐使用一些社区比较活跃的第三方通用配置，例如我最近在使用的lunarVim。使用这类的配置有一些好处：不用费力折腾配置，节约时间学习高手的配置，提升自己对编辑器的审美。就像没学习vim之前我一直觉得使用编辑器用鼠标选中文本是天经地义的事，我习惯了它，甚至习惯了用鼠标翻页等操作，完全不知道这样有多么的浪费时间。通过高手配置可能能使你重新审视自己使用编辑器的习惯，从而找到一套真正适合自己的高效的文本操作术。社区活跃的话，除了问题不用自己死磕，可能有人能帮忙解决PS: 如果各位觉得我的教程不好或者有些内容没有提到，各位可以去看看lunarVim作者的另一个项目，Neovim-from-scratch 该项目也是从0开始配置vim，并且在油管上有对应的教学视频。后面的学习通过本系列的学习相信各位小伙伴已经有能力能看懂各种第三方配置的代码，能在此基础之上衍生出一套属于自己的配置。甚至能完全抛弃第三方配置独立弄出一套自己的配置。所以后面我推荐的学习路线就是：不断阅读vim官方手册熟练使用某一个第三方配置在熟练的基础之上根据自己的习惯来定制一些只属于自己的功能形成一套只属于自己的科学的、高效的文本操作习惯根据这套习惯尝试定制自己的配置在其他编辑器中通过一定的配置尝试复刻这一套科学而又高效的操作习惯目前我正在第三部分努力。希望本系列文章能带领大家真正入门vim ，不会再出现因为觉得难而中途放弃。最后祝愿各位小伙伴在vim的使用中能收获快乐，并坚持下去！
很抱歉又拖更了这么久了，在这个新公司我想快速度过试用期，所以大部分的精力主要花在日常工作上面。但是这个系列还是得更新下去，平时只能抽有限的业余时间来准备。这就导致我写这些文章就慢了一些。废话不多说，咱们正式开始有关git相关的配置。这些配置都是根据我自身使用习惯来定义的，不一定符合各位的习惯，各位可以根据自身的习惯来调整gitsigns第一个要推荐的插件是 gitsigns。就像它的名字一样，该插件可以将最近的更改以标签的形式展现出来方便我们查看。我们可以使用这样的代码进行安装 use {'lewis6991/gitsigns.nvim' }。需要注意最新的版本需要 neovim 的版本在0.7 以上。安装完成之后我们通过配置 require('gitsigns').setup()来使用它。这样我们就可以通过 Gitsigns toggle_signs来打开或者关闭符号显示了。除了采用最基本的符号显示以外，它还可以对改变位置的行号进行标记以及高亮显示变更的行。这两个功能可以通过 Gitsigns toggle_numl和 Gitsigns toggle_linel来打开，打开之后显示如下：从图中可以看到，更改行行号被用绿色显示了出来并且更改行进行了高亮显示另外它还有其他的显示效果：它主要的一些显示功能主要有下面几个：toggle_signs: 显示变更记录toggle_numl: 显示变更行号toggle_linel：高亮变更的行toggle_delete: 显示被删除的行，以红色背景高亮显示toggle_word_diff: 在两行分别显示修改前和修改后的内容toggle_current_line_blame: 在对应行后面显示提交记录我们将所有的这些功能都打开将得到这么一个效果是不是看着有点乱？这是我修改了一处的，一旦修改多了看着会更混乱。所以我自己的经验告诉我在这个buffer里面最好是只打开 signs、numl、linel、current_line_blame的功能，其他的都关掉。也就是在当前buffer中只显示现在的代码，然后辅助以简单的符号来显示哪行是新加的，哪行被删除了，哪行被修改了，至于修改前是什么样子的，我可以通过其他方式来查阅。所有内容都在一个buffer 中显示会比较乱，不利于阅读代码。我们可以通过配置将我们要显示的内容进行定义，也可以定义使用何种图标来表示修改记录。我们采用如下的配置gitsigns.setup({ signs = { add = {hl = 'GitSignsAdd' , text = '+', numhl='GitSignsAddNr' , linehl='GitSignsAddLn'}, change = {hl = 'GitSignsChange', text = '│', numhl='GitSignsChangeNr', linehl='GitSignsChangeLn'}, delete = {hl = 'GitSignsDelete', text = '-', numhl='GitSignsDeleteNr', linehl='GitSignsDeleteLn'}, topdelete = {hl = 'GitSignsDelete', text = '-', numhl='GitSignsDeleteNr', linehl='GitSignsDeleteLn'}, untracked = {hl = 'GitSignsAdd', text = '+', numhl='GitSignsAddNr' , linehl='GitSignsAddLn'}, changedelete = {hl = 'GitSignsChange', text = '~', numhl='GitSignsChangeNr', linehl='GitSignsChangeLn'}, }, signcolumn = true, -- Toggle with `:Gitsigns toggle_signs` numhl = true, -- Toggle with `:Gitsigns toggle_numhl` linehl = true, -- Toggle with `:Gitsigns toggle_linehl` word_diff = false, -- Toggle with `:Gitsigns toggle_word_diff` watch_gitdir = { interval = 1000, follow_files = true }, attach_to_untracked = true, current_line_blame = false, -- Toggle with `:Gitsigns toggle_current_line_blame` current_line_blame_opts = { virt_text = true, virt_text_pos = 'eol', -- 'eol' | 'overlay' | 'right_align' delay = 1000, ignore_whitespace = false, }, current_line_blame_formatter = '<author>, <author_time:%Y-%m-%d> - ', sign_priority = 6, update_debounce = 100, status_formatter = nil, -- Use default max_file_length = 40000, -- Disable if file is longer than this (in lines) preview_config = { -- Options passed to nvim_open_win border = 'single', style = 'minimal', relative = 'cursor', row = 0, col = 1 }, yadm = { enable = false }, })其中大部分都是官方给出的默认配置，我只是在此基础之上改了 signs在各种状态下显示的图标，以及显示哪些内容。主要是使用 + 来表示新增、| 来表示修改、- 表示删除。除了显示以外它有一个重要的功能就是在各种修改状态之间跳转，例如调用 next_hunk来跳转到下一个更改位置。并且它也集成了一些git的操作。我们对常用的操作定义一些快捷键on_attach = function() vim.api.nvim_set_keymap("n", "<leader>gj", "<cmd>Gitsigns next_hunk<CR>", {silent = true, noremap = true}) vim.api.nvim_set_keymap("n", "<leader>gk", "<Cmd>Gitsigns prev_hhunk<CR>", {silent = true, noremap = true}) vim.api.nvim_set_keymap('n', '<leader>hs', ':Gitsigns stage_hunk<CR>', {silent = true, noremap = true}) vim.api.nvim_set_keymap('v', '<leader>hs', ':Gitsigns stage_hunk<CR>', {silent = true, noremap = true}) vim.api.nvim_set_keymap('n', '<leader>hr', ':Gitsigns reset_hunk<CR>', {silent = true, noremap = true}) vim.api.nvim_set_keymap('v', '<leader>hr', ':Gitsigns reset_hunk<CR>', {silent = true, noremap = true}) vim.api.nvim_set_keymap('n', '<leader>hS', '<cmd>Gitsigns stage_buffer<CR>', {silent = true, noremap = true}) vim.api.nvim_set_keymap('n', '<leader>hu', '<cmd>Gitsigns undo_stage_hunk<CR>', {silent = true, noremap = true}) vim.api.nvim_set_keymap('n', '<leader>hR', '<cmd>Gitsigns reset_buffer<CR>', {silent = true, noremap = true}) vim.api.nvim_set_keymap('n', '<leader>hp', '<cmd>Gitsigns preview_hunk<CR>', {silent = true, noremap = true}) vim.api.nvim_set_keymap('n', '<leader>hb', '<cmd>lua require"gitsigns".blame_line{full=true}<CR>', {silent = true, noremap = true}) vim.api.nvim_set_keymap('n', '<leader>tb', '<cmd>Gitsigns toggle_current_line_blame<CR>', {silent = true, noremap = true}) vim.api.nvim_set_keymap('n', '<leader>hd', '<cmd>Gitsigns diffthis<CR>', {silent = true, noremap = true}) vim.api.nvim_set_keymap('n', '<leader>hD', '<cmd>lua require"gitsigns".diffthis("~")<CR>', {silent = true, noremap = true}) vim.api.nvim_set_keymap('n', '<leader>td', '<cmd>Gitsigns toggle_deleted<CR>', {silent = true, noremap = true}) vim.api.nvim_set_keymap('o', 'ih', ':<C-U>Gitsigns select_hunk<CR>', {silent = true, noremap = true}) vim.api.nvim_set_keymap('x', 'ih', ':<C-U>Gitsigns select_hunk<CR>', {silent = true, noremap = true}) end这里是我将官方给出的配置直接粘贴了过来，这堆快捷键定义的操作中我最常用的就是 next_hunk和 prev_hunk来在各种修改之间进行跳转。虽然偶尔也用用 diffthis来显示差异，但这部分我更喜欢使用我接下来介绍的插件diffview这个插件从名字上看就知道是专门用来查看版本差异的插件。与前面介绍的 gitsigns插件相比它有下面几个优点：它是专门用来显示差异的，与gitsigns相比，显示的更加明显它可以在文件树中显示有变更的文件它可以做到任意版本之间的差异对比它可以显示单个文件的版本提交记录它还有另外的功能，可以由各位小伙伴根据官方文档自行了解。它的使用方式如下：:DiffviewOpen显示当前与上一个版本之间的差异:DiffviewOpen + 版本号 可以显示当期与某一个特定版本的差异，例如 :DiffviewOpen HEAD~2或者:DiffviewOpen 906ddac317来查看版本差异● :DiffviewFileHistory + 文件名 来查看某个文件的版本差异因为它比较简单，具体的用法就不在这里演示了。关于它的配置，我自己认为平时很少使用，而且默认的配置已经够用了，也不太需要花精力为它优化快捷键了。lazygitlazygit是一个非常好用的git客户端，可以方便的进行提交、回滚、查看变更等git操作。这里我不推荐什么插件，因为它本身已经很强大了，而且脱离vim它也可以很好的工作。想来想去只有配置一个快捷键来快速打开 lazygit 的终端不知道各位小伙伴是否还记得之间介绍的 toggleterm 插件，我们将要依赖它来快速启动local lazygitterm = Terminal:new({ cmd = 'lazygit', direction = 'float' }) function lazygit_toggle() lazygitterm:toggle() end vim.api.nvim_set_keymap("n", "<leader>lg", "<Cmd>lua lazygit_toggle()<CR>", {noremap = true, silent = true})通过这样简单的配置我们已经可以使用 <leader>lg来快速启动 lazygit的客户端了。至此我们关于git的配置就完成了。一般我的使用习惯是使用 gitsigns来在更改中进行跳转，用于提交前或者合并分支前的代码审查，做到提交和合并都心中有数。在发生bug要回溯代码并且查看当前与没有问题的版本之间的差异会用到 diffview插件。在进行提交、合并、回溯等git相关操作时会使用到 lazygit。各位小伙伴也可以根据自己的使用习惯来定制这一部分的配置。
很抱歉这么久才来更新这一系列，主要是来新公司还在试用期，我希望在试用期干出点事来，所以摸鱼的时间就少了。加上前面自己阳了休息了一段时间。在想起来更新就过去一个多月了。废话不多说了，让我们开始进入正题。在前一章，我们谈论了如何在 neovim 中使用cpptools 这个DAP 的适配器对代码进行调试，目前针对编译型和解释型语言来说我们都有了对应的方法来配置调试器对其进行调试。本节将要介绍关于dap的其他一些功能，主要包括 repl窗口和 gdb的集成repl 窗口什么是 repl 呢？它的全称是 Read Eval Print Loop 中文一般翻译为交互式解析器，可能看到这你还是一脸懵逼，你可以想想 python 或者nodejs，在控制台输入python 就可以进入到它的交互式解析器中，随着我们输入python 的语句，它会实时的给出运行的结果。交互式解析器就是这么一个东西，输入命令，它给你一个实时的结果。在调试中使用交互式解析器还是很有用的，比如我想显示当前某个变量的值，当前执行到哪个语句了等等。nvim-dap已经提供了一个内置的 repl 窗口，我们每次启动调试的时候都会看到它每次都会创建一个新的名为 dap-repl的buffer。之前我们没怎么关注它主要还是因为那个时候的重点在于如何进行调试，现在我们将要来优化这部分的显示和功能。首先我们发现每次调试结束的时候这个buffer 都会被遗留，需要我们手动的进行关闭，除了针对buffer通用的 :q 或者 :bd命令进行关闭，还可以使用 :lua require('dap').repl.close()。当然也可以配套使用 :lua require('dap').repl.open()来打开一个 repl的窗口，既然每次都会自动新建，那么这里我们就不需要进行新建，主要用于想办法关闭就可以了。还记得之前介绍 nvim-dapui 插件的时候介绍的那两个监听函数吗，同样的我们要在监听调试结束的函数中添加代码来关闭repl 窗口，函数的整个代码如下dap.listeners.before.event_terminated["dapui_config"] = function() dapui.close({}) dap.repl.close() end dap.listeners.before.event_exited["dapui_config"] = function() dapui.close({}) dap.repl.close() end再启动调试的时候发现它已经可以在调试结束的时候自动关闭了。还有另外一个问题就是我不太喜欢现在这样在最下角显示 repl。我希望它能够在最下方以整行的形式显示。或者可以方便的不显示，只有在需要的时候显示。要达成这个目的我们需要修改 dapui 的配置。dapui 的配置主要以 element为基础，每个 element 代表一个提供对应功能的窗口。我们先来根据它默认的配置来讲解每部分的含义require("dapui").setup({ icons = { expanded = "", collapsed = "", current_frame = "" }, mappings = { -- Use a table to apply multiple mappings expand = { "<CR>", "<2-LeftMouse>" }, open = "o", remove = "d", edit = "e", repl = "r", toggle = "t", }, -- Use this to override mappings for specific elements element_mappings = { -- Example: -- stacks = { -- open = "<CR>", -- expand = "o", -- } }, -- Expand lines larger than the window -- Requires >= 0.7 expand_lines = vim.fn.has("nvim-0.7") == 1, -- Layouts define sections of the screen to place windows. -- The position can be "left", "right", "top" or "bottom". -- The size specifies the height/width depending on position. It can be an Int -- or a Float. Integer specifies height/width directly (i.e. 20 lines/columns) while -- Float value specifies percentage (i.e. 0.3 - 30% of available lines/columns) -- Elements are the elements shown in the layout (in order). -- Layouts are opened in order so that earlier layouts take priority in window sizing. layouts = { { elements = { -- Elements can be strings or table with id and size keys. { id = "scopes", size = 0.25 }, "breakpoints", "stacks", "watches", }, size = 40, -- 40 columns position = "left", }, { elements = { "repl", "console", }, size = 0.25, -- 25% of total lines position = "bottom", }, }, controls = { -- Requires Neovim nightly (or 0.8 when released) enabled = true, -- Display controls in this element element = "repl", icons = { pause = "", play = "", step_into = "", step_over = "", step_out = "", step_back = "", run_last = "", terminate = "", }, }, floating = { max_height = nil, -- These can be integers or a float between 0 and 1. max_width = nil, -- Floats will be treated as percentage of your screen. border = "single", -- Border style. Can be "single", "double" or "rounded" mappings = { close = { "q", "<Esc>" }, }, }, windows = { indent = 1 }, render = { max_type_length = nil, -- Can be integer or nil. max_value_lines = 100, -- Can be integer or nil. } })首先最上面的 icons 表示，各个部分显示的图标，这里分别定义了展开的，合并的以及当前位置的图标信息，我们可以观察一下变量栏或者调用栈显示信息的左侧就可以看到这里定义的图标。mappings 代表的是部分窗口动作定义的快捷键。例如上面定义的 expand = { "\<CR>", "" }表示可以在待展开项上按下回车或者鼠标左键双击来展开。element_mappings表示的是我们为某些窗口特意定制的一些快捷键。例如上面注释的是针对调用栈定义的快捷键。layouts代表布局，每个布局都有一个子table，而每个子table主要由 elements、size、position组成，它们分别代表采取该种布局的元素(也可以说是窗口)，窗口大小以及窗口的位置。窗口一般通过id来描述，每种窗口都有固定的ID，根据官方文档的描述，它支持这么几种窗口:scopes显示全局或者当前局部变量，它支持的操作主要是 edit编辑变量的值、expand展开结构化的变量、repl将变量拷贝到repl窗口stacks显示当前正在运行的线程以及它们对应的调用栈，它主要支持的操作是 open :运行代码到当前被选中的位置， toggle:打开或者关闭该窗口watches显示我们需要追踪的变量，它支持的主要操作是 edit: 输入想要追踪的变量或者给对应的变量赋值。expand: 展开结构化的变量，remove:删除当前监视的变量，repl:将变量拷贝到repl窗口breakpoints显示当前激活的断点。它支持的主要操作有 open:执行代码到当前选中的断点处, toggle :激活或者使当前断点无效repl显示repl窗口console显示控制台窗口这些窗口的这些操作的快捷键我们已经通过上方的 mappings做了定义了，只要保持光标在对应窗口然后按下快捷键就可以执行对应的窗口命令了。如果想要单独对窗口进行快捷键定义可以在element_mappings 中被注释的代码controls 部分配置的是在repl窗口上方显示的那一堆调试按钮。由于我们定义了一些快捷键，这些按钮没太大的作用。这里我不需要它显示调试用的按键，所以我就在 controls 项中设置 enabled = false 禁用它。floating、window、render则定义的是悬浮窗口样式和普通窗口的一些样式。这里就不深究了。下面是改造之后的配置dapui.setup({ icons = { expanded = "", collapsed = "", current_frame = "" }, mappings = { -- Use a table to apply multiple mappings expand = { "<CR>", "<2-LeftMouse>" }, open = "o", remove = "d", edit = "e", repl = "r", toggle = "t", }, layouts = { { elements = { { id = 'scopes', size = 0.35 }, {id = "stacks", size = 0.35}, {id = "watches", size = 0.15}, {id = "breakpoints", size = 0.15}, }, size = 40, position = "left", }, { elements = { "repl" }, size = 5, position = "bottom", } }, controls = {enabled = false}, floating = { max_height = nil, -- These can be integers or a float between 0 and 1. max_width = nil, -- Floats will be treated as percentage of your screen. border = "single", -- Border style. Can be "single", "double" or "rounded" mappings = { close = { "q", "<Esc>" }, }, }, windows = { indent = 1 }, })repl 窗口的主要命令如下:.exit: 退出/关闭一个 repl 窗口.c/.continue: 继续执行代码.n/.next: 执行下一行代码.into: 跳转到函数中继续执行.out: 跳出函数.scopes: 打印当前栈的一些变量信息.threads: 打印线程信息.frames: 打印当前线程的调用栈.capabilities: 打印当前适配器实现的一些功能.p 暂停当前运行的程序更多的命令可以通过在 repl窗口中输入 .help查看看了这么多无聊的文字描述，不知道小伙伴们有没有觉得头晕眼花呢？我们来通过实际的例子来看看如何应用这些内容来进行调试。示例1：调试单线程死循环假设有一段程序在不知不觉中被写成死循环了，程序无法正常执行下面的操作，我们以下面的程序为例#include <stdio.h> #include <unistd.h> void loop_forever(){ for(unsigned int i = 10; i >= 0; --i){ // do something sleep(0.1); } } int main (int argc, char *argv[]) { // do something loop_forever(); printf("do other things\n"); return 0; }本来我们想的是它执行完函数 loop_forever之后会执行接下来的操作，但是我们死活看不到它执行后面的操作，这个时候我们意识到它可能在某个地方陷入死循环，无法出来了，假设前后都有大断的代码，无法快速定位到死循环的位置，该如何处理这种情况呢？我们先通过<F5>来执行操作，然后在 repl 中输入 i 进入插入模式，然后执行.p 中断当前程序执行。此时程序已经断了下来，接着我们输入.frames 查看当前调用栈信息。我们发现此时程序停留在loop_forever 函数的 sleep 中，我们在栈中找到 sleep 的位置并按下回车，这个时候我们发现程序执行到了 sleep() 函数处了。这个时候我们在这里按下<F9>下一个断点，接着使用 <F5> 继续运行到断点位置停止，这个时候我们通过实时显示的 i值已经发现问题所在了。原来i 递减到0之后，继续递减，因为它是无符号数所以永远无法达成小于0 的条件。示例2：调试多线程死锁我们以下面一个多线程程序为例#include <cstdio> #include <thread> #include <vector> using namespace std; void func(int thread_id){ int var = thread_id; while(true); } int main (int argc, char *argv[]) { vector<thread> vec; for(int i = 0; i < 5; i++){ vec.push_back(thread(func, i)); } // join for(int i = 0; i < 5; i++){ vec[i].join(); } return 0; }因为使用了c++ 相关的内容和 thread 库，因此编译的时候需要使用 g++ 并且指定链接 pthread 库，我们采用 g++ main.cpp -g -o main -lpthread 来编译它我们还是按照之前的步骤，先按下 .p来暂停程序，这个时候我们发现它会提示我们需要暂停哪个线程，遗憾的是根据线程的id还没法判断具体哪个是子线程哪个是主线程。这里我们随便选一个暂停。然后执行 .threads查看当前线程信息，在某个线程下使用回车键可以看到调用的函数栈。我们发现子线程卡在while 这句话，我们还是一样在卡主的位置按下回车跳转到对应代码位置，在此处下一个断点。然后我们在对应线程位置按下 o 命令来继续执行之前暂停的线程。这样就完美的找到了线程卡死的位置了。后面可以使用 .c 来继续执行所有被中断的线程nvim-gdb 插件该插件提供了一种方式，可以直接在neovim中进入gdb的session。例如我们可以通过命令 :GdbStart gdb -q a.out来启动一个gdb会话，并且关联了一个 a.out 的程序。后续可以直接使用gdb相关的命令来启动调试这个程序。我们可以使用如下的代码来安装它use {"sakhnik/nvim-gdb", run="./install.sh"}我们先来试试效果，直接使用快捷键 <leader>dd 来加载一个程序进行调试。进入到gdb会话之后可以使用gdb 的命令。例如我们使用 b main来在 main函数的位置打一个断点，然后通过r来启动程序运行到断点处。接着可以使用 n来执行下一步或者使用 c来直接运行到下一个断点。最后可以使用 q退出基础配置我们发现使用 nvim-gdb 插件的时候会在对应代码位置显示断点或者当前执行行。这里我们对它做一些配置，先统一使用nvim-gdb和 nvim-dap这两种情况下的显示信息。先创建一个新的配置文件为 nvimgdb.lua作为它的配置文件。因为它暂时还不支持lua的配置所以这里我们使用vim原生的写法。vim.cmd([[ let g:nvimgdb_config_override = { \ 'sign_breakpoint': [''], \ 'sign_current_line': '', \ } ]])在这里定义了断点和执行到当前行的配置，这里只能统一图标，还无法做到完全像使用 dap那样显示。另外我们可以在 nvimgdb_config_override 这个变量中定义如下快捷键以保证在调试时拥有同样的体验 \ 'key_next': '<F10>', \ 'key_step': '<F11>', \ 'key_continue': '<F5>',最后我们发现当我们输入文件名进行调试的时候，它会一直闪屏。这是因为它会不断根据我们输入的内容在文件系统中匹配合适的可执行文件，为了解决闪屏问题我们屏蔽它的这个特性，我们可以使用如下的配置来解决这个问题 let g:nvimgdb_use_find_executables = 0 let g:nvimgdb_use_cmake_to_find_executables = 0显示窗口的配置定义了显示形式，我们来定义显示的窗口，这里我让它显示常用的像调用栈，变量，以及watch窗口。我们先使用如下代码来将窗口分割为左右两个部分 let w:nvimgdb_termwin_command = "rightbelow vnew" let w:nvimgdb_codewin_command = "vnew"根据官方文档的描述，nvimgdb_termwin_command 是终端窗口，用来显示repl 相关信息并且与用户交互，后面我们可以对它进行切割用于显示其他信息。nvimgdb_codewin_command 是源代码窗口。这两句代码可以形成一个左右分屏的界面，左侧显示代码，右侧显示repl窗口。在gdb成功加载之后，我们可以使用命令 :GdbCreateWatch info locals来创建一个显示当前变量的窗口。默认是新创建一个窗口来显示，但是我们可以在命令前加上 belowright来指定在右侧继续分屏，它会在右下角新建窗口来显示变量。需要查看其它窗口可以对应传入不同的参数，例如传入 breakpoints来显示所有断点信息。传入的参数就是gdb中接收的对应参数。有了这些基础我们就可以对其进行配置了，我们要实现的目标就是当gdb成功加载的时候自动加载这些窗口。在vim中要实现自动化我们目前知道有两种方式，第一种使用自动命令，第二种使用插件配置中提供的回调函数。遗憾的是在这个插件中我没有找到回调函数，因此我们只能采用自动命令这种方法。根据官方的文档，我们主要使用这么两个事件——NvimGdbStart和 NvimGdbCleanup。它们一个是成功加载gdb的时候触发，一个是关闭gdb会话的时候触发。vim.cmd(augroup GdbSession autocmd! autocmd User NvimGdbStart :lua StartGdbSession() autocmd User NvimGdbCleanup :lua EndGdbSession() augroup END)在 StartGdbSession 函数中写入如下代码来完成窗口的配置StartGdbSession = function() vim.api.nvim_command(":belowright GdbCreateWatch backtrace") vim.api.nvim_command(":wincmd h") vim.api.nvim_command(":belowright GdbCreateWatch info locals") vim.api.nvim_command(":set wrap") vim.api.nvim_command(":wincmd k") end不知道各位小伙伴能不能理解这段代码是如何在分屏的。首先启动gdb的时候会将整个屏幕纵向分为两个部分，左侧为 code右侧为 repl窗口接着我们执行 :belowright GdbCreateWatch backtrace 它会在右下方创建一个窗口用来展示调用栈然后执行 :wincmd h来将光标移动到代码窗口上继续执行 :belowright GdbCreateWatch info locals它会在代码窗口的下方新增一个窗口用于显示变量的信息这样就将窗口分为4个部分了，左上部分显示代码，左下部分显示变量信息。右上部分是repl窗口，右下部分显示变量信息。最后我们通过 :set wrap设置窗口中自动换行，不然有些内容显示在一行不容易查看。通过 :wincmd k移动光标到 repl窗口。方便后续调试启动之后他的效果如下最后我们在结束gdb的时候做一些收尾工作，关闭我们创建的窗口EndGdbSession = function() vim.api.nvim_command(":bdelete! backtrace info locals") end这里我是根据buffer的名称来进行删除。最后的效果如下到此我们已经介绍了关于dap 的所有配置，至于其他语言相信各位小伙伴根据官方给出的示例可以独立完成配置，这里就不一一介绍了。
首先给大家说一声抱歉，前段时间一直在忙换工作的事，包括但不限于交接、背面试题准备面试。好在最终找到了工作，也顺利入职了。期间也有朋友在催更，在这里我对关注本系列的朋友表示感谢。多的就不说了，我们正式进入vim 的配置吧上一节通过配置 python 的调试环境，我们大概了解了配置 dap 的基本步骤。首先需要一个 dap 的客户端负责在编辑器上显示各种调试信息，并且与用户进行交互。然后需要一个服务端，与客户端通信并完成调试的实际步骤。然后需要配置两个东西， dap.adapters 用来配置如何启动调试器，dap.configurations用来配置如何将当前项目加载到调试器上。本篇我们进一步配置 dap。让它变得更好用，并且介绍编译型语言(C/C++)调试的配置。优化界面回顾一下上一篇中在演示图片里面看到的效果。默认界面在断点位置以 B 来标识，当前运行的代码以 -> 来标识。看起来不那么的直观，我们先对它进行优化，我们采用 Visual Code 的调试图标来进行标识我们采用以下代码进行配置local dap_breakpoint_color = { breakpoint = { ctermbg=0, fg='#993939', bg='#31353f', }, logpoing = { ctermbg=0, fg='#61afef', bg='#31353f', }, stopped = { ctermbg=0, fg='#98c379', bg='#31353f' }, } vim.api.nvim_set_hl(0, 'DapBreakpoint', dap_breakpoint_color.breakpoint) vim.api.nvim_set_hl(0, 'DapLogPoint', dap_breakpoint_color.logpoing) vim.api.nvim_set_hl(0, 'DapStopped', dap_breakpoint_color.stopped) local dap_breakpoint = { error = { text = "", texthl = "DapBreakpoint", linehl = "DapBreakpoint", numhl = "DapBreakpoint", }, condition = { text = 'ﳁ', texthl = 'DapBreakpoint', linehl = 'DapBreakpoint', numhl = 'DapBreakpoint', }, rejected = { text = "", texthl = "DapBreakpint", linehl = "DapBreakpoint", numhl = "DapBreakpoint", }, logpoint = { text = '', texthl = 'DapLogPoint', linehl = 'DapLogPoint', numhl = 'DapLogPoint', }, stopped = { text = '', texthl = 'DapStopped', linehl = 'DapStopped', numhl = 'DapStopped', }, } vim.fn.sign_define('DapBreakpoint', dap_breakpoint.error) vim.fn.sign_define('DapBreakpointCondition', dap_breakpoint.condition) vim.fn.sign_define('DapBreakpointRejected', dap_breakpoint.rejected) vim.fn.sign_define('DapLogPoint', dap_breakpoint.logpoint) vim.fn.sign_define('DapStopped', dap_breakpoint.stopped)上面的代码主要配置了显示的颜色和图标。最终调试的效果如下图所示然后我们需要提供一个可用的界面用来显示调试过程中的各种信息，包括变量值和调用栈。完成这个工作的是插件 nvim-dap-ui 。我们使用如下的代码进行安装use { "rcarriga/nvim-dap-ui", requires = {"mfussenegger/nvim-dap"} }这个插件里面包装了很多调试相关的窗口，例如变量监控、调用栈等等。我们可以对他进行配置，让这些窗口元素出现在我们希望它出现的位置。为了加载这个插件我们还是按照之前的惯例，为它准备一个单独的配置文件，并且加载它。local dapui = require("dapui") dapui.setup({})我们可以使用该插件中的函数 toggle() 开打开或者关闭这些调试窗口。最终的效果就像这样每次都输入这个函数来打开和关闭调试窗口比较麻烦，因此我们这里可以使用以下代码来实现自动加载和关闭local dapui = require("dapui") dapui.setup({}) local dap = require("dap") dap.listeners.after.event_initialized["dapui_config"] = function() dapui.open({}) end dap.listeners.before.event_terminated["dapui_config"] = function() dapui.close({}) end dap.listeners.before.event_exited["dapui_config"] = function() dapui.close({}) end这段代码在 dap 的事件中注册了几个回调函数，当对应的事件发生时会调用对应的函数，我们在 dap 的调试启动时打开调试窗口，在结束时关闭调试窗口最后关于界面方面的优化再来推荐一个插件——nvim-dap-virtual-text 它的作用是在调试过程中，在变量附近事实显示变量的值。我们可以在 dap-ui 的配置文件中对他进行配置require("nvim-dap-virtual-text").setup({ enabled = true, enable_commands = true, highlight_changed_variables = true, highlight_new_as_changed = false, show_stop_reason = true, commented = false, only_first_definition = true, all_references = false, filter_references_pattern = '<module', virt_text_pos = 'eol', all_frames = false, virt_lines = false, virt_text_win_col = nil })上述的配置是官方给出的，我原封不动的复制过来了。它的效果如下图所示：配置c++基础调试环境终于到了本文最重要的环节了，就是配置 c/c++ 的调试环境，上一篇我们讲解了 Python 的配置，它代表了脚本类解释型语言的调试配置，C/C++ 代表了编译型语言的调试配置。针对 C/C++ 的调试我们选用 cpptools 作为 dap 的服务端。首先通过 MasonInstall cpptools 来下载安装它，也可以通过 :Mason 命令在图形化的界面上进行安装。然后我们还是按照之前的顺序来对他进行配置，首先配置它的加载方式local dap = require("dap") dap.adapters.cppdbg = { id = "cppdbg", type = 'executable', command = "~/.local/share/nvim/mason/bin/OpenDebugAD7", }这里我们设置它以 executable 的方式启动(在客户端调试时启动)。然后指定可执行程序的路径，如果这里报找不到 OpenDebugAD7 这种错误，可以将 ~ 改为 /home/user 这样的具体目录。然后我们配置一下客户端与服务器通信相关的内容dap.configurations.cpp = { { name = "Launch file", type = "cppdbg", request = "launch", program = function() return vim.fn.input("Path to executable: ", vim.fn.getcwd() .. "/", "file") end, cwd = "${workspaceFolder}", stopAtEntry = true, }, } dap.configurations.c = dap.configurations.cpp最后我们通过一个 dap.configurations.c= dap.configurations.cpp 让c++和 c使用同一个配置。因为 C/C++ 是编译运行的，在调试的时候其实调试的是它生成的可执行程序，所以这里每次在调试的时候需要手工指定要调试的可执行程序。最后别忘了在 ftplugin/cpp.lua 中加载它另外需要注意，因为可执行程序运行时是不依赖源代码的，但是调试的时候想让调试器能够准确的知道当前在源码的位置并且能够显示当前变量的值，这个时候需要在可执行程序中打包符号表，对于linux 的 C/C++ 程序来说，只需要在编译的时候给gcc/g++ 传递 -s 参数即可。我们写一个简单的 C程序来进行实验#include <stdio.h> int main (int argc, char *argv[]) { printf("hello world\n"); for (size_t i = 0; i < 10; i++) { printf("i = %ld\n", i); } return 0; }注意: 这里我们使用的调试器仍然是gdb, cpptools 只是在上层进行了一层封装。因此这里能调试的前提是安装了gdb 调试器到此我们将关于 dap 调试的部分都基本介绍完了。其实 dap 也并没有想象中那么难，目前从安装到配置使用，都有大量的插件来方便我们使用，而且官网上基本都有配置的介绍，没有特殊需求只需要将标准配置原样拷贝粘贴即可。下一篇我们将补充一些关于 dap 的其他内容，并介绍 neovim + gdb 的组合，敬请期待！
从零开始配置vim(28)——代码的编译、运行与调试
在前面几个章节，我们逐渐为 Vim 配置了语法高亮、代码的跳转和自动补全功能。现在的 Vim 已经可以作为代码编辑器来使用了。但是想将它作为日常发开的主力编辑器来用还需要很长一段路要走，其中一个就是要为它配置代码的一键编译与运行功能。这里我们仍然以 C 和 Python 为例。一个是需要编译运行的一个是直接就可以运行的，这两个语言应该能代表大多数语言的情况。自动运行C 语言的配置在之前 vim 入门的一系列教程中我们介绍过 vim 自带 make 命令的运行机制以及如何进行自定义。对于其他语言要实现这个自动编译运行的效果我们核心的操作就是在修改 make 命令。而 C/C++ 本身采用 make 命令来进行编译和运行，所以这里 C/C++ 我们直接采用 vim 自带的 :make 命令在之前 vim 入门的一系列教程中我们介绍过 vim 自带 make 命令的运行机制以及如何进行自定义。对于其他语言要实现这个自动编译运行的效果我们核心的操作就是在修改 make 命令。而 C/C++ 本身采用 make 命令来进行编译和运行，所以这里 C/C++ 我们直接采用 vim 自带的 :make 命令我们先创建一个 C 的工程。让后使用上一节的生成 hello world 的代码片段生成一个基本的程序。然后提供一个供 :make 命令使用的 Makefile 文件main.out: main.o gcc main.o -o main.out main.o: main.c gcc -c main.c clean: rm -rf *.o *.out run: ./main.out然后我们执行 make 用来编译。如果出错了，可以使用 quickfix 相关命令跳转到对应位置。我们一般的流程是 :make 进行编译，然后使用 :make run 来进行运行。把命令搞清楚了，下面就考虑如何加快这个流程，做到一键编译运行。我们的思路还是绑定快捷键。每种语言虽然定义相同的快捷键但是运行的命令不同，我们需要根据不同的语言类型绑定对应的命令。这个时候最好的办法就是在 filetype 的机制上完成绑定的操作。我们在 lua/lsp/cpp.lua 中绑定快捷键。local on_attach = function(client, bufnr) lsp_set_keymap.set_keymap(bufnr) -- 编译 vim.api.nvim_buf_set_keymap(bufnr, "n", "<F7>", "<cmd>make<CR>", {silent = true, noremap = true}) -- 编译运行 vim.api.nvim_buf_set_keymap(bufnr, "n", "<F5>", "<cmd>make run<CR>", {silent = true, noremap = true}) end到此我们关于 C/C++ 的配置就完成了。可能显的有些简单但是已经初步可用了，小伙伴可以根据自己的需求来进一步修改这个配置。使用这个配置的前提是 C/C++ 的工程中有已经定义好的 Makefile 文件Python的配置之前我们在讲解命令的模式的提到过可以使用 % 来代表当前 buffer 所对应的文件。所以 python 的配置就比较简单了。因为 Python 不需要编译，所以这里直接绑定 <F5> 来运行vim.api.nvim_buf_set_keymap(bufnr, "n", "<F5>", "<cmd>!python %<CR>", {silent = true, noremap = true})dap 配置我们经常看到有人配置 neovim 或者 vim 的时候会介绍到 dap ，那么什么是 dap 呢? dap 的全称是 Debug Adapter Protocol 从名称上看它又是一个协议。它为多种调试器提供了一层统一的适配抽象层。有点类似于前面的介绍的 lsp。只要在适配层提供接口的实现，那么在客户端，也就是代码编辑器这端可以不做任何修改的集成不同调试联想到 lsp 的配置，我们配置dap 首先需要的是有一个 dap 的客户端，用来向调试器发送各种命令，例如下断点、显示变量名等等。另外想要能够调试也需要有具体的调试器，用来接收处理这些命令。现在思路有了，我们 这里先以 Python 为例来介绍 dap 的基本配置。首先是需要一个客户端，用于通过 neovim下发各种调试命令并实时显示调试信息。截止到 0.7 版本 NeoVim 并没有在内部集成 dap 客户端的功能，需要我们单独安装相关插件来实现这部分的功能。这里我们使用的客户端是 nvim-dap 插件。我们先使用 use {'mfussenegger/nvim-dap'} 来安装它。接着我们来定义一下相关的快捷键，这里我喜欢使用 Visual Studio 的快捷键。各位小伙伴可以自行选择自己喜欢的快捷键。这里我希望在插入模式和选择中也可以使用这些快捷键，由于 vim.api.nvim_set_keymap 函数第一个参数只能有一个模式字符串，如果采用这个函数来定义快捷键，这里同样的代码我要写三次，为了简化代码，这里介绍一个新的函数 vim.keymap.set。它与 vim.api.nvim_set_keymap 函数支持的参数相同，只是它第一个表示模式的参数可以支持用字典来一次绑定到多个模式中。这样就简化了绑定快捷键的代码量。vim.keymap.set({"i", "n", "v"}, "<F5>", "<cmd>lua require'dap'.continue()<CR>", {silent = true, noremap = true, buffer = bufnr}) vim.keymap.set({"i", "n", "v"}, "<F10>", "<cmd>lua require'dap'.step_over()<CR>", {silent = true, noremap = true, buffer = bufnr}) vim.keymap.set({"i", "n", "v"}, "<F11>", "<cmd>lua require'dap'.step_into()<CR>", {silent = true, noremap = true, buffer = bufnr}) vim.keymap.set({"i", "n", "v"}, "<F12>", "<cmd>lua require'dap'.step_over()<CR>", {silent = true, noremap = true, buffer = bufnr}) vim.keymap.set({"i", "n", "v"}, "<F9>", "<cmd>lua require'dap'.toggle_breakpoint()<CR>", {silent = true, noremap = true, buffer = bufnr})这个函数是 0.7 以后的版本引进的，如果你的是0.7之前的版本，还是老老实实的多写几遍。另外我们这里绑定了 <F5> 快捷键，因此之前我们在 Python 中，绑定的直接运行的 <F5> 键的代码需要注释一下。我们想要真正实现调试，还需要配合调试器使用。前面说 dap 只是一层协议，需要客户端服务器按照这一层协议来实现相关功能，某些调试器可能自身支持这个协议，而某些可能不支持，这样就需要额外的配置来使调试器也能支持该协议。下面我们以 Python 为例先把整个调试环境搭建起来，先跑起来再说Lsp 在安装 Server 的时候有 nvim-lsp-installer 这样的插件来专门安装 LSP server 的，那么 dap 有没有类似的插件来安装 dap 调试器相关的服务呢？有！我们使用 mason 来管理 dap 的调试器。use { "williamboman/mason.nvim" }当初我推荐过 nvim-lsp-installer 插件作为下载、管理 lsp server 的工具。后来只是知道作者发布了新的管理工具，因为比较新怕出问题就没怎么关注，后来有好多小伙伴在评论区推荐，我仔细看了一下发现它已经支持 dap 服务的管理了。那还是使用它吧 ^_^。我们可以使用 Mason 打开一个带界面的 Lsp 和 DAP 的服务管理窗口，可以使用 数字键在上面进行跳转，找到想要的服务之后直接使用 i 来安装也可以使用 MasonInstall 来安装想要的服务。我们先在插件配置中删除与 nvim-lsp-installer 相关的配置，包括 packer 中对它的引用和 plugins-config 目录中的配置。下一步就是配置 dap 的客户端与 服务端的联动，这需要配置 nvim-dap 插件，根据官方的描述我们主要配置两个部分，第一个部分叫做适配器，主要配置我们加载哪个调试器，以及如何加载调试器。这一步需要提供如下的配置框架local dap = require('dap') dap.adapters.language = { }language 是具体的调试器例如 debugpy 这里的 language 就是 python了。既然与语言相关，我们自然的想到要用 ftplugin目录。为了方便管理，这里与 lsp 配置的组织形式类似，我们将所有关于 dap 的配置都放到 lua/dap目录中。并且按语言名称来命名，例如关于 python 的 dap 配置我们放到 lua/dap/python.lua 中。然后在 ftplugin/python.lua 中加载这个配置文件即。require("dap/python")然后在 python.lua 文件中写入以下配置local dap = require('dap') dap.adapters.python = { type = 'executable'; command = '/usr/bin/python3.8'; args = { '-m', 'debugpy.adapter' }; }其中各个参数的含义如下:type : 表示启动调试器的方式， executable 表示由客户端自行启动调试器； server 表示 调试器已经单独启动了，后续客户端只需要将调试请求发送到服务器即可。command: 表示启动调试器的命令args: 表示启动调试器的命令行参数由于 python 调试工具 debugpy 是一个 Python 的第三方模块，因此这里我们使用 python -m debugpy.adapter 来启动这个调试器。接着我们需要针对语言来配置如何进行调试。它的配置都放在一个名为 dap.configurations.language 的 字典中。language 代表的是当前文件的文件类型名, 所以针对 Python 来说这里需要填写的是 dap.configurations.python。它的配置如下所示:dap.configurations.python = { { type = "python"; request = "launch"; name = "launch file"; program = "${file}"; pythonPath = function () return "/usr/bin/python3.8" end }, }各参数的含义如下:name : 是一个字符串它表示当前配置的名称，你可以理解为一个idtype: 使用哪个调试器，跟我们之前配置的 dap.adapters相关request：调试的方式，支持 attach 附加到一个已有的进程或者 launch 启动一个新进程。由于在上一步我们指定由客户端来启动调试器，因此这里应该选择 launch 来启动一个新调试进程program: 需要调试的代码， ${file} 表示当前 buffer 所对应文件pythonPath： 执行该文件需要使用的 python 解析器路径这样我们在某一个打开的文件上按下 <F5> 的时候，它会通过 pythonPath 指定的解析器来执行脚本，并且会按照配置中 request 指定的方式来打开一个新的调试器进程。并且将对应的调试命令发送到调试器完成调试工作。好了，到此为止我们配置了最基本、最简单的dap 调试。现在只是有一个勉强能用的调试工具，距离好用还差的很远，下一篇里面我们首先会对 dap 功能进行增强，美化，并讨论如何针对 C/C++ 这种编译型的语言进行调试。最后感谢各位给我提意见的小伙伴，谢谢大家!
我们之前介绍过缩写相关的内容，缩写是可以自动帮我们将缩写的单词展开成一段完整的话。但是代码本身是结构话的，仅仅使用缩写来配置是无法完成自动生成代码这个步骤的。好在我们大量的插件来进行配置。本篇我们将要来讨论如何使用相关插件来完成代码片段自动完成的功能vsnip 插件我们之前在配置自动补全的时候已经下载了 vim-vsnip这个插件。vsnip是一个非常强大的插件，它支持我们使用与 VS Code 类似的方式来扩展定义我们自己的代码片段，同时它也内置了不同语言版本的代码片段。在之前的配置中我们还加了另外一个 friendly-snippets 。它提供了丰富的已定义好的可以直接使用的代码片段，加快了我们的编码效率。在前面介绍补全的时候我们已经安装并配置了它们。它们的效果如下图所示:自定义代码片段虽说这些插件预定义了大量的代码片段。但是他们都是通用型的代码片段，总有那么些时候无法满足我们的需求。一个明显的例子就是不同的公司有不同的代码和注释的风格。这个时候就需要我们自定义了。本篇也准备将重点放在如何自定义代码片段上。如果小伙伴们已经有了在 vscode 上自定义代码片段的经历，那么请跳过本篇以节省各位的时间。入门下面我们以 C 为例来说明如何自定义代码片段来满足我们的需求。其他语言只是填入的内容不同，在定义上并没有什么大的差别。这里我采用 C 语言一个原因是静态类型的语言更方便的演示其中的各项功能，另一个原因就是我对C/C++ 比较熟悉首先我们要找到代码片段的配置文件所在位置，这个位置保存在变量 g:vsnip_snippet_dir 中。我们可以通过 :echo g:vsnip_snippets_dir 找到它。当然也有更简单的办法，我们可以执行 :VsnipOpen 来打开该语言对应的配置文件，如果该文件不存在，命令将创建一个以对应语言名称命名的 json 文件，例如这里它会创建一个 c.json 的文件这里我们先来写一个最简单的代码片段——自动生成一个 Hello World 的 C 程序"example": { "prefix": ["hello"], "body": [ "#include <stdio.h>", "int main(int argc, char* argv[])", "{", "\tprintf(\"hello world\");", "\treturn 0;", "}" ], "description": "create a hello world" }我们来试试效果，输入 hello 将自动生成一段代码现在我们来看看这些字段都代表什么含义example：它代表该代码段的名称，也可以认为它是一个id，用于标识一个代码片段prefix：用于触发该代码片段，这里也就是我们输入 hello将会触发补全，当选择对应项的时候会调用该代码片段进行补全body：补全时自动生成的代码。它可以是一个字符串或者字符数组。虽然它本身也支持 \r\n来进行换行。但是我更倾向于使用字符数组的形式，每一行是数组中的一个字符串。description ：描述信息使用占位符如果我们仅仅只能生成像上述 hello world 那样给写死的代码的话，那么它也没什么太大的用处。有时候我们的代码需要生成一个模板，关键地方应该由我们自己进行填充。这个时候可以用到占位符，例如我们使用下面的来生成一个函数"ifunc": { "prefix": ["ifunc"], "body": [ "int $1()", "{", "\treturn 0;", "}" ], "description": "create a function return int" } }占位符内容可选我们现在已经可以生成函数了，但是有一个问题摆在我们的面前。函数的返回类型多种多样，如果我们每一个类型都定义一个片段，例如 返回 int 的定义一个 ifunc，返回 float 的定义一个 ffunc。显得多此一举了，代码的重复度太高了。这个时候我们我们给占位符一些预选项以供我们选择。它的语法格式如下${1|sel1, sel2, sel3,...|}前面的1代表是第一个占位符。如果是后面的占位符需要提供选项，那么就可以依次类推例如我们将上述生成函数的代码片段改为"func": { "prefix": ["func"], "body": [ "${1|int,float,double,char,short,*|} $2()", "{", "\treturn $3;", "}" ], "description": "create a function" } }占位符间跳转生成函数的代码片段中有3个占位符，其中第一个是可以选的，第二个第三个需要我们手动填写。按照习惯，我们一般使用 <Tab> 键来进行跳转，但是这个时候我们发现它没有用。vsip 有自己的命令来跳转到占位符，因此为了保持使用习惯不变，我们需要定义快捷键vim.cmd[[imap <expr> <Tab> vsnip#jumpable(1) ? '<Plug>(vsnip-jump-next)' : '<Tab>']] vim.cmd[[smap <expr> <Tab> vsnip#jumpable(1) ? '<Plug>(vsnip-jump-next)' : '<Tab>']] vim.cmd[[imap <expr> <S-Tab> vsnip#jumpable(-1) ? '<Plug>(vsnip-jump-prev)' : '<S-Tab>']] vim.cmd[[smap <expr> <S-Tab> vsnip#jumpable(-1) ? '<Plug>(vsnip-jump-prev)' : '<S-Tab>']]由于该插件是使用 vimscript 脚本写的,它还没有提供 lua 的接口，因此这里我也就使用 vimscript 的方式来定义快捷键。使用变量使用语法 $name 或者 ${name: default} 可以插入一个变量。如果未设置变量，则会插入其默认值或空字符串。当变量未知（未定义其名称）时，会将插入的变量名称转换为占位符。这里的变量一般是环境变量或者是 vim 自带的一些变量。我们直接拿来用但是在不同的环境下得到的结果是不一样的。我们可以使用变量来丰富一些信息。例如我们使用下面的代码片段来生成注释"doc-header": { "prefix": ["doc-header"], "body": [ "/**", "* @file ${TM_FILENAME}", "* @bref ${1}", "* @details ${2}", "* @date ${CURRENT_YEAR}/${CURRENT_MONTH}/${CURRENT_DATE}", "* @commit history:", "* \t v${3}: ${4}", "*/${0}" ], "description": "create a doc header" }这样我们就生成了一个 .cpp 文件开头的注释了。其中用到了 TM_FILENAME 来表示文件名，使用 CURRENT_YEAR CURRENT_MONTH CURRENT_DATE 来分别获取到年月日。它的效果如下:这里的 ${0} 一般用来截断 Tab键的跳转，也就说遇到 ${0} 之后 <Tab> 键就不再起到跳转到下一个占位符位置这个作用了。具体有哪些变量可以使用，可以参考 visual studio code 官方给出的文档本篇主要谈论了该如何定义自己的代码片段。如果想要更完整的内容可以参考 Visual Studio Code 官方的文档。我们也可以从Visual Studio Code 相关代码片段中 Copy 部分来进行使用。
之前我们通过几个实例演示如何配置其他语言的lsp服务，相信各位小伙伴碰到其他的编程语言也能熟练的配置它对应的lsp服务。本篇讲作为一个补充，我们来优化一下LSP 相关的显示配置 UI原始的 lsp 显示有点素，我们使用插件对它进行一些美化，这里使用插件 lspsaga.nvim 。使用如下的代码进行安装use{"glepnir/lspsaga.nvim"}然后我们新建一个 plugin-config/lspsaga.lua 对它进行配置local saga = require('lspsaga') saga.init_lsp_saga()该插件对 NeoVim 原生 LSP 显示做了一些更改，并提供了一些方便的命令来实现LSP 相关的功能。我们将它对应的功能绑定到快捷键上替换原有的 LSP 对应的快捷键lsp_keybinds.set_keymap = function (bufnr) print("set lsp keymap") -- 跳转到声明 vim.api.nvim_buf_set_keymap(bufnr, "n", "gd", "<cmd>Lspsaga peek_definition<CR>", {silent = true, noremap = true}) -- 跳转到定义 vim.api.nvim_buf_set_keymap(bufnr, "n", "gD", "<cmd>lua vim.lsp.buf.definition()<CR>", {silent = true, noremap = true}) -- 显示注释文档 vim.api.nvim_buf_set_keymap(bufnr, "n", "gh", "<cmd>Lspsaga lsp_finder<CR>", {silent = true, noremap = true}) -- 跳转到实现 vim.api.nvim_buf_set_keymap(bufnr, "n", "gi", "<cmd>lua vim.lsp.buf.implementation()<CR>", {silent = true, noremap = true}) -- 跳转到引用位置 vim.api.nvim_buf_set_keymap(bufnr, "n", "gr", "<cmd>Lspsaga rename<CR>", {silent = true, noremap = true}) -- 以浮窗形式显示错误 vim.api.nvim_buf_set_keymap(bufnr, "n", "go", "<cmd>lua vim.diagnostic.open_float()<CR>", {silent = true, noremap = true}) vim.api.nvim_buf_set_keymap(bufnr, "n", "gp", "<cmd>lua vim.diagnostic.goto_prev()<CR>", {silent = true, noremap = true}) vim.api.nvim_buf_set_keymap(bufnr, "n", "gn", "<cmd>lua vim.diagnostic.goto_next()<CR>", {silent = true, noremap = true}) vim.api.nvim_buf_set_keymap(bufnr, "n", "<leader>cd", "<cmd>Lspsaga show_cursor_diagnostics<CR>", {silent = true, noremap = true}) vim.api.nvim_buf_set_keymap(bufnr, "n", "<leader>cd", "<cmd>Lspsaga show_line_diagnostics<CR>", {silent = true, noremap = true}) vim.api.nvim_buf_set_keymap(bufnr, "n", "<leader>ca", "<cmd>Lspsaga code_action<CR>", {silent = true, noremap = true}) vim.api.nvim_buf_set_keymap(bufnr, "v", "<leader>ca", "<cmd>Lspsaga code_action<CR>", {silent = true, noremap = true}) end我们可以对比一下原生的 lsp 界面和 lspsaga 界面，我这里使用 gh 这个命令进行对比我们可以看到它能显示更丰富的信息，而且我们可以根据显示快速跳转到对应位置。对于我们查阅代码也是一个大的提升而且它还提供 code action 相关的功能。例如上面的截图中它在出现错误的一行代码的行号前以小灯泡的形式进行标记，体验类似与 VS Code。我们将光标放在对应位置，使用绑定的快捷键 <leader>ca 。它会显示出对应的修复方案当然它的功能还不止于此，各位小伙伴可以去对应的官网上阅读相关文档，针对自己的需求进行进一步的配置显示符号表我们可以使用插件 symbols-outline 在窗口右侧显示当前 buffer 中出现的类、方法等符号，方便我们快速跳转到想去的地方。该插件仅支持 NeoVim7,0 以上的版本。使用下面的代码进行安装use {'simrat39/symbols-outline.nvim'}还是额外的给它创建一个文件保存它的配置require("symbols-outline").setup()配置完成之后我们可以使用 :SymbolsOutline 命令来控制窗口的打开和关闭，为了方便我们可以绑定到一个快捷上配置完成之后我们可以使用 :SymbolsOutline 命令来控制窗口的打开和关闭，为了方便我们可以绑定到一个快捷上vim.api.nvim_set_keymap("n", "<leader>so", "<cmd>SymbolsOutline<CR>", {silent = true, noremap = true})最终的效果如下图所示本章我们对之前的LSP 配置进行了一定程度的优化，优化了使用的体验。截止到当前文章我们已经完成了代码的高亮、跳转、语法分析、代码补全、错误提示以及修复建议的相关配置。关于LSP的相关配置已经结束了，下一篇我们将要讨论如何使用代码片段进一步减少我们编码时输入的字符数。请各位敬请期待
从9月份到国庆这段时间，因为得了女儿，于是回老家帮忙料理家事以及陪伴老婆和女儿。一时之间无暇顾及该系列教程的更新。等我回来的时候发现很多小伙伴私信我催更。在这里向支持本人这一拙劣教程的各位小伙伴表示真诚的感谢。言归正传，让我们开始吧之前我们根据lua语言配置了基于lsp的代码高亮、自动跳转、自动补全等等功能，那个时候我们安装了很多插件，像 nvim-lspconfig、nvim-lsp-installernvim-cmp等等，每个插件都在干嘛，虽然我们配置好了 lua相关的内容，但是可能仍然有小伙伴有疑问，碰到其他语言该如何配置，是不是要重新下载对应的插件呢？为了解答这些问题，这篇文章我们将要来根据 c++和 python的日常习惯来进行配置，给大家演示一下在上述内容都配置完成之后面对其他语言我们该如何进行处理安装配置 c++ 相关的lsp服务关于c++ 的服务，我们根据 nvim-lsp-installer 官方给出的表格中显示它可以使用 ccls 和 clang，这里我们以 ccls 作为示例进行讲解。首先通过命令安装 :LspInstall ccls接着我们新建一个 ftplugin/c.lua 和 ftplugin/cpp.lua 来配置 c/c++ 。不过他们两个采用相同的配置，我们暂时将一份配置复制两遍require("lsp/cpp")他们的作用只有一个，那就是加载 lsp/cpp 这个文件，我们将他们的配置放到一个文件中然后我们再在 lua/lsp/cpp.lua 文件中加入以下内容用于启动 lsp服务端local lspconfig = require('lspconfig') lspconfig.ccls.setup { init_options = { cache = { directory = ".ccls-cache"; }; } }我们进入一个 .c/.cpp 文件发现已经加载了 ccls 了。如果没有加载可以使用 :LspStart 命令手工加载或者使用 LspInfo 查看是否有问题现在我们已经可以看到lsp服务给出的提示了修改之前的配置我们在第22篇文章中给出了基于 lsp 的 lua 的配置，主要是使用 lsp 服务端的配置和对应的跳转之类的快捷键配置，我们将它放到了 lsp/lua.lua 目录下了。但是一想想我们使用 c++、Python 或者其他什么语言的时候，这些快捷键应该是不会修改的，这个时候自然就想到了要重用快捷键了。所以来配置之前的第一件事就是想办法重用这些快捷键。这个时候我们想到的办法就是将之前定义的快捷键封装成函数，然后在 on_attach 的回调函数中调用该函数。我们将那些快捷键定义放到 lua/keybindings.lua 中local lsp_keybinds = {} lsp_keybinds.set_keymap = function (bufnr) -- 跳转到声明 vim.api.nvim_buf_set_keymap(bufnr, "n", "gd", "<cmd>lua vim.lsp.buf.declaration()<CR>", {silent = true, noremap = true}) -- 跳转到定义 vim.api.nvim_buf_set_keymap(bufnr, "n", "gD", "<cmd>lua vim.lsp.buf.definition()<CR>", {silent = true, noremap = true}) -- 显示注释文档 vim.api.nvim_buf_set_keymap(bufnr, "n", "gh", "<cmd>lua vim.lsp.buf.hover()<CR>", {silent = true, noremap = true}) -- 跳转到实现 vim.api.nvim_buf_set_keymap(bufnr, "n", "gi", "<cmd>lua vim.lsp.buf.implementation()<CR>", {silent = true, noremap = true}) -- 跳转到引用位置 vim.api.nvim_buf_set_keymap(bufnr, "n", "gr", "<cmd>lua vim.lsp.buf.references()<CR>", {silent = true, noremap = true}) -- 以浮窗形式显示错误 vim.api.nvim_buf_set_keymap(bufnr, "n", "go", "<cmd>lua vim.diagnostic.open_float()<CR>", {silent = true, noremap = true}) vim.api.nvim_buf_set_keymap(bufnr, "n", "gp", "<cmd>lua vim.diagnostic.goto_prev()<CR>", {silent = true, noremap = true}) vim.api.nvim_buf_set_keymap(bufnr, "n", "gn", "<cmd>lua vim.diagnostic.goto_next()<CR>", {silent = true, noremap = true}) end return lsp_keybinds我们将它打包到lsp_keybinds 模块中作为 keybinds.lua 文件的导出模块。然后在 lsp/lua.lua文件的 on_attach 函数中调用这个函数完成快捷键的配置工作local lsp_set_keymap = require("keybindings") local on_attach = function(_, bufnr) lsp_set_keymap.set_keymap(bufnr) end现在我们就完成了 c/c++ 相关的配置尝试一下绑定的那些快捷键，发现它可以正常进行跳转是不是很简单了，我们没有安装任何的插件，只是安装了 c++ lsp 的服务端。剩下的配置依然延续之前的就好了python 相关配置我们再来以 python 的配置作为例子来讲lsp的配置。python 的服务端我们采用 pyright 。还有一个方法可以安装 lsp 服务。我们可以在命令模式中输入 :LspInstallInfo 查看当前已安装的 lsp 服务。下面会列出一堆的未安装的 lsp 服务。这个时候我们可以将光标移动到某个服务上，按下 i 来安装该服务。这里我提前已经安装好了，所以它显示在 Installed Server 中，没有安装它应该显示在下方的Available Server 列表中。安装完成之后我们还是按照惯例，在 ftplugin 目录下建立一个 python.lua 文件并且在该文件中加载 lua/lsp/python.lua 文件我们在 lua/lsp/python.lua 文件中加入下面的代码local lsp_set_keymap = require("keybindings") local util = require 'lspconfig/util' require('lspconfig').pyright.setup{ on_attach = function(_, bufnr) lsp_set_keymap.set_keymap(bufnr) end, cmd = { "pyright-langserver", "--stdio" }, filetypes = { "python" }, settings = { python = { analysis = { autoSearchPaths = true, diagnosticMode = "workspace", useLibraryCodeForTypes = true, typeCheckingMode = "off" }, }, }, root_dir = function(fname) local root_files = { 'pyproject.toml', 'setup.py', 'setup.cfg', 'requirements.txt', 'Pipfile', 'pyrightconfig.json', } return util.root_pattern(unpack(root_files))(fname) or util.find_git_ancestor(fname) or util.path.dirname(fname) end, }前面的配置与 c++ 的配置类似。主要设置快捷键，这里需要注意的是 root_dir 这里的配置，想要在对应缓冲中启动相应的 lsp 服务，需要buffer处在对应语言的项目中，root_dir 规定在 buffer 所在目录中存在这些文件或者目录时将该目录作为对应项目文件如果我们不加该配置，可能会导致lsp 服务启动失败。如果失败的话我们使用 :LspInfo 来查看失败原因这个根目录设置是可以作用于它下面所有子目录的。这里我根据 python 中常用文件给出了一个列表，各位小伙伴可以根据自己的需求自行添加另外需要注意的一个问题时，pyright 依赖于 node 的 work_threads 模块，该模块从12版本以后才被支持，如果小伙伴的 node 版本低于该版本需要考虑升级 node根据这两个例子，相信各位已经熟悉了该如何配置不同语言的 lsp 服务了。这里面没有什么深奥的代码，也没有什么个性化到只有自己才会用的配置，希望能起到抛砖引玉的作用，后面再碰到什么其他语言小伙伴们应该可以很容易的添加它的lsp服务了。
neovim 自带的代码补全的效果并不好，而且它分为好多类，如果需要人为的去判断使用路径补全、使用当前buffer中的单词补全、亦或者使用include 来进行补全，那样使用起来就很不方便了。针对代码的补全我们可以基于 lsp的配置使用插件来完成，这里我推荐使用 nvim-cmp插件安装我们使用下面的代码进行安装-- nvim-cmp use {'hrsh7th/cmp-nvim-lsp'} use {'hrsh7th/cmp-buffer'} use {'hrsh7th/cmp-path'} use {'hrsh7th/cmp-cmdline'} use {'hrsh7th/nvim-cmp'} -- vsnip use {'hrsh7th/cmp-vsnip'} use {'hrsh7th/vim-vsnip'} use {'rafamadriz/friendly-snippets'} -- lspkind use {'onsails/lspkind-nvim'}看着好像要安装好多插件，但是真正重要的就只有 nvim-cmp 剩下的以 cmp- 开头的都是在指定补全的来源，就像 neovim 自带补全功能可以来源于当前buffer、路径、引用一样，该插件的补全项也来源于各个地方。例如 cmp-nvim-lsp来源于语言的 lsp 服务端、cmp-buffer来源于当前 buffer 、cmd-path来源于路径配置我们还是按照原来的惯例进行配置，新增一个 plugin-config/cmp.lua 的配置文件，并且加载它require('plugin-config/cmp')在该配置文件中我们加入如下内容local cmp = require("cmp") local lspkind = require("lspkind") cmp.setup({ -- 设置代码片段引擎，用于根据代码片段补全 snippet = { expand = function(args) vim.fn["vsnip#anonymous"](args.body) end, }, window = { }, mapping = { -- 选择上一个 ['<C-p>'] = cmp.mapping.select_prev_item(), -- 选择下一个 ['<C-n>'] = cmp.mapping.select_next_item(), -- 出现补全 ['<A-.>'] = cmp.mapping(cmp.mapping.complete(), {'i', 'c'}), -- 取消补全 ['<A-,>'] = cmp.mapping({ i = cmp.mapping.abort(), c = cmp.mapping.close(), }), -- 确认使用某个补全项 ['<CR>'] = cmp.mapping.confirm({ select = true, behavior = cmp.ConfirmBehavior.Replace }), -- 向上翻页 ['<C-u>'] = cmp.mapping(cmp.mapping.scroll_docs(-4), {'i', 'c'}), -- 向下翻页 ['<C-d>'] = cmp.mapping(cmp.mapping.scroll_docs(4), {'i', 'c'}), }, -- 补全来源 sources = cmp.config.sources({ {name = 'nvim_lsp'}, {name = 'vsnip'}, {name = 'buffer'}, {name = 'path'} }), --根据文件类型来选择补全来源 cmp.setup.filetype('gitcommit', { sources = cmp.config.sources({ {name = 'buffer'} }) }), -- 命令模式下输入 `/` 启用补全 cmp.setup.cmdline('/', { mapping = cmp.mapping.preset.cmdline(), sources = { { name = 'buffer' } } }), -- 命令模式下输入 `:` 启用补全 cmp.setup.cmdline(':', { mapping = cmp.mapping.preset.cmdline(), sources = cmp.config.sources({ { name = 'path' } }, { { name = 'cmdline' } }) }), -- 设置补全显示的格式 formatting = { format = lspkind.cmp_format({ with_text = true, maxwidth = 50, before = function(entry, vim_item) vim_item.menu = "[" .. string.upper(entry.source.name) .. "]" return vim_item end }), }, })配置之后我们就可以使用自动补全相关功能了本篇的内容到此结束了，后面我们再次以 c/c++以及 python为例来演示如何进行 lsp相关的配置。帮助大家巩固一下这方面的内容。
上一章，我们初步认识了lsp，并且对 nvim-treesitter插件进行了配置，为编辑器提供了代码着色、自动格式化以及增量选中功能。算是初步体验了 lsp的相关功能。从这篇开始我们通过lsp的功能，进一步提升代码编辑、查阅等功能的体验neovim lsp早期想通过 neovim 使用 lsp 得通过一个额外的插件 nvim-coc 。它使用 nodejs实现，而且提供 lua 的接口。但是在 neovim 中混用不同编程语言有时候会出现莫名其妙的问题，比如我之前遇到的补全失效，但是过一段时间它自己又好了。好在 neovim 社区听从了程序员们对于 lsp 的呼唤，它内置了lsp 的客户端，并且为了方便配置服务端，它提供了一个名为 nvim-lspconfig 的插件。在现在的 neovim 版本下配置不同语言的 lsp已经很方便了，根据官方的文档，我们只需要4步即可安装 nvim-lspconfig 插件安装对应语言 lsp 的服务端针对 xx语言，添加语言的配置 require('lspconfig').xx.setup{…}检查 lsp 的服务端在该缓冲区是否正常运行 print(vim.inspect(vim.lsp.buf_get_clients()))安装 nvim-lspconfig好了，我们按照官方的提示，我们先来配置 lua 相关的内容，以便后期在写配置时可以有更好的编程体验。use {'neovim/nvim-lspconfig'}我们可以去微软的官方网站查看各个语言的服务端信息 https://microsoft.github.io/language-server-protocol/implementors/servers/安装对应服务端针对 lua 语言我们选中的是 sumneko/lua-language-server 这个服务端。根据 wiki 页面的安装方式，我们可以使用命令行自行安装。但是现在有了更方便的方式了我们使用 nvim-lsp-installer 插件进行安装。use { "williamboman/nvim-lsp-installer", "neovim/nvim-lspconfig", }我们还是一样，在plugin-config 目录下创建一个配置文件用来配置 nvim-lsp-installer 插件require("nvim-lsp-installer").setup {}我们可以使用 LspInstallInfo 命令来查看当前lsp服务的安装情况。我们使用 :LspInstall --sync [server] 来安装对应的服务端。其中 --sync 代表我们希望以同步的方式安装，也就是安装时会卡主 neovim 主体。使用下面的命令来安装 lua 的服务端:LspInstall --sync sumneko_lua我们可以在 这个页面 查看 nvim-lsp-installer 插件支持的各个语言对应的服务端针对lua进行配置安装完成之后，我们来配置 lua 相关的内容。不知道还记不记得我们之前介绍 文件类型的时候说过不同文件类型的配置都在 ~/.config/nvim/ftplugin 里面。我们在这个目录里面定义一个 lua.lua 的文件，写入以下内容-- 是否将 tab 替换为 space vim.bo.expandtab = true vim.bo.shiftwidth = 4 vim.bo.tabstop = 4 vim.bo.softtabstop = 4 -- 取消自动注释，当前行是注释时，按下回车键会默认添加一行注释，这里取消这一行为 vim.opt_local.formatoptions = vim.opt_local.formatoptions - {"r", "c", "o"}我们之前分析过 neovim 是如何实现文件类型检测的。在那篇文章中我们说它定义了自动命令，当检测到对应文件类型的时候会调用 ~/.config/nvim/ftplugin 目录中对应以文件类型命名的目录或者 lua 文件。使用这种方式有两个好处，第一个就是我们不用手动使用 require 来加载了，第二个好处就是可以根据文件类型动态的选择调用或者不调用，而且这个工作由 neovim 自动完成，不需要我们进行干预基于这些好处，我们在 ftplugin/lua.lua 中保存 lua 相关的配置。另外再在 ~/.config/nvim/lua 目录创建一个 lsp目录，专门用来保存语言的配置。然后再在 ftplugin/lua.lua 中加载它require('lsp/lua')我们在这个文件中添加 Lua 相关的lsp配置-- 定义快捷键 -- 根据官方的提示，这里我们使用 on_attach 表示当前缓冲加载服务端完成之后调用 local on_attach = function(client, bufnr) -- 跳转到声明 vim.api.nvim_buf_set_keymap(bufnr, "n", "gd", "<cmd>lua vim.lsp.buf.declaration()<CR>", {silent = true, noremap = true}) -- 跳转到定义 vim.api.nvim_buf_set_keymap(bufnr, "n", "gD", "<cmd>lua vim.lsp.buf.definition()<CR>", {silent = true, noremap = true}) -- 显示注释文档 vim.api.nvim_buf_set_keymap(bufnr, "n", "gh", "<cmd>lua vim.lsp.buf.hover()<CR>", {silent = true, noremap = true}) -- 跳转到实现 vim.api.nvim_buf_set_keymap(bufnr, "n", "gi", "<cmd>lua vim.lsp.buf.implementation()<CR>", {silent = true, noremap = true}) -- 跳转到引用位置 vim.api.nvim_buf_set_keymap(bufnr, "n", "gr", "<cmd>lua vim.lsp.buf.references()<CR>", {silent = true, noremap = true}) -- 以浮窗形式显示错误 vim.api.nvim_buf_set_keymap(bufnr, "n", "go", "<cmd>lua vim.diagnostic.open_float()<CR>", {silent = true, noremap = true}) vim.api.nvim_buf_set_keymap(bufnr, "n", "gp", "<cmd>lua vim.diagnostic.goto_prev()<CR>", {silent = true, noremap = true}) vim.api.nvim_buf_set_keymap(bufnr, "n", "gn", "<cmd>lua vim.diagnostic.goto_next()<CR>", {silent = true, noremap = true}) end require'lspconfig'.sumneko_lua.setup { settings = { Lua = { runtime = { -- Tell the language server which version of Lua you're using (most likely LuaJIT in the case of Neovim) version = 'LuaJIT', }, diagnostics = { -- Get the language server to recognize the `vim` global globals = {'vim'}, }, workspace = { -- Make the server aware of Neovim runtime files library = vim.api.nvim_get_runtime_file("", true), }, -- Do not send telemetry data containing a randomized but unique identifier telemetry = { enable = false, }, }, }, on_attach = on_attach, }大部分的配置都是根据 sumneko_lua 官方的文档抄过来的，这里我们需要着重强调一下 on_attach 这里，根据 nvim-config 的描述，当 lsp 服务程序加载完成之后会调用我们在 on_attach 出指定的回调函数，在函数内部我们使用 vim.api.nvim_buf_set_keymap 仅仅针对当前加载了lsp 服务的缓冲区进行，而对于其他普通文件我们不需要用到这些快捷键。看到这里不知道小伙伴是否有点头晕了，这次我们安装了好多内容，也创建了不少配置文件，下面来回顾一下安装 nvim-lspconfig 来用于lsp的配置安装 nvim-lsp-installer 来安装不同语言的 lsp 服务端安装 sumneko_lua 作为 lua 语言的 lsp 服务端创建了一个 ~/.config/nvim/ftplugin 作为加载 lua 语言配置的入口创建了一个 ~/.config/nvim/lsp/lua.lua 保存lua 语言相关的配置本篇只讲了如何基于 neovim 官方给出的那4步来配置一个语言的 lsp 服务端，但是还有如何自动补全没有谈到，下节我们将开始讲解如何使用自动补全。敬请期待!