tea☆ · 1mo · discuss
Wait, shouldn’t this be the other way around? Note Because it confused me a bit while writing I’ll be using “vim” to refer to the general concept which includes both Vim and Neovim (and a lot of the time also regular vi) and “Vim” to refer to the specific program. I first started using Vim some time in 2014 when a coworker taught me how to deploy new versions of our application onto our servers. Just the basics to get me started, and I left it at that for a couple years. I was comfortable enough to use it for editing config files, but never looked into more than the basic motions. In 2019 I started customizing Vim by adding the gruvbox colorscheme and vim-airline to have the cool™ statusline with the chevrons as separators that I saw everyone use online. I don’t remember where I first saw gruvbox but the colors are just really pleasant to me and I still use it as the color scheme for my terminal and code editors to this day. In mid 2023 I switched to Neovim. I think one of my coworkers who was more up to date with Linux stuff (and using Arch1) was talking about it. At that time I pretty much only used it as a drop-in replacement for my existing .vimrc that had been mostly unchanged since 2019. I was definitely using vim a lot more by this point, from doing small source code edits (because it took too long for IntelliJ to open a project) to even writing the occasional macro. Then in late 2023 I saw a video that showcased code completion using LSP in Neovim and pointed to kickstart.nvim as a good way to get started with it. So that’s exactly what I did. I started off with kickstart.nvim as a base, got a feel for how to navigate around projects, and slowly made it my own. Up to this I had primarily used VSCode to edit code (outside of work, where I was still primarily using IntelliJ), but I wouldn’t say I ever enjoyed using it. Insert VSCode rant here. Since I was fresh into my Elixir phase I decided to work on all my projects exclusively in Neovim, which included my attempt at that year’s Advent of Code and the static site generator that I blogged about in the past. I enjoyed working with Neovim. It was fun. Navigating between code felt quick. It was just a single editor window without any tool- or sidebars. (Okay really, why does VSCode and all the new versions of Microsoft Office have this giant sidebar on the left side with only three buttons? Yes, of course I need a dedicated sidebar button in Outlook that opens OneDrive.) I tried using “vim motions” in other editors before making Neovim my main editor, but it always felt incomplete. Sure, you can quickly navigate within the same file, but if you want to do more than that you always need to reach for your editor’s shortcuts (which likely even conflict with some of the vim ones). You can’t control everything while being in vim mode. Even at work I started to use Neovim for more and more things, only reaching for IntelliJ when I had to do some deep debugging. Okay enough being sentimental, I can start to get to my point now. I generally think it’s a good idea to learn how to use something vi shaped. It’s on every single system, it’s easy to use (once you get past the “can’t quit” jokes) and every additional bit that you learn can be really helpful. And somehow Neovim prevented me from learning more of those additional bits. Last week there was some drama on Neovim’s issue tracker (which I’m not gonna go into), but Michcia mentioned switching from Neovim back to Vim – which I initially reacted to with something along the lines of “that would be too much work for me since it is my primary editor and I’m used to all these plugins”. Well, despite that I kept thinking about what it would take to switch to Vim and made my first attempts that very same day. Then last weekend I spent way too much time configuring Vim while trying out different plugins while trying to pinpoint what I like about my Neovim setup and how I could replicate it with Vim. Since then (and while having the opportunity to try it out at work) I came up with the following list of things that I would like to have in Vim, roughly ordered by priority: LSP and code navigation Java debugging Diff editing Syntax highlighting LSP and code navigation In Neovim I was using the builtin LSP together with telescope.nvim to quickly navigate between files in a project. I heavily use shortcuts like gd (go to definition of a variable), gr (got to references of a variable), <leader>sf (search files by name), <leader>sg (grep in files) and <leader>sw (search for the current word in all files). After searching for LSP support in Vim I found out that there’s 5-ish plugins that are commonly used, of which I tried out the following ones: yegappan/lsp (“Vim9 LSP”) – A newer plugin written in Vim9 script (which should make it faster than alternatives written in vanilla Vim script that I did not consider). YouCompleteMe – A plugin written in Python that comes out of the box with support for a couple languages. coc.nvim – A plugin written in TypeScript that tries to be easy to use by offering extensions for most languages instead of making you configure the LSP directly. I started with the Vim9 LSP plugin and didn’t have that many issues. It felt similar to how I was used to configuring LSP in Neovim. But what annoyed me was that for some reason initializing rust-analyzer blocks the editor, meaning I have to wait a couple seconds before I can do anything.2 So that’s when I tried out YouCompleteMe, mostly because it has “easy” integration with a debugger (more on that in the next section). But idk, the configuration felt bad, it wasn’t super clear what to do. On top of that some advanced configuration could only be done in Python. And sure the whole thing is written in Python, but when you’re a Vim plugin please let me do all the configuration in Vim script (or some JSON file if you really have to). There was definitely more that annoyed me about it which made me try the next option. I knew about coc.nvim because it came up a lot when I set up LSP for Neovim. But I never paid it much attention since Neovim had its own builtin LSP at that point and using a separate TypeScript thing that talked with lots of additional extensions seemed a bit unnecessary. Well, I was pleasantly surprised when I tried it out in Vim. Extensions are easy to install, you barely have to configure anything, and if you do there’s really good auto completion for the config file that feels a lot like editing settings.json in VSCode. In the end I switched back to the Vim9 LSP – not because I had any major issues with coc.nvim, but because of the second part of this section: code navigation. I am really used to telescope.nvim. The popups are nice, there’s fuzzy finding, it feels well integrated into Neovim and is one of the reasons I enjoyed using it so much. In the terminal I’m already used to fzf so I tried out fzf.vim which more or less displays the output of fzf in a nice looking popup inside of Vim. And that’s already the main issue I have with it: Since it is fzf all the shortcuts you already know for filtering etc. will just apply. But since it is not a pure vim plugin it doesn’t support things like syntax highlighting. If you have bat installed it will use that for syntax highlighting, but this will also mean the highlighting is completely different from what you normally see inside of Vim.3 I kept with it for a couple days because coc.nvim has an extension that shows popups using fzf.nvim, but in the end I got too annoyed4 and decided to look for alternatives. That’s when I found out about fuzzbox.vim which has a plugin that makes it integrate with the Vim9 LSP. So I threw away everything I had set up using coc.nvim and fzf.nvim and decided to give Vim9 LSP (in combination with fuzzbox.vim) another try. And yeah, it does look and feel much nicer over all (despite having to wait 5 seconds every time I open a Rust project). It gives me back that feeling of using telescope.nvim and in turn makes using Vim a lot more fun. Yay, one problem solved! On to the next one. Java debugging This is probably the most annoying section and it even was (and still is) in Neovim. When working with Java I often have to debug things, be it test executions or running applications. In 2024 I was using Neovim to write most of my code, but when it came to debugging I always started IntelliJ. But gradually I looked more into how to debug things with Neovim and ended up with the following setup: nvim-dap – Provides support for the Debug Adapter Protocol that is also used by VSCode. nvim-dap-ui – A graphical debugger for Neovim that allows you to see the values of variables and evaluate commands at the current location (it also has buttons to skip over lines etc. which I always instead of setting keybinds for them). nvim-jdtls – Provides better integration with the Java LSP and allows easy test execution (for a full class or the closest method). Overall this worked well enough, but felt very prone to breakage. It required registering both the vscode-java-test and vscode-java-debug extensions with the LSP, of which vsocde-java-test very often had conflicts in its dependencies that required manual workarounds. Some time last month test execution stopped working completely for me, and while I tried to roll back to previous versions nothing really helped. Knowing this I imagined that getting debugging working in Vim might be the biggest hurdle. There’s a popular graphical debugger for Vim called vimspector, which from the screenshots looked like what I wanted and has builtin support for the Debug Adapter Protocol. But how could I integrate it with Java? The readme mentions integration with YouCompleteMe (which is the main reason I tried it out as an LSP plugin), but apart from being too frustrated with it I now realize that there’s no real “integration” and instead the readme just documents how to manually start the debug server provided by vscode-java-debug. In the issues of vimspector someone also mentioned that there’s a coc.nvim extension, so I tried it out for a bit and while debugging works fine I realized that the main thing I was missing was the support for test execution provided by nvim-jdtls. But since I know how to manually start a debugger for a single test (mvn test -Dmaven.surefire.debug -Dtest=TestClass#testMethod) I thought why not try to write some Vim script that takes care of getting the debugger running and tells coc-java-debug to connect to it afterwards. So I did that and, while the usability felt a bit worse than nvim-jdtls, it worked! When I switched back to the Vim9 LSP I had to find another solution (there weren’t any extensions available) and not yet having realized that the vimspector readme actually told me how to do it with any LSP I stumbled upon this blog post that went into more detail on manually starting the debug server and then connecting vimspector to it (using vim-lsp, one of the LSP plugins I didn’t try out). Unfortunately my config for this is still very much work-in-progress, but hopefully I’ll remember to link it here once I’m done with it. In short I created two commands (TestMethod and TestClass) that figure out the name of the current class (expand('%:t:r')) and method (currently I’m hacky and search backwards for @Test and then the first word after “void”) and then use term_start() to start debugging the test with Maven. This has a callback attached that looks for the “Listening for transport dt_socket at address: 5005” message. Afterwards it uses the rpc() command provided by the Vim9 LSP plugin to tell jdtls to start the debug server provided by vscode-java-debug. This returns the port, which in turn gets passed to vimspector’s vimspector#LaunchWithSettings({DAPPort: port}) and automatically opens the graphical debugger. This was definitely the biggest hurdle and the only part where I had to write more than a few line of Vim script, but it works fine and feels more resilient because it only depends on vscode-java-debug (which doesn’t have any dependencies and thus has less chance of having conflicts) and not vscode-java-test. Diff editing I switched to Jujutsu as my primary way to interact with commits in my Git repos some time last year (except for work projects that require Git LFS, which unfortunately isn’t well supports by Jujutsu yet). As part of that I’ve gotten used to “interactive committing/squashing” to create a new commit from a selection of my changes or squash them into an existing one. Jujutsu has a builtin tool for this which feels a bit clunky to use, so I had previously switched to hunk.nvim. Things I like about hunk.nvim: It has a list of changed files on the left side that allows me to select individual files or whole folders to include in my commit. Showing the diff for a file is a single button press (right arrow key). I can switch the diff splits and select individual lines to include with regular vim motion. Things I don’t like about hunk.nvim: You have to select both the deleted lines (in the left split) and the changed lines (in the right split), otherwise it’ll add the new lines next to the deleted ones instead of replacing them (there might be a shortcut for this but I never looked into it). It obviously doesn’t work with Vim. I took a look through the Jujutsu wiki (which is how I originally found out about hunk.nvim) and tried out vim-dirdiff. This did give me a file list (at the bottom) together with a split diff view, but some of its behavior felt weird, it wasn’t actively maintained and I was too focused on getting something closer to hunk.nvim that I looked for another option. On the same page (for some reason only under the Neovim section) it is mentioned how to use vim as a diff editor for existing Git tooling. This requires setting up a script that will create a temporary repository which has all your changes uncommitted. Because of this I spent more time learning how diff editing works in vim, and how to do it using vim-fugitive (which was installed because it was included with kickstart.nvim, but I had pretty much only used it for the occasional :G blame). So I made some changes in a repo, set up the script for Jujutsu, executed a jj split, and was placed inside of an empty Vim window. Now I could do :G to show the list of changes, stage them with s and quit Vim, which automatically opened the window where I could type my commit message. One thing that annoyed me was that deleted files were automatically staged, so I made some changes to the script (replacing git add --intent-to-add -A with git add --intent-to-add --ignore-removal .) and changed the Vim invocation to immediately drop me into vim-fugitive’s summary window (vim -c 'Git | bdelete 1). At first I tried working with the diff editor (opening a changed file and doing :Gvdiffsplit or pressing dv on a file name) which involved learning about :diffput (dp) and :diffget (do) to send changes to or fetch changes from the other split window. It’s funny, I’ve been using vim for diffing stuff for years and only now learned that there’s shortcuts to make it easier than manually copying and deleting lines. This works well and is quite powerful (you can even stage individual word changes which wasn’t possible with hunk.nvim) but overall involves too many keypresses for a simple “I quickly want to squash theses lines into the previous commit”. vim-fugitive also has an option to show an inline diff for a file right in the summary window using either = to toggle or >/< to open and close it. All of these keys are a bit hard to reach or require an additional modifier so I decided to remap them to the arrows keys like I was used to from hunk.nvim (on my Moonlander keyboard the left/right arrow can be quickly reached by my thumb, which I don’t really need for staging changes): autocmd FileType fugitive nmap <buffer> <Left> <plug>fugitive:< autocmd FileType fugitive nmap <buffer> <Right> <plug>fugitive:> This isn’t exactly the workflow I was used to, but after trying it out a bit it feels more efficient, because I stay in one buffer. And since deleted and added lines are displayed right next to each other I can do a quick visual select and press s to stage them instead of having to switch between three splits. more powerful, because I can always fall back to :Gvdiffsplit when I care about more than simple line changes. Syntax highlighting This is the least important point since it is only a very minor issue. Of course Vim has syntax highlighting. The Vim9 LSP even supports “semantic highlighting” (where you can get slightly better syntax highlighting via information from the LSP, although this won’t apply to the previews in fuzzbox.vim). But I’ve also gotten really used to how code is highlighted with nvim-treesitter. So I’ll either get used to how it looks without it, or write my own highlighting rules if it annoys me too much, or one of the experiments for adding treesitter support to Vim will get stable enough.5 Conclusion There are definitely some annoyances left, but overall I’m happy with the switch. The majority of things I’m used to from Neovim also work in Vim and in a lot of cases they even feel more responsive (this might just be placebo, but I can imagine that plugins like fuzzbox.vim that are written in Vim9 script are faster than the Lua alternatives for Neovim). Switching to Vim actually made me learn more about my editor since now I’m using more default features (like diff editing), had to write my own scripts, and rely (slightly) less on plugins that completely change how things work. PS: I completely forgot about it until I was about to publish but I also really liked using oil.nvim to switch between and copy/move/delete files. The only alternative for Vim I could find was SupraWater, which has some weird issues sometimes but overall still does what I want. I had a brief phase in uni where I was running Arch on an old ThinkPad X200T, but apart from that I mostly stuck with openSUSE (since our servers at work back in the day were running SLES and I liked the mascot and it came in handy a couple of times to use something RPM based because we built RPM packages and all our customers were also on SLES or RHEL) before I ended up switching everything to NixOS. ↩ The documentation says to configure rust-analyzer with syncInit: true and I tried setting it to false, but none of the other LSP plugins had this issue. I should look into it some time… ↩ Whenever I click on one of those “must use Linux tools” videos I always see bat as one of the recommended programs, but I never got the point of it? If I want to cat something it means I want to take a quick glance at the contents and don’t care about the syntax, because if I care about the syntax I will just open it in vim. Maybe integration with things like fzf is where bat shines more: Not as a standalone tool but as something you can integrate if you want some basic syntax highlighting. ↩ coc.nvim and its extensions are such a mishmash of languages that make me just wonder “why”. LSP has a feature that allows you to show “document symbols” and “workspace symbols”. Naturally coc.nvim has an API to access those, but for some reason there’s no way to show them in a (native Vim) popup like it does with “go to references”? coc-fzf solves this for “document symbols”, but for “workspace symbols” it needs the Neovim Python client (which obviously doesn’t work in Vim). Why does this require Python in the first place when coc.nvim already has the necessary data? ↩ vim-clap currently has “experimental” plugin support for things like treesitter among other things. I’m not that interested in its main feature (the fuzzy finders don’t look as nice as fuzzbox.vim, although that makes them feel more “Vim native”), but maybe I’ll give it a try some time since one of the plugins provides LSP support as well. ↩