从零开始配置vim(28)——代码的编译、运行与调试

在前面几个章节,我们逐渐为 Vim 配置了语法高亮、代码的跳转和自动补全功能。现在的 Vim 已经可以作为代码编辑器来使用了。但是想将它作为日常发开的主力编辑器来用还需要很长一段路要走,其中一个就是要为它配置代码的一键编译与运行功能。这里我们仍然以 CPython 为例。一个是需要编译运行的一个是直接就可以运行的,这两个语言应该能代表大多数语言的情况。

自动运行

C 语言的配置

在之前 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 打开一个带界面的 LspDAP 的服务管理窗口,可以使用 数字键在上面进行跳转,找到想要的服务之后直接使用 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目录中。并且按语言名称来命名,例如关于 pythondap 配置我们放到 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 : 是一个字符串它表示当前配置的名称,你可以理解为一个id
  • type: 使用哪个调试器,跟我们之前配置的 dap.adapters相关
  • request:调试的方式,支持 attach 附加到一个已有的进程或者 launch 启动一个新进程。由于在上一步我们指定由客户端来启动调试器,因此这里应该选择 launch 来启动一个新调试进程
  • program: 需要调试的代码, ${file} 表示当前 buffer 所对应文件
  • pythonPath: 执行该文件需要使用的 python 解析器路径

这样我们在某一个打开的文件上按下 <F5> 的时候,它会通过 pythonPath 指定的解析器来执行脚本,并且会按照配置中 request 指定的方式来打开一个新的调试器进程。并且将对应的调试命令发送到调试器完成调试工作。
在这里插入图片描述
好了,到此为止我们配置了最基本、最简单的dap 调试。现在只是有一个勉强能用的调试工具,距离好用还差的很远,下一篇里面我们首先会对 dap 功能进行增强,美化,并讨论如何针对 C/C++ 这种编译型的语言进行调试。最后感谢各位给我提意见的小伙伴,谢谢大家!

posted @ 2022-10-24 16:51  masimaro  阅读(966)  评论(0编辑  收藏  举报