hanger.nvim
Ever since I switched to Neovim from VSCode, I’ve never looked back — I’ve become way more productive, and honestly, I can't imagine going back to any other text editor (trust me, I tried, but it just felt wrong). However, there was one thing I really missed: the "run" button above my tests and runnables. I used it way more than I care to admit to rapidly iterate on my unit tests, all while lazily hitting a shortcut. So, naturally, I built my own version in Neovim.


Since I mainly work with Rust, Go, and JavaScript, I started by building support for those three. I used Tree-sitter to parse the code into syntax nodes so I could navigate the structure and figure out which test the cursor is currently in, as well as whether the file itself contains tests.
To run tests, I built a Telescope picker that lists all the detected tests or test groups in the current file. Each item in the list includes a preview that shows the actual test content, so you can quickly scan through them. The test surrounding your current cursor position is automatically selected when the picker opens, giving you a clear sense of where you are in the file. It also shows the type of each test (e.g. unit, suite) for better context. Alternatively, you can run the test under your cursor instantly with a shortcut — no UI involved.
For JavaScript, it currently supports Jest. For Go, it works with both go test and testify. For Rust, it uses cargo test. I initially relied on the rust-analyzer LSP to fetch runnable items, but eventually I built my own test runner for more control and consistency.
While building my own solution, I studied how test runners work in editors like Zed and VSCode. While they’re solid, I ran into some issues — especially with Go and Jest. In Go, if two or more test functions have the same name but belong to different test suites, running one of them in VSCode would execute both. I noticed a similar issue in Jest: if multiple tests share the same description string, triggering one would end up running all of them. That made quick, focused iteration really difficult. So I took the time to handle those edge cases properly. My runner ensures that even tests with the same name or label — as long as they’re distinct in structure or context — can be executed individually without side effects.
By default, tests run in Neovim’s built-in terminal pane. But I’ve also integrated support for Zellij — if it’s enabled, tests will run in a separate floating Zellij pane, which keeps the Neovim workspace clean and focused. I’m also planning to add support for tmux in the future. For now, it works with Neovim’s terminal and Zellij.

