After migrating from Vim to Emacs as my primary C++ editor in 2015, I switched from Vim to Neovim for miscellaneous non-C++ tasks as it is more convenient in a terminal. Customizing the editor with a language you are comfortable with is important. I found myself increasingly drawn to Neovim's terminal-based simplicity for various non-C++ tasks. Recently, I've refined my Neovim setup to the point where I can confidently migrate my entire C++ workflow away from Emacs. This post explores the key improvements I've made to achieve this seamless transition.
Key mapping
I've implemented custom functions that simplify key mappings.
1 | local function map(mode, lhs, rhs, opts) |
Cross references
Like many developers, I spend significantly more time reading code than writing it. Efficiently navigating definitions and references is crucial for productivity.
Many Emacs and Neovim configurations advocate for gd
.
However, its placement on the QWERTY keyboard can be less than ideal.
For years, I relied on M-j
to quickly jump to
definitions.
To avoid a conflict with my recent zellij change (I adopted
M-hjkl
for pane navigation), I've reassigned J to trigger
definition searches. While losing the original J
(join
lines) functionality is unfortunate, vJ
provides a suitable
alternative.
1 | nmap('J', '<cmd>Telescope lsp_definitions<cr>', 'Definitions') |
I've adopted x
as a prefix key for cross-referencing
extensions, further streamlining the process. dl
provide a
suitable alternative for x
's original functionality.
1 | nmap('xB', '<cmd>CclsBaseHierarchy<cr>') |
I utilize xn
and xp
to find the the next or
previous reference. The implementation, copied from from LazyVim, only
works with references within the current file. I want to enable the
xn
map to automatically transition to the next file when
reaching the last reference in the current file.
Semantic highlighting
I've implemented rainbow semantic highlighting using ccls and LSP Semantic Tokens. Please refer to ccls and LSP Semantic Tokens for my setup.
Window navigation
While I've been content with the traditional C-w + hjkl
mapping for years, I've recently opted for the more efficient
C-hjkl
approach.
1 | nmap('<C-h>', '<C-w>h') |
This streamlined approach mirrors my pane navigation preferences in
tmux and zellij, where I utilize M-hjkl
.
To accommodate this change, I've shifted my tmux prefix key from
C-l
to C-Space
. Consequently, I've also
adjusted my input method toggling from C-Space
to
C-S-Space
.
Debugging
For C++ debugging, I primarily rely on cgdb. I find it superior to
GDB's single-key mode and significantly more user-friendly than LLDB's
gui
command.
1 | cgdb --args ./a.out args |
I typically arrange Neovim and cgdb side-by-side in tmux or zellij. During single-stepping, when encountering interesting code snippets, I often need to manually input filenames into Neovim. While Telescope aids in this process, automatic file and line updates would be ideal.
Given these considerations, nvim-dap appears to be a promising solution. However, I haven't yet determined the optimal configuration for integrating rr with nvim-dap.
Live grep
I've defined mappings to streamline directory and project-wide searches using Telescope's live grep functionality:
1 | nmap('<leader>sd', '<cmd>lua require("telescope.builtin").live_grep({cwd=vim.fn.expand("%:p:h")})<cr>', 'Search directory') |
Additionally, I've mapped M-n to insert the word under the cursor,
mimicking Emacs Ivy's M-n (ivy-next-history-element)
behavior.
Lua
Neovim embraces Lua as a preferred scripting language. While Lua's
syntax is lightweight and easy to learn, it doesn't shy away from
convenience features like func 'arg'
and
func {a=42}
.
LuaJIT offers exceptional performance.
LuaJIT with the JIT enabled is much faster than all of the other languages benchmarked, including Wren, because Mike Pall is a robot from the future. -- wren.io
This translates into noticeably smoother editing with LSP, especially for hefty C++ files – a significant advantage over Emacs. With Emacs, I've always felt that editing a large C++ file is slow.
The non-default local
variables and 1-based indexing
(shared with languages like Awk and Julia) are annoyances that I can
live with when using a configuration language. So far, I've only needed
index-sensitive looping in one specific location.
1 |
|