使用 Vimspector 在 Vim 中调试
使用 Vimspector 在 Vim 中调试
下一步是什么?
关注@learnvim获取更多 Vim 技巧和窍门!
使用 Vimspector 在 Vim 中调试
Vimspector 是一款功能强大的 Vim 图形调试器插件。不过,上手可能需要一些时间。在本文中,我将向您展示如何使用 Vimspector 进行调试:
- 一个节点文件。
- 使用 Chrome 的客户端应用程序。
- 开玩笑地测试。
- 一款 Express 应用程序。
他们的Github 页面和官网都非常全面。建议你有机会的时候去看看!
本文中的许多内容都可以在 Vimspector 官网找到。然而,当我阅读这些内容时,我被海量的信息量所震撼。本文可以作为桥梁,帮助您尽早入门。我认为尽早动手实践是有益的。一旦开始使用,阅读文档就会变得更加清晰。虽然我以 JavaScript 生态系统(后端、前端、测试)为例,但您应该能够将相同的概念应用于您选择的任何语言。
要求
撰写本文时,我正在 MacOS Catalina 上使用 Vim 8.2。本指南适用于任何操作系统(需进行适当修改)。不过,Vimspector Github 页面建议你使用 Vim 8.2 或 NeoVim 0.4.3。你还需要 Python 3.6 或更高版本。
快速概览
那么 Vimspector 是什么?它是如何工作的?
Vimspector 并非通用语言调试器。从技术上讲,它本身并不负责调试。你可以把它想象成一个中间人,用于帮助你与所选语言的调试器进行通信。它依赖于特定的小工具,具体取决于你使用的语言。因此,如果你想调试 Node.js 应用,请使用vscode-node-debug2
小工具。如果你想调试 Go 应用,请使用vscode-go
小工具。如果你想调试客户端 JS 应用,请使用debugger-for-chrome
小工具。
不同的语言有不同的调试器。有些语言内置调试器,有些则使用外部库进行调试。如果我们想将多种语言集成到我们常用的编辑器/IDE 中,可能会变得很混乱。Python 调试器的通信方式与 Node 调试器不同。Node 调试器的行为方式与通过 Chrome 浏览器的前端 JS 调试器不同。如果您是 Ruby/JavaScript 开发人员,理论上您可以在编辑器/IDE 中同时安装 Ruby 和 Node 调试器。但这两个调试器可能使用不同的协议,并表现出不同的行为。您处理的语言/环境越多,复杂性就越高。
因此,在不同的语言环境中调试会变得非常混乱。为了协调这些差异,我们需要在 X 语言调试器和我们的编辑器/IDE 之间架起一座桥梁——我们需要一个抽象协议。这个协议被称为调试适配器协议 (DAP)。DAP 最初是由 VSCode 团队创建的。但幸运的是,他们决定让它(在某种程度上)与编辑器无关,这样开发者就可以将它用于其他编辑器/IDE。Vimspector 就是基于 DAP 为 Vim 编辑器开发的成果。要查看支持 DAP 的工具列表,请查看此页面:支持 DAP 的工具。
虽然不是必读内容,但当您有时间时,我强烈建议您查看这些页面以更好地了解 DAP:
好了,理论已经讲够了——让我们从一个实际的例子开始吧!
入门
首先,请安装 Vimspector(如果尚未安装)。请按照Vimspector 安装指南进行操作。
我使用的是vim-plug。我所做的就是在 vimrc 的插件列表中添加以下内容:
Plug 'puremourning/vimspector'
然后我获取了我的 vimrc 并运行:PlugInstall
。
示例
学习一项新技能最快的方法就是立即投入其中,并边学边学。你现在可能有很多疑问,但一旦你了解了它的原理,读完这篇文章后,我希望你的一些疑问能够得到解答。
让我们看一下节点示例。
调试节点应用程序
Vimspector 插件本身已经自带示例。请前往 Vim 保存 Vimspector 插件的目录。我使用的是 vim-plugged,所以我的插件安装在该plugged/
目录中。就我而言,该目录位于~/.vim/plugged/vimspector/
。您的插件可能位于其他位置,具体取决于您的插件管理器和系统。找到它们后,从vimspector/
目录转到/support/test/
目录。在该目录中,您会发现不同的示例,我强烈建议您在读完本文后查看一下。
由于本节是关于调试节点应用程序,让我们检查一下节点目录。
cd node/
在撰写本文时,您应该在其中找到一个名为的目录simple/
。转到那里,您将看到一个simple.js
文件。
在我们调试这个文件之前,使用 Vimspector 进行调试至少有两个要求:
- 相关的小工具。
- Vimspector 配置文件。
小工具是一种调试适配器(类似于微软的调试适配器页面中列出的适配器)。由于我们正在调试 Node 应用,因此需要一个 Node 适配器。该 Node 适配器将在 NodeJS 和抽象协议 DAP 之间中继消息。
(顺便说一句,在安装节点适配器之前,根据Vimspector 的站点,您需要使用 6 到 12 之间的 Node 版本)。
对于第一个要求,要安装 Node 适配器,请在 Vim 中运行:VimspectorInstall vscode-node-debug2
。调试不同的语言/环境时,需要不同的小工具。如果您需要调试 Python 文件,则必须安装 Python 小工具。如果您正在调试 Go 文件,则必须安装 Go 小工具。由于我们正在调试 Node 应用,因此需要安装 Node 小工具。有关小工具列表,请查看 Vimspector Github 页面中的以下部分:支持的语言。
对于第二个要求,该.vimspector.json
文件恰好位于项目的根目录(内部simple/
)。如果您检查目录的隐藏文件vimspector/support/test/simple/
,应该已经有一个.vimspector.json
可用的文件,因此您无需执行任何操作。请记住,在检查自己的项目时,请记得创建自己的 vimspector 文件。
满足这两个要求后,我们就可以进行调试了。
Vimspector 提供了许多命令和快捷键。新手入门时,要全部用上可能会让人不知所措。我发现以下这些命令和快捷键足以让你上手。以下是我在 vimrc 中设置的一些 Vimspector 快捷键:
nnoremap <Leader>dd :call vimspector#Launch()<CR>
nnoremap <Leader>de :call vimspector#Reset()<CR>
nnoremap <Leader>dc :call vimspector#Continue()<CR>
nnoremap <Leader>dt :call vimspector#ToggleBreakpoint()<CR>
nnoremap <Leader>dT :call vimspector#ClearBreakpoints()<CR>
nmap <Leader>dk <Plug>VimspectorRestart
nmap <Leader>dh <Plug>VimspectorStepOut
nmap <Leader>dl <Plug>VimspectorStepInto
nmap <Leader>dj <Plug>VimspectorStepOver
你可以随意借鉴我的经验,或者自己创造一些。Vimspector 还附带一组称为“人机模式映射”的快捷键。如果你习惯使用 VSCode 的调试快捷键,你可能会觉得它们更适合你。
最后,让我们打开simple.js
。它看起来应该是这样的:
var msg = 'Hello, world!'
var obj = {
test: 'testing',
toast: function() {
return 'toasty' + this.test;
}
}
console.log( "OK stuff happened " + obj.toast() )
要启动 vimspector,请运行 launch 命令。按下<Leader>dd
( :call vimspector#Launch()
)。你应该会看到一个 Vimspector 窗口。太酷了!
屏幕上应该会显示 6 个不同的窗口——根据 Vim 的方向,它们的显示顺序可能会有所不同。如果您以前从未使用过调试器,也不必担心。使用一段时间后,您会逐渐习惯其中一些功能。要退出 Vimspector,请按<Leader>de
( :call vimspector#Reset()
)。
顺便说一下,启动 vimspector 时,窗口底部会出现一些提示,要求输入类似 的内容...Break on Uncaught Exceptions?
。我通常会按N
。如果你不想一直收到提示,请在 vimspector.json 文件的块中添加以下内容"run": { ...
:
...
"breakpoints": {
"exception": {
"all": "N",
"uncaught": "N"
}
},
...
该文件的完整 vimspector json(因此您只需复制粘贴即可)如下所示:
{
"configurations": {
"run": {
"adapter": "vscode-node",
"breakpoints": {
"exception": {
"all": "N",
"uncaught": "N"
}
},
"configuration": {
"request": "launch",
"protocol": "auto",
"stopOnEntry": true,
"console": "integratedTerminal",
"program": "${workspaceRoot}/simple.js",
"cwd": "${workspaceRoot}"
}
}
}
}
回到调试,再次启动 vimspector ( <Leader>dd
)。由于我们必须stepOnEntry
在true
Vimspector json 文件中,所以即使你没有标记断点,Vimspector 也会在第一行停止。
要遍历文件,您可以:
- 走出(走出范围)
- 步入(步入函数范围)
- 跨过(在范围内跨到下一行)
以下是我使用的按键映射。请注意,我使用的hlj
按键与 Vim 的移动键类似。
nmap <Leader>dh <Plug>VimspectorStepOut
nmap <Leader>dl <Plug>VimspectorStepInto
nmap <Leader>dj <Plug>VimspectorStepOver
如果您不确定什么是走出去、走进去和跨过,我发现这些简短的视频教程非常有帮助:
回到我们的调试——现在我们位于代码的第一行,按下<Leader>dj
( <Plug>VimspectorStepOver
)。注意,高亮会移动到变量声明处。如果再按<Leader>dj
一次,它会再次向下移动。如果继续单步执行,最终会到达simple.js
文件末尾。遗憾的是,您无法“回退”到上一行。一旦进入下一步,您就会继续前进,直到到达末尾。
如果您不小心跳过了重要的一行,只需重启调试器即可。要重启,请运行<Leader>dk
( <Plug>VimspectorRestart
)。重启后,Vimspector 会从头开始运行。或者,您也可以重置并重新启动 Vimspector。
您可以在文件中设置断点。回到主simple.js
文件,在要添加断点的行上运行<Leader>dt
( :call vimspector#ToggleBreakpoint()
)(在断点所在的行再次运行该命令即可移除断点)。
在整个文件中添加断点后,再次启动 Vimspector。按下<Leader>dc
( :call vimspector#Continue()
) 键,Vimspector 就会跳转到下一个断点。太酷了!
要清除断点,请运行<Leader>dT
(:call vimspector#ClearBreakpoints()
)。
在进入下一部分之前,请花 10-15 分钟尝试一下simple.js
。更改中的代码simple.js
。四处走动。玩一玩。
在我们进入下一部分之前,让我们简单回顾一下 6 个 Vimspector 窗口的功能。
变量窗口
变量窗口包含相对于当前范围的可用变量(及其当前值)。
- Scope: Local
+ this (Object): Object
- __dirname (string): "/Users/iggy/.vim/plugged/vimspector/support/test/node/simple"
- __filename (string): "/Users/iggy/.vim/plugged/vimspector/support/test/node/simple/simple.js"
+ exports (Object): Object {}
+ module (Object): Module {id: ".", path: "/Users/iggy/.vim/plugged/vimspector/support/test/n…", exports: Object, …}
*- msg (undefined): undefined
*- obj (undefined): undefined
+ require (Function): function require(path) { … }
+ Scope: Global
在上面的例子中,在 Local 作用域内,我有一些常见的 Node 变量,例如this
、__dirname
,以及写成和__filename
的变量。当你跨过下一个变量时,请注意观察它们如何从 undefined 变为有值。msg
obj
尝试一下:跨过并进入不同的函数作用域。找到一种方法进入函数内部,查看可用的变量。然后跳出函数外部并进行比较。同时检查函数内部的内容Scope: Global
。为什么它有这些变量?这能告诉你关于 Node 的什么信息?
监视窗口
在“监视”窗口中,您可以监视特定的值。最初它是空白的。如果您想监视msg
变量的值,请在“监视”窗口中输入该变量msg
。当您位于文件开头时,该值为undefined
。然后,当您单步执行时,该值将变为'Hello, world!'
。
堆栈跟踪窗口
Stack Trace 窗口显示节点文件执行的调用堆栈。
控制台窗口
在控制台窗口中,您可以输入定义的变量,例如msg
。您还可以计算表达式的值。
终端窗口
终端窗口显示整个调试会话中的所有输出。
在浏览器中调试
让我们探索如何使用 Chrome 调试器调试客户端应用程序。幸运的是,Vimspector 目录中也有一个示例。在该~/.vim/plugged/vimspector/support/test/chrome/
目录中,您会找到一个名为run_server
、atest.js
和 a 的文件.vimspector.json
。
让我们看看 vimspector 配置文件里面有什么:
{
"configurations": {
"launch": {
"adapter": "chrome",
"configuration": {
"request": "launch",
"url": "http://localhost:1234/",
"webRoot": "${workspaceRoot}/www"
}
}
}
}
这个配置和我们之前看到的 Node Vimspector 配置确实有所不同。其中很重要的一行是"adapter": "chrome"
——它表明我们将使用 Chrome 适配器。URL 被定义为 on,localhost:1234
因为这是我们服务器运行的端口。
顺便说一句,稍后启动 Vimspector 时,它会再次提示你是否要在未捕获的异常等情况下中断。如果你不想处理这些提示,请.vimspector.json
像之前在 Node 应用程序中那样,在里面添加以下几行代码。
"breakpoints": {
"exception": {
"all": "N",
"uncaught": "N"
}
},
我们需要安装正确的小工具。在前面的部分,我们用 安装了一个小工具:VimspectorInstall vscode-node-debug2
。这次我们需要为 Chrome 安装一个。从 Vim 运行以下命令:
:VimspectorInstall debugger-for-chrome
完成后,运行服务器(确保已安装 PHP):
./run_server
查看localhost:1234
。您应该会看到一个带有一些弹出模式的简单应用程序。
现在打开www/js/test.js
。在任何你喜欢的地方添加断点。运行 Vimspector 启动命令<Leader>dd
。运行它,它会自动启动 Chrome 浏览器。Vimspector 会在断点处暂停 Chrome 浏览器。单步执行并进入断点。观察一些变量。修改代码。尽情体验吧!
注意:请查看“变量”窗口。同时检查“本地”和“全局”作用域。与使用 Node 调试时相比,你在“全局”作用域中发现了什么不同吗?这能说明客户端和后端代码执行的哪些方面?
注2:请注意,这次启动 Vimspector 时,它会启动一个 Chrome 浏览器。如果你查看.vimspector.json
,你会看到 ,而不是之前 Node 调试时显示"request": "launch",
的"request": "attach",
。它们有什么不同?调试应用程序的方法有两种:将其附加到已运行的进程或启动新进程。
调试 Jest 测试
现在让我们学习如何调试 Jest 测试。在本节中,我将使用vscode-recipes仓库。首先,访问网站并克隆仓库。然后,进入debugging-jest-tests/
目录。您将看到两个目录:lib/
和test/
。这是我们的工作区根目录。
首先,安装依赖项:npm i
。
确保 Jest 测试正在运行并且全部通过:npm run test
。
调试 Jest 测试需要 Node 调试器。如果您一直在输入,那么您应该已经拥有之前安装的 Node 调试器。使用 Vimspector,您只需安装一次小工具(Vimspector 会将所有已安装的小工具保存在 Vimspector 目录中 - 在我的情况下,它们存储在 中~/.vim/plugged/vimspector/
)。
在目录中debugging-jest-tests/
,添加一个.vimspector.json
文件。在其中:
"configurations": {
"my awesome jest test": {
"adapter": "vscode-node",
"breakpoints": {
"exception": {
"all": "N",
"uncaught": "N"
}
},
"configuration": {
"request": "launch",
"name": "Jest debugger",
"type": "node",
"console": "integratedTerminal",
"program": "${workspaceRoot}/node_modules/.bin/jest",
"skipFiles": ["*/<node_internals>/**/*.js", "node_modules/**/*.js"],
"cwd": "${workspaceRoot}"
}
}
}
}
该配置看起来很熟悉,新添加了:"program"
: " ${workspaceRoot}/node_modules/.bin/jest"
。以下是详细信息:
- 是
workspaceRoot
当前目录(debugging-jest-tests/
目录)。 - 这
node_modules/.bin/jest
是来自的 Jest 可执行文件node_modules/
(您应该在之后获得它npm i
)。
这次运行调试器时,Vimspector 需要运行 Jest 可执行文件。你可以在node_modules/
目录内找到 Jest 可执行文件。你也可以通过全局 Jest 命令来运行它,但我喜欢将其隔离开来(如果我从容器中运行它,并且不能保证有全局 Jest 命令怎么办?通过全局命令.bin/jest
,我可以保证有全局命令——但这只是我的个人偏好)。
太棒了!让我们在测试文件中设置一些断点,然后启动 Vimspector ( <Leader>dd
)。瞧!你的测试套件将暂停,现在你可以调试代码了。
如果将断点放在调用该函数的行上,如add()
下面的函数:
...
it('Should return correct result', () => {
const result = add(1, 2); // put a breakpoint here
expect(result).toEqual(3);
});
...
当你单步执行 ( <Leader>dl
) 时,它会进入 内部的原始函数声明lib/calc.js
,让你可以调查源代码。这太棒了!有了它,你可以调试错误的测试,直到函数的起源!
有一个问题。使用当前的 Vimspector 配置,它会在启动时运行所有测试。这很好,但在实际应用中,你的应用可能有数百个测试(如果你一直在实践 TDD……眨眨眼)。运行所有测试可能不是最好的生活方式。如果你想一次只运行一个特定的测试怎么办?
你一定可以!
在 Jest 中,你可以通过将文件名(或文件名的一部分)作为参数传递来运行特定文件。如果你只想运行add.spec.js
,可以运行命令jest add
。Jest 足够智能,会匹配add.spec.js
而不是subtract.spec.js
。
如果您想匹配文件中的特定测试,Jest 也足够智能,可以匹配您使用该-t
选项传递的任何关键字。
假设add.spec.js
我有两个测试(请注意,我修改了测试描述):
const { add } = require('../lib/calc');
describe('When adding numbers', () => {
it('one should return correct result', () => {
const result = add(1, 2);
expect(result).toEqual(3);
});
it('two should not return correct result', () => {
const result = add(1, 5);
expect(result).not.toEqual(3);
});
});
我只想运行第二个测试。为此,我可以从 CLI 运行test add -t two
。Jest 足够智能,可以只运行add.spec.js
测试!'two should not return correct result'
试试看。
有了这些知识,我们需要在运行 Vimspector 时传递这些参数。事实证明,Vimspector 有一个属性args
,你可以用它来向程序传递参数。
{
"configurations": {
"jest": {
"adapter": "vscode-node",
"breakpoints": {
"exception": {
"all": "N",
"uncaught": "N"
}
},
"configuration": {
"request": "launch",
"name": "Jest debugger",
"type": "node",
"console": "integratedTerminal",
"program": "${workspaceRoot}/node_modules/.bin/jest",
"skipFiles": ["*/<node_internals>/**/*.js", "node_modules/**/*.js"],
"cwd": "${workspaceRoot}",
"args": [
"${FileName}",
"-t",
"${TestName}"
]
}
}
}
}
我们的新属性args
是一个包含 3 个元素的数组。那些看起来很奇怪的变量(${FileName}
和${TestName}
)是 Vimspector 的命名参数。这样,我就可以在启动 Vimspector 时传递FileName
和变量了。TestName
让我们再次启动 Vimspector。这次它会提示:“输入 FileName 的值”(在其中输入“add”)。之后,Vimspector 会提示:“输入 TestName 的值”(在其中输入“two”)。然后观察它是否只运行了该文件中的那个测试。成功!
现在你没有理由不去实践 TDD!:D
调试 Express 应用
在接下来的示例中,我们尝试调试一个简单的 Express 应用。由于 Express 是一个 Node 库,因此需要一个 Node 小工具。
创建一个目录 ( mkdir express-debug
) 并进入其中。运行npm init -y
以初始化 NPM 项目。安装 express ( npm i express
)。然后创建一个app.js
。在其中:
const express = require('express');
const app = express();
const port = 3000;
const helloFunc = () => {
const hello = 'hello';
return hello;
};
app.get('/', (req, res) => {
const msg = helloFunc();
res.send(msg);
});
app.listen(port, () => {
console.log(`Example app listening on port ${port}!`)
});
在该目录中创建一个.vimspector.json
。至少应该包含:
{
"configurations": {
"run": {
"adapter": "vscode-node",
"default": true,
"configuration": {
"type": "node",
"request": "attach",
"processId": "${processId}"
}
}
}
}
但是,如果我们启用一些默认配置可能会更好:
{
"configurations": {
"run": {
"adapter": "vscode-node",
"default": true,
"breakpoints": {
"exception": {
"all": "N",
"uncaught": "N"
}
},
"configuration": {
"name": "Attaching to a process ID",
"type": "node",
"request": "attach",
"skipFiles": ["node_modules/**/*.js", "<node_internals>/**/*.js"],
"processId": "${processId}"
}
}
}
}
然后在检查模式下运行 express 应用程序:
node --inspect app.js
你应该会看到你的应用在 上运行localhost:3000
。接下来,在 中添加几个断点app.js
,然后启动 Vimspector(它也会要求processId
,但我发现不给它任何值,只需按下 Return/Enter 键就可以了)。
最后,刷新页面,你会看到调试器在第一个断点处暂停。从那里,你可以单步退出、单步进入和单步跳过你的代码。
恭喜!您已成功调试 Express 应用。
下一步是什么?
本文只是粗略地介绍了一下调试器的功能。Vimspector 还有很多其他功能。希望通过从不同角度的了解,你能更深入地理解这个插件。
您的调试需求可能与我在本文中给出的示例不同,但我真诚地相信,如果您了解其背后的原理,您应该能够实现 Vimspector 来满足您的需求。
感谢你读到这里。祝你 Vim 愉快!
文章来源:https://dev.to/iggredible/debugging-in-vim-with-vimspector-4n0m