Neovim comes with lots of useful plugins, and built-in features, compared to Vim. It’s lua configs is also much more readable and powerful compared to Vim. Here’s some useful tips for coding in Nvim.
Searching(files, text, diagnostics, help)#
Similar to techniques mentioned in
search-in-vim, we can search for text inside
files, or looking for files by filtering filenames. In Neovim, we use fzf-lua
which is very similar to fzf.vim.
Using FZF picker
FZF’s UI consists with a picker (fuzzy search a list of entries) and a
preview window. In picker, use c-j/k/n/p(or c-u/d) to move.
Tab/S-Tab to select files. The default action (Enter) is to edit or
send to qflist, depending on number of selections. If want to edit multiple
files, use c-e.
Another useful command is “resume”(<Leader>sr), which picks up the previous
FZF search.
Find files with fuzzy matching#
If you know which file you want to open, using the files picker and fuzzy search. I created different keymaps for looking for files in different scopes.
| Use | Keymap | Details |
|---|---|---|
| Files in PWD | <Leader>f | PWD |
| Recent files (MRU) | <Leader>o | Very useful to search through previously opened files |
| Sibling files of buffer | <Leader>s. | Also includes files in subfolders |
| Project-wide files (git) | <Leader>gf | Only sees git files |
| Any path starting with buffer dir | <Leader>sf | Can modify path before press Enter |
Search text#
If you want to find some text among files, grep is the way to go. fzf allow
you to grep then fuzzy search the results for refinement. Similar to find files,
there are different keymaps for grepping in different scopes.
Grep in files#
| Use | Keymap | Details |
|---|---|---|
| Files in PWD | <leader>/ | Use keyword -- glob to filter files. (! for nagative patterns) |
| Git root | <leader>g/ | Same as above |
| current Word/WORD | <leader>w/W | w also works in visual mode |
live grep: Both keymaps uses “live grep”, meaning each keystroke would run a
new ripgrep command and feed the result. this means we can test our grep
expressions on-the-fly.
glob: In live grep, globs can be added with -- after keywords. Example:
-- *.lua !*.md lib/**/*.c.
*means any character**means any folders (recursively)!means negative matching.
So, our example expression *.lua !*.md lib/**/*.c means, grep on lua files,
and all c files under lib/, but excluding any markdown files.
Fuzzy search buffer lines#
Besides real grep, two convenient methods allows fuzzy-searching lines in buffers:
| Use | Keymap | Details |
|---|---|---|
| Search current buffer lines | <leader>sb | [S]earch [B]uffer lines |
| Search all open buffers | <leader>so | [S]earch [O]pen buffer lines |
Grep git repo stuff#
Git repository contains other useful info that we usually want to search for, like (current buffer) commits, branches, etc. FZF provides a convenient way to search through them.
The most useful one would be search commit history of current buffer, via the
:FzfLua bcommits command. I’ve created two keymaps for them:
| Use | Keymap | Details |
|---|---|---|
| Search commits containing current buffer | <leader>gb | [G]it [B]uffer commits |
| Search all commits | <leader>gc | [G]it [C]ommits |
Search vim help#
FZF also is convenient to search for help tags and keymaps. (through sh and
sk followed by <leader>). There’s also one for Man pages with <leader>sm.
Treesitter#
Treesitter parses files into code objects, which can not only provide better highlighting, but also brings a whole new set of motion objects, that can use in both operation-pending mode and visual mode. This allows a much easier and more accurate text manipulation.
Treesitter objects for selection and motion#
I’ve defined text objects with the following letters:
c: Classesm: Methods/functions definitionsf: Function callsi: Conditional (if) statementsl: Loopsa: Args=: assignments
All objects support jumps to prev/next via [ and ]. Upper case goes to the
end while lower case goes to the beginning. Here’s a few examples:
[c: start of prev class]L: end of net loop[f: start of prev function call
A more powerful way to use TS objects is via selection like other text objects like words, paragraphs, etc. Visual selection is one way to do it, but it’s more powerful with operation-pending mode. Some examples:
cr=: [C]hange [R]hs of [=]caa: [C]hange a [A]rgumentcii: [C]hange [i]n [I]f: This is very useful, depending on cursor position. It selects the “if” condition, or selects the conditional body when cursor is inside the body.daf: Delete a function call.
I also have Flash plugin to quick select a block. Triggered by S or R.
S: select a block from current cursor positionR: select a remote block. Type the letters in the remote block, then colored labels will appear to allow you to select blocks.
[x is another special and useful hotkey. If the cursor is deeply nested
several levels down, the current scope will show at top of the screen. [x
jumps to the start of a “upper” scope.
See it in action:
Outlines#
Treesitter also provides more accurate outlines of documents. I installed
aerial.nvim for showing outlines.
- To toggle aerial window on the side:
<leader>a(<leader>Astays in aerial window)
- To jump to next/prev outline item:
<leader>{and<leader>}
LSP and diagnostics#
Neovim supports LSP natively. It brings diagnostics, signature help, documentation, code actions and formatting capabilities.
Diagnostics#
Diagnostics shows info, warning or errors of the code. My default setting will show diagnostics icons on the gutter, and virtual text in each line.
Work with diagnostics#
| Use | Keymap | Details |
|---|---|---|
| Previous diagnostics | [d | |
| Next diagnostics | ]d | |
| Toggle diagnostics | <leader>dt | Toggles virtual text |
| Show floating box | <leader>df | Shows float box with details |
| Fzf search diagnostics | <leader>sd | Searches document diagnostics |
Once at a diagnostics point, you may see code actions at the cursor position, which may provide useful fixes to the issues.
| Show code actions | <leader>ca | Some LSP may not provide useful ones |
You can also put diagnostics to quickfix list so you can process with commands
like :cdo / :cfdo or :ldo / :lfdo
| Send to quickfix list | <leader>dq | Can then use :cdo or :cfdo to process |
| Send to location list | <leader>dl |
Accessing diagnostics with trouble.nvim#
| Buffer Diagnostics | <leader>xX | Toggle the trouble window |
| Workspace Diagnostics | <leader>xx | |
| Quickfix list | <leader>xq |
Rename and code actions#
| Rename | <leader>rn | Rename via LSP API |
| Code action | <leader>ca |
Auto complete#
keymaps#
| Use | Keymap | Description |
|---|---|---|
| Prev/Next | <C-j> / <C-k> | |
| Close | <C-c> | |
| Scroll | <C-b> / <C-f> | |
| Confirm | <C-y> | Not CR or TAB |
| Move next/prev location | <C-h> / <C-l> | Work in INSERT mode, Useful in snippets |
| Next choice | <C-e> | For choice node in snippets |
Snippets#
Luasnip is the plugin to provide snippets to auto completion.
My own snippets are created and located at dotfiles/nvim/lua/snippets/,
filenames needs to match the filetype.
I also created helper snippets for lua so that creating new snippets skeleton
is super fast. The snippets can be triggered with:
snipf: For complex snippets withinodes, etc.snipt: For simple text snippets (no expansion locations or dynamic stuff)
To create snippets, create a [ft].lua file, then use the snippets above to
quickly add new content.
My Snippets#
Markdown
scfor Hugo shortcodes.
Plugin details#
Auto complete is provided by nvim-cmp. It take completion choices from configurable sources, like LSP, snippets, path, buffer, etc.
A list of cmp sources can be found at nvim-cmp Wiki
Formatting#
Auto format#
I use conform.nvim to format files. Auto cmds are created to format on save
for certain file types. I currently use a whitelisted approach instead of let
formatters to format files by default, as I don’t want to break stuff, and
manually triggering formatting is not that hard with the keymap:
| Use | Keymap | Description |
|---|---|---|
| Manually trigger formatting | ,f | Calls Conform |
| Format injected code | ,mf | Like code blocks in Markdown |
conform.nvim is a “middleman” that calls external formatters (and fallback to
LSP), so you need to configure formatters for different filetypes, and install
the formatters separately, following their own doc.
See :h conform-formatters
Formatter setup#
- Prettierd config to format markdown with 80 column width
By default, prettier(and prettierd) will not break lines for markdown files.
I needed to create a config override for prettierd to force it.
- Create a custom config file with this single line and save it to
.config/nvim/utils/linter-config/.prettierd.toml
| |
- Set environment variable for prettierd in
conform’sformatterssection:
| |
Jumps with leap and grapple#
Leap within the reach of your eyes#
To quickly jump in the current view, use leap. It’s like using a mouse: jump
to where your eye is looking at. (in contrast to regular Vim motions that moves
over “text objects” that has some meaning)
How to use
Starts with s, then type two letters. You’ll either jump to there already, or
need to press a label key. Since the label is displayed from beginning, typing
the labels won’t slow you down.
Another important tip is using gs jump to the other window, which saves the
window move.
Some special characters when using leap:
<space>end of line,<space><space>: blank lines- All types of brackets are considered equivalent
Remote operation#
It’s quite often that we want to modify a remote location like yank something and put it where the cursor is. Normally, we’ll have to:
- Move to the remote location
- Perform the operation (yank/delete/etc.)
- Jump back with
c-o - Paste or continue typing
Steps 1 and 3 can be saved with remote operation. When in operation-pending mode, press “r” to trigger it. Now the new process is
- Want to yank something remotely, press
y, thenrto enter remote mode. - Start typing to match the remote location, and press the label key. Now the cursor is in the remote location
- Type the motion like
aw,i{, etc. Operation is done on the remote text and now you’re back where you started.
Syntactical selections with flash#
When writing code, you can easily select a syntactical block, with the help from treesitter and flash.
| Feature | Keymap | Comment |
|---|---|---|
| Select a block containing current cursor | S | Shows labels around different levels of blocks |
| Select a block remotely | R | After press R, start typing the remote location text, and labels will appear for selecting blocks around that location. |
Jump between files#
When working on some projects, we often jump between a few important files.
Examples like {build, test, header, impl, reference}. Switching buffers is still
not fast enough: buf list is usually noisy with other stuff. Even with fzf, we
need to type words for filtering each time we switch.
Grapple allows marking files with tags, and cycle through them. It’s like
global marks, but with names, project scoping, and better UI. Here’s the common
workflow. We also have status bar integration to show all tags and active
status.
| Use | Keymap | Details |
|---|---|---|
| Add tag | <Leader>ma | Can enter an optional name for the tag. |
| Remove a tag(untag) | <Leader>md | Untag the current file. |
| Move to next tag | <Leader>n | I only mapped foward-move |
| Show all tags and select | <Leader>mk | Provides single-key hotkeys to jump |
File management#
oil.nvim allows you edit your file system like a vim buffer. On save, it
applies the changes to the file system. It much more convenient than doing shell
commands to create folders, move files, etc.
oil.nvim is configured to be the default file explorer in nvim and can be
triggered by nvim . in shell, or like :e . in cmdline.
Working with Git#
Two plugins are spciefically for git:
gitsigns: Gutter signs for diff, with helpers to stage/unstage and show inline diff, blame, etc.Fugitive: classic plugin.
fzf.lua also provides pickers for search git commit history.
Inline diff and staging#
When editing files, gitsigns makes it easy to:
- Jump through all hunks of changes in the file (
[h,]h) - Toggle inline diff (against staged version, or last commit) (
hd,hD,hp) - See blame info (
hb) - Stage/unstage hunks (
hs,hS,hu)
All keymaps are under <leader>h (hunk)
| Use | Keymap | Details |
|---|---|---|
| Move between changes/hunks | [h / ]h | Hunks are changed blocks compared to staged version. |
| Preview hunk diff | <Leader>hp | Inline preview of diff of this hunk. |
| Diff the buffer(side-by-side) | <Leader>hd / Leader>hD | d: diff against staged version; D: against last commit(~) |
| Quit diff view | <Leader>hq | |
| Stage buffer/hunk | <Leader>hS/hs | Upper case for buffer, lower for hunk. |
| Reset buffer/hunk | <Leader>hR/hr | Upper case for buffer, lower for hunk. |
| Unstage hunk | <Leader>hu | |
| Blame line | <Leader>hb | shows full blame in float window, with commit, hunk, etc. |
| Toggle blame inlay | <Leader>htb | Shows blame of curline in normal mode |
| Toggle deleted hunk display | <Leader>htd | Show/hide deleted hunks |
Stage files and Create commit#
Staging buffers while editing using gitsigns is convenient. Another
way to see the full status of the project and do stage/unstage, see diff and
create commits are using Fugitive’s “status” view.
- Open git status with
<leader>gs - Move between files using
(,) - Toggle diff of file under cursor with
= - Toggle stage with
-. (or uses,u) - hard reset a file with
X - Create a commit using
cc
Handling Merge Conflicts#
Explore git history with Fugitive#
- Commits / logs
- History files
- Diff