aboutsummaryrefslogtreecommitdiff
path: root/content/weblog/2022-01-25_debugging-in-vim/index.md
blob: a281e6d03c5bd2aedcce719514b92bf5e51f0f99 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
+++
title = "How I debug programs in Vim"
date = 2022-01-25T19:49:30Z
+++

I like to use separate tools for different tasks, and also like it when those
tools integrate with the other tools I use. Unfortunately I haven't seen much
information online for possible workflows on debugging programs with Vim. There
is information but it is all spread out. So I decided to write about the
workflow that I've built over the years for writing and debugging programs with
Vim.

<!-- more -->

Many, or most, IDEs already come with some sort of debugging facility. But
those IDEs are not vim, and more often than not, they also come with a lot of
other features that frankly are not very useful. And no, vimifcation plugins
don't count, most of them don't cover even half the functionality that vim
offers.

I love using vim (technically neovim, eh, same diff), I really think that it is
the best text editor out there. Hell, I even, unironically, think it might the
program with best UI/UX. Sure it is not intuitive at all ([insert obligatory
exit vim meme](#exit-vim)), but once you learn the ropes of it, and it doesn't
take as long as
they meme it to be, it's just bliss. In shot, vim is a lifestyle, not a program.

The problem is that learning to be a power user of vim can take time. And to be
able to efficiently develop programs typing them is not enough. You also need
to navigate around code bases, quickly fix the most basic of errors (such as
typos), reduce repetitive tasks (e.g. autocomplete), and of course, debug and
profile.

Fortunately both vim and neovim, for at least some 3–4 years now, offer
facilities to integrate software development and debugging tools with ease.
Since I use neovim, some things that I talk about here might differ a little
bit.

## Development tools

It used to be that each editor/IDE needed to have plugins developed for, or even
baked into, them individually. This meant that efforts had to split for every
and each language and editor combination, which meant that not every editor had
support even every popular language.

Fortunately, Microsoft did a very un-Microsoft thing, and instead of baking in
another completely new implementation of IDE-like development tools for each
language to their VSCode editor, they developed what now is called the Language
Server Protocol[^1], and with the help of Red Hat et al they open sourced it and
standardized it.

In short, the Language Server Protocol (LSP) is just a JSON RPC protocol for
communications between a server that provides the functions, such as
autocompletion, linting, go-to-definition, etc. and the client which would be
the IDE or editor, which basically displays the results. And one of the editors
that supports LSP is vim. In fact, at least in neovim, the support is built in.

### LSP

In order to use LSP in neovim, you need to enable the plugin and configure the
LSP server(s) for your language(s). The good thing is that configuring it is
pretty simple and straightforward, especially since there is a plugin that
already comes with default configurations for a lot of popular languages LSPs.

I use [vim-plug](https://github.com/junegunn/vim-plug) to manage my plugins, so
first I added the official [lspconfig](https://github.com/neovim/nvim-lspconfig)
plugin:

```vim
"...
	Plug 'neovim/nvim-lspconfig'
"...
```

And then proceeded to configure it for my needs by adding the configurations for
the LSP servers for C, Go, Rust, Python and Javascript, the languages that I use
the most often:

```vim
" LSP
	set omnifunc=v:lua.vim.lsp.omnifunc
	lua require('lspconfig').clangd.setup{filetypes = { "c", "cpp", "objc", "objcpp", "ch" }}
	lua require('lspconfig').gopls.setup{}
	lua require('lspconfig').pylsp.setup{}
	lua require('lspconfig').rls.setup{}
	lua require('lspconfig').tsserver.setup{}

" LSP keybinds
	nmap <silent> gd <cmd>lua vim.lsp.buf.definition()<CR>
	nmap <silent> gD <cmd>lua vim.lsp.buf.declaration()<CR>
	nmap <silent> gK <cmd>lua vim.lsp.buf.hover()<CR>
	nmap <silent> <leader>n <cmd>lua vim.lsp.buf.rename()<CR>
	nmap <silent> <leader>b <cmd>lua vim.lsp.buf.formatting()<CR>

" neovim overrides my omnifunc with whatever ccomplete is, so I use this
	autocmd FileType c,ch,header,cpp setlocal omnifunc=v:lua.vim.lsp.omnifunc
```

Of course, in order to use the functionality, you also need to have the LSP
servers installed in your system. In the case of Go, it comes with the standard
toolchain, same as with Rust. For C, you will need clang, as far as I know GCC
doesn't provide an LSP implementation.

The keybindings in my config make it so that I can go to definition with `gd`,
go to declaration with `gD`, get a popup with information implementations and
comment documentation with `gK`, rename variables with `<leader>n` (leader is
space in my config) and process the file through a formatting tool (e.g. gofmt)
with `<leader>b`.

### Other plugins

Now, LSP provides most of the functions you would expect from an IDE, but there
are still some other plugins that I use for better quality of life. Namely:

```vim
"...
	Plug 'ervandew/supertab'
	Plug 'majutsushi/tagbar'
	Plug 'tpope/vim-commentary'
"...
```

The first one, [supertab](https://github.com/ervandew/supertab), makes using
autocompletion much more comfy. By default the omnifunc autocomplete is bound to
\<C-x\>\<C-o\> which is not very comfortable to enter. Usually, in shells and
other editors, you use Tab to autocomplete, which is a pretty sane way to do it.
But sometimes you actually need to insert tabs manually. That's where this
plugin comes in, since it intelligently chooses whether to insert a tab, or
autocomplete on pressing the Tab key.

The next is [tagbar](https://github.com/preservim/tagbar). This plugin allows
you to open up a sidebar of sorts that lists the global variables,
data/struct/type definitions, and functions in the current source file. It
requires a ctags implementation to work.

Finally is [vim-commentary](https://github.com/tpope/vim-commentary), which
provides an easy way to comment out multiple lines easily. Not AS useful as the
other ones, but still handy nonetheless.

## Debugging

Sooner or later when writing programs and testing them you come across some kind
of programming error that causes it to misbehave. You can try to find the
problem manually by looking at the code and scratching your head, but a much
better way is to fire up a debugger and go step-by-step over the program
execution in search of your bug.

For some time, at least for C and other languages that are supported by it, I
would just open gdb in a different terminal window and keep my source file open
in vim in another. But a couple of additions were made to both vim and neovim
which made it much pleasurable to debug programs.

The first is `terminal` which, just as its name implies, is a terminal emulator
inside vim. Basically it allows you to open a terminal inside a vim buffer. For
me personally this feature by itself is not of great use, since I use a tiling
WM which already makes it much more comfortable for me to move around terminals.
However, it gives way to a plugin that is much more useful, and which depends on
this functionality.

Introducing `Termdebug`. This is a plugin which is built into both Vim (version
>= 8.1) and Neovim. What it basically does is it opens two split windows, one
with gdb and the second one with the output of your program. You can then input
gdb commands directly into the gdb console, or through some vim-provided
shortcuts.

In order to start using Termdebug, you need to first load the plugin:

```vim
:packadd termdebug
```

And then load it providing the name of the binary that is going to be executed
by gdb:

```vim
:Termdebug <path to binary>
```

You will be greeted with two new split buffers, one with the program output and
the other with a gdb console. They are both terminal buffers. The way you use
those terminal buffers in vim is, you enter insert mode to type into the
terminal, and exit insert mode with \<C-\\\>\<C-n\>. Why not escape? Because
some terminal programs make use of escape, for example, vi mode in bash or zsh.
Or maybe even another vim instance inside vim, why not ¯\\_(ツ)_/¯.

After having opened Termdebug, you can run the program the classic way from gdb.
Or from within vim with `:Run <args>`. Other useful shortcuts are `:Break` to
set a breakpoint at the current line of code, `:Clear` to delete a breakpoint at
the current line if there's any, `:Continue` to resume program execution, etc.

[![A screenshot of an example debugging session](debugging-session.png)](debugging-session.png)
<figcaption>

An example debugging session.
</figcaption>

What makes this really wonderful is the fact that you see the process of
debugging straight in your source file. You don't have to constantly list the
code inside gdb, and you can just navigate around files to set or clear
breakpoints.

Of course, since you're just debugging programs with gdb the usual rules of
debugging with gdb apply. For example, you need to compile your program with
debugging symbols, i.e. with the `-g` flag.

## Afterword

These are just of the plugins and configurations that use in vim to help me
write and debug software. This is, of course not meant as an in-depth tutorial
or guide on how to configure vim or how to properly debug programs. This is
just a mere guide on how one can use vim in conjunction with other tools to
develop and debug programs. There's a lot of information on the internet on how
to configure vim or how to debug programs with gdb, and using other tools such
as valgrind and ASan.

If you want to know more about lsp in neovim, you can read the help page inside
neovim by entering `:help lsp`. The same goes with Termdebugger: `:help
Termdebugger`.

If you are interested, you can check my full neovim config here:
<https://git.yaroslavps.com/configs/vimrice/>

<figcaption id="exit-vim">

![How to exit vim](quit-vim.jpg)

If you have never used Vim before, and this article caused you to open it in a
whim and now you can't exit it, this picture might be helpful.
</figcaption>

[^1]: Not completely Unix-y, but somewhat adherent to the Unix philosophy in the
  sense that one program should do one thing and do it right. A shock coming
  from Microsoft, I know. You can learn more about it here:
  <https://microsoft.github.io/language-server-protocol/>