Why I chose vscode-neovim over VSCodeVim
A comparison between the two VSCode Vim extensions
2021-03-22 • 10 min read
I like using Vim. I also like using VSCode. But I can’t really go back to typing in insert mode by default anymore. To get the best of both worlds, I appeal to the extension that provides the Vim keymap. At the time of writing, the VSCodeVim extension is the most popular Vim keymap with 2.4 million installs. The second most popular one is vscode-neovim.
Which one should you use? Which one is “better” (It is always about trade off in coding. There is no absolute best). ? I have worked with both extension for a substantially long enough time. I would definitely recommend vscode-neovim over VSCodeVim. My goal is to tell you why I prefer one over the other.
VSCodeVim and vscode-neovim are implemented differently. VSCodeVim simulated the Vim controls using JavaScript code and directly manipulated the text buffer within VSCode. On the other hand, vscode-neovim uses an actual Neovim instance. The key presses are sent directly to the neovim instance and the text buffer is manipulated by Neovim instead. This means that vscode-neovim should have a more accurate and complete functionality mapping from Vim. Here, I show a few differences of behavior of vscode-neovim and VSCodeVim.
Visual Block Selection
One of the feature that I love the most about Vim is visual block selection. It let’s you select a 2D block from the two-dimensional plane of the text editor. Then you can yank (copy) the selected block and move it around. This is what it looks like with vscode-neovim.
Visual block mode in vscode-neovim
The behavior of visual block in vscode-neovim is exactly the same as in Vim. However, this feature doesn’t work properly within VSCodeVim. Take a look at the following demo:
Broken visual block mode in VSCodeVim
The reason why I think that did not work is that VSCodeVim probably kept the block selected text as a string with newline inserted in the buffer. When it is pasted somewhere else, it inserts the newline from the buffer as well. This gives us less flexibility when it comes to text manipulation.
Performance
People has claimed that the vscode-neovim is more performant than VSCodeVim. Here we see a few review under vscode-neovim:
Skye Nygaard 20/01/2021: This is so so much faster than the normal vscode vim extension, no lag, works like vim should
Grant Mercer 03/12/2020: After spending hours and hours trying to optimize VSCodeVIM so it wouldn’t lag on my large django codebase I decided to give Neo Vim a try.
Night and day difference. Neo Vim is lightning fast and I’m so glad I don’t have to suffer through huge keypress lag just to have VIM shortcuts. Incredible extension
I can’t easily recreate a performance issue. However, from how the two extensions implement macros, we can see why vscode-neovim is faster than VSCodeVim.
Macro in vscode-neovim
Let me explain what command I just issued there:
qq
: start recording a macro to the bufferq
:dw
: delete the next word from the cursorj
: move down one line
q
: stop recording macro10@q
: invoke the macro in the bufferq
for10
times
Combined into a sequence of key strokes: qqdwjq10@q
.
Therefore, the macro simply delete the first word of the line, and move the the line below. Repeating the macro times will delete the first word for lines. Yes, this is the power of Vim. You just gotta admire it.
We can actual do the same thing within VSCodeVim, using the exactly same key stroke we used above.
Macro in VSCodeVim
But this time, notice how we are able to perceive command being executed 10 times serially right in front of us. 10 lines might not be too bad. However, imagine a super complex macro applied to the entire file of over 200 lines. 200 lines are not uncommon code file size. At the same time, there is a heavy language server running in the background against the large code base, like a django code base mentioned by one of the review. All the static analysis and syntax highlighting everytime the view is updated. This can be a real pain. My guess is that VSCodeVim updated the buffer and the view (the DOM since VSCode runs in electron) everytime the macro is executed. And all these happens in a blocking manner. Vscode-neovim only updates the view once after all the macros have been applied, hence its superior perforamnce.
This constantly causes me issue when I am using VSCodeVim. Sometimes, I forgot
that I hit a number like 100
. Then, I just went ahead to insert mode and type.
(100ilove
ESC) After escape, the text I typed got reinputted 100
times. Then, I need to wait for it to finish rendering. Even if I hit undo u
,
it has to finish the repetition completely before it can undo.
Undoing
The next issue with VSCodeVim is undoing. In Vim, the key to undo is u
. In
VSCode, or any common software really, the short cut to perform an undo is
Ctrl/Cmd+z. In VSCodeVim, the vim u
undoing and VSCode
control-z undoing has different behavior. Again using the macro example above,
we take a look at the difference in behavior in the u
undo and control-z undo:
u
undo in VSCodeVim
control-z undo in VSCodeVim
In this example, u
undoes all eleven times of macro. However, the control-z
undoes only one time of the macro. Now imagine what happens if you hit
control-z, and then hit u
? Is it going to redo the undone macro? If so, what
is the meaning of undo if the behavior is redo? If not, is our buffer being put
into a unpredictable state? This behavior is extremely confusing. My code file
has definitely be put in some state that is broken and unrecoverable.
The undo stack also causes a lot of problems for other people as well, as outlined in these two issues on the VSCodeVim GitHub:
I quote one user from the GitHub issue:
jcjolley commented on 13 Oct 2017 FWIW, I also am experiencing this bug. Hitting undo is like rolling the dice.
In vscode-neovim, the behavior is consistent. I didn’t have to worry about by code file being put in a state that is broken.
gqip
in markdown list
gqip
is another feature that I love about Vim once I found out about it. It
indents the current paragraph of text such that the line width doesn’t exceed a
pre-configured maximum line width, defined by the variable textwidth
.
However, the feature needs to be a little smarter when it comes to markup files like Markdown. Consider the following example:
- a short line
- a super long line within a list that is so so so so so so so so so so so so so so so so long
Since the two lines are directly next to each other, they are in the same
“paragraph” and performing gqip
should format both line. But a human knows
that only the second line should be wrapped and the first line should stay
unchanged since we know Markdown relies on newline as a syntax to separate two
list items. Let’s see how this is handled within vscode-neovim and VSCodeVim.
gqip
in vscode-neovim
gqip
in VSCodeVim
Hey, vscode-neovim is actually very smart. Well, since it uses an actual Neovim instance, we should say that Neovim is smart. It knows to only wrap around the second list item but not the first. It even indented the wrapped line such that it matches the first character after the marker. However, the VSCodeVim is not language-aware and treats two adjacent list item as the same paragraph and performed the line wrap.
The smart line wrapping matters a lot to me since I takes a lot of my class notes in Markdown and it makes life easier if it can wrap my line smartly.
Search and replace
The difference in search and replace boils down to the two regex engine driving
the :substitute
function within Vim in VSCode. Here, I will explain the
difference with the capture group syntax as an illustrating example on how the
two differs.
VSCodeVim uses the JavaScript regex engine. Therefore, in the match expression
we can use any regex syntax supported by the JavaScript regex engine (refer to
this MDN
cheatsheet
for a quick reference for regex syntax support). By default, capture group is
denoted with unescaped parantheses. The expression (\d+)
is a capture group
that captures a substring with one or more digits. vscode-neovim uses the
program sed
for its search and replace function. This means whatever is
supported by sed
is supported by :s
. To write the same capture group, one
will say instead \(\d\+\)
. To be fair, the JavaScript one will be easier to
write in this case.
Another point of difference is referencing capture group in the replace
pattern. According to the Visual Studio
documentation,
referencing capture group in VSCode is done with $1
. sed
uses \1
. For the
same search and replace, one will need to write different command in VSCodeVim
and vscode-neovim.
# In VSCodeVim
:s/(\S+\(\))/package.$1/g
# In vscode-neovim
:s/\(\S\+()\)/package.\1/g
Let me explain the regex here. We are matching for function calls with no
parameter, where \S
matches non-whitespace character and it is followed by
()
. This matches any expression like error()
or someGenericThunk()
. We
put this match in a capture group. In the replace pattern, we will prepend
package.
to the matched group. Therefore, error()
will turn into
package.error()
and someGenericThunk()
will turn into
package.someGenericThunk()
. The g
indicate search replace globally across
each line. It will replace all occurences of a matched pattern in a line.
Choosing which one you like is a matter of habit. Since I am pretty comfortable with the Vim regex, then I think vscode-neovim is more for me. If you think that you like the JavaScript regex better, go ahead and try VSCodeVim!
vscode-neovim is not all perfect
Overall, I think vscode-neovim allows for an smoother adaptation from Vim to VSCode. It matches actual Vim behavior and has better performance. Even better, we can make use of the Neovim config file to customize behavior. You get basically all the power of Vim through vscode-neovim. However, it is not without drawback.
First, its selection is not actual VSCode selection. What does this mean? It means that I will not be able to copy a selected chunk of text using controll/cmd-c. For instance, if a select a word in visual mode and attempt to copy it with cmd-c. It does not work!
Broken cmd-c copy and paste in vscode-neovim
Copying with cmd-c copyed the current line where the cursor is located, doesn’t exist out of visual mode. When paste happens, we are still in visual mode and more chunk of text are selected. If we want something in copied to the system clipboard buffer, we can invoke the copy command using the command selector.
cmd-c copy and paste in vscode-neovim through the command picker
On the other hand, VSCodeVim support cmd-c copying seemlessly. However, this is not really a big deal for me since in the terminal Vim, I cannot use cmd-c copying anyway.
Another drawback of vscode-neovim is setting up. It requires a system installation of Neovim. VSCodeVim only requires the extension to be installed and there are no other dependencies. Setting VSCodeVim is way easier than vscode-neovim.
There are also other limitations of vscode-neovim that I won’t list here. The author of the extension seems to know the limitation pretty well and they are documented in the README of vscode-neovim. It gives a good idea to the user of the extension on what to expect.
In conclusion, I like vscode-neovim since it fits I find it more comfortable, which is the most important. However, if you like how VSCodeVim does things, go ahead as well! Again, it is all about trade-offs.