If you want to see the video version of this content, it’s on my YouTube channel here: https://youtu.be/yMs97o_TdBU.

For the last year or so I’ve been collating Neovim tips I’ve picked up along the way. Here, in no particular order, are a bunch of ‘Power User’ tips.

Up and down through wrapped text

If you are coming from more modern conventional editors, Vim does this thing where if you have a visually wrapped line and you go up or down to get to the next line, Vim goes to the next actual line.

To get to the next visual line you can use the g key to move between these kind of lines when you need to. So, just g and then the direction you want to go to. I’m an arrow keys guy but if you are on QWERTY you can do gk or gj.

Paste the same thing over multiple selections

Often I have a string of text that I want to paste in a couple or more places.

What usually happens is this. You yank the text you want to use, visually select the first area you want to overwrite and paste. All good. You move to the second instance, visually select the area, paste and… wait, what?? So what just happened there?

When you Put over some selected text, Vim effectively cuts that underlying text and as deleted text always gets placed into a register, it just so happens it goes in the register that Vim had your prior yanked text in.

Turns out there is a simple way to avoid this pattern and it had evaded me for months. The answer is to use a capital P to put instead of the standard little p. That’s all there is to it. You can paste with a capital P as many times you want and it will not overwrite the register so your original yanked text is available to you as many times as you need.

Put above/below, not in the middle

If you yank part of a line, and then try and paste that above or below another line with a simple p, you’ll find Neovim will try and place it directly in the line you are on. Not above or below.

If you want to paste, or ‘put’ on an actual line above or below, use the :pu and :pu! commands. You might find it useful to remap these to something even easier. I have mine to [p and ]p respectively.

Delete every instance of

If you have a number of instances of text, such as a bunch of log statements in your buffer, you can remove them all in one go with :g/console/d, where console is the thing you want to find, and d is delete. Amend to suit! The only thing to be aware of is that won’t catch multi line logs.

Diffing files, showing blame

There are a couple of ways I find it useful to diff files. For me, I’m usually wanting to compare the current file with a prior version of the same file. If you have Gitsigns installed, which I recommend you do, you can do :Gitsigns diffthis ~1 in the command line, where the number is the prior version you want to diff.

The other option is using Telescopes built it builtin.git_bcommits which shows prior versions. Pressing return will check that one out.

A bonus feature of Gitsigns I get a lot of use out of is the :Gitsigns toggle_current_line_blame which will show you who is responsible for each line. It’s a way of finding out, it was indeed you that messed the code up, not your colleague, before actually accusing them, which has done wonders for team morale where I work.

Pasting file names

I’m often wiring up assets into CSS files and I want to get the filename and/or path from Telescope to stick into a file. Whatever the reason, you might also find it useful to get the filename and path.

We can do this by adding a couple of functions and mappings to our Telescope file browser. I’m adding these in my telescope.lua file. If you want to know how I handle my config, you might want to take a look at this other video.

Here are the two functions. One adds the filename, the other adds the path and filename.

--- Insert filename into the current buffer and keeping the insert mode.
actions.insert_name_i = function(prompt_bufnr)
  local symbol = action_state.get_selected_entry().ordinal
  actions.close(prompt_bufnr)
  vim.schedule(function()
    vim.cmd([[startinsert]])
    vim.api.nvim_put({ symbol }, "", true, true)
  end)
end

--- Insert file path and name into the current buffer and keeping the insert mode.
actions.insert_name_and_path_i = function(prompt_bufnr)
  local symbol = action_state.get_selected_entry().value
  actions.close(prompt_bufnr)
  vim.schedule(function()
    vim.cmd([[startinsert]])
    vim.api.nvim_put({ symbol }, "", true, true)
  end)
end

Then we map them like this:

    file_browser = {
      theme = "ivy",
      hidden = true,
      mappings = {
        ["i"] = {
          ["<S-M>"] = fb_actions.move,
          ["<C-Y>"] = actions.insert_name_i,
          ["<C-P>"] = actions.insert_name_and_path_i,
        },
      },
    },

Obviously amend the mappings to suit, but now I can paste the filenmane or full path into my buffer with ease.

Get the path of a buffer so you can paste it somewhere

So, lets suppose you want to paste the filename and relative path of the file you are working on somewhere. Well, # and % have special meaning in Vim. # is your alternate file, and % is your current one. And the registers of Vim automatically save these for you. Use :reg and you can see them listed.

So, if you want to paste the current filename and path into your buffer, you can do "%p. If you want paste the alternate one, do "#p.

That’s great if you are just copying and pasting in Vim, but what about if you want that filename to paste into an issue ticket, for example. Simplest way is to make the system clipboard, the +, be equal to the path, the %, which we can do by saying let the system clipboard be the current buffername: :let @+=@%. Now it is in your clipboard.

I’ve given up trying to remember this and added a mapping:

-- Get the current buffer name and path and add it to the system clipboard
km.set("n", "<Leader>xn", ":let @+=@%<cr>", { desc = "Copy Buffer name and path" })

Select non-uniform strings down multiple lines

Suppose we have a bunch of text like this:

import { york1400 } from "./data/york1400";
import { york1430 } from "./data/york1430";
import { york1500 } from "./data/york1500";
import { york1530 } from "./data/york1530";
import { york1600 } from "./data/york1600";
import { york1630 } from "./data/york1630";
import { sanddown1405 } from "./data/sanddown1405";
import { sanddown1435 } from "./data/sanddown1435";
import { sanddown1505 } from "./data/sanddown1505";
import { sanddown1535 } from "./data/sanddown1535";
import { sanddown1605 } from "./data/sanddown1605";
import { sanddown1635 } from "./data/sanddown1635";

And we want to select everything inside the {}. This is the sort of task that multiple cursors in modern editors shine at. Super fast and super intuitive. As ever, there is a way with Vim, even if not immediately obvious.

We can’t use visual block mode, as the strings are an uneven length.

But we can achieve this by selecting all the lines in question and then running :'<,'>norm "Ayi{. This runs Normal mode and then selects the ‘“’ register and Appends into it by Yanking Inside {.

Comments on the YouTube version also revealed you could call out to awk and do :%!awk '{print $3}'. By default awk splits lines on space so print $3 gets the third ‘word’ in each line.

Appending to a text based option in Lua

With that prior example, by default, you will get each string appended right after each other, effectively making a single line. That likely isn’t what you want.

There is an option you can set in cpoptions+=> but if you have a Lua config, appending options isn’t very well documented. Here is a couple of ways…

You can’t do opt.cpoptions = opt.cpoptions .. ">" to try Lua string concatenation but that doesn’t work in this case.

Instead, I think the simplest way is to do opt.cpoptions:append(">"). You can also do opt.cpoptions = opt.cpoptions + (">").

With that option in place, our prior, differing string length copy will go into the register with each string on a new line.

Clear a register

Need to clear a register? Easiest way is q[register]q so if you wanted to clear the A register before doing the previous tip, you would do qAq. This is actually recording a blank macro to it.

Start neovim with an alternate startup file

You can start Neovim up with a different startup file. That can be pretty handy if you have a problem with your config. Here is how you can do that:

nvim -u ~/.config/nvim/minimal.lua

The key thing being the -u flag. Then just supply a path to the alternate startup file on disk. I have no idea what the u is supposed to stand for. Even the help files don’t offer an explanation.

Dealing with splits more effectively

When coding, I’m often interested in a couple of files at once, say TypeScript on one side, and the CSS on the other. But I’ll often want to maximise one of those for a spell.

Well, you likely know you can go ctrl+w, ctrl+w to move from one window to the other. Put there are a few more goodies I’m trying to use more often:

  • ctrw+w, ctrl+= will equally split the windows, useful if you have just resized your Vim window
  • ctrl+w,ctrl+r will ‘rotate’ the windows. If you have two side by side, it will flip them. If you have move than two splits it makes more sense as it rotates around them
  • ctrl+w, ctrl+| will horizontally maximise the current buffer (you can just see the other buffer here). Use the = shortcut to get back
  • ctrl+w, ctrl+_ does the same thing but vertically

Now, these are practically begging to be remapped. I use <Leader>arrow to move around and <Leader>] to go full width etc

I also have mappings to allow me to go <leader>1, or <leader>2 to move to each numbered window. More about that in my NeovimConf talk if you are interested. Being able to quickly jump between windows has been one of the best productivity gains I have made.

Finally, one option I now always set relating to windows is the equalalways option. Setting this to true automatically makes windows equal width when one is closed.

opt.equalalways = true -- make windows the same width when closing one

Manipulate your buffer with CLI programs

Suppose you have a bunch of markup, and you realise you no longer need the wrapping ‘span’ elements.

<p>
    Here is a paragraph
    <span>with lines I don't want</span>
</p>
<p>
    Here is a paragraph
    <span>with lines I don't want</span>
</p>
<p>
    Here is a paragraph
    <span>with lines I don't want</span>
</p>
<p>
    Here is a paragraph
    <span>with lines I don't want</span>
</p>
<p>
    Here is a paragraph
    <span>with lines I don't want</span>
</p>

You can strip any line out you want by leveraging something like Grep, or ripgrep in this instance.

Select the text, then enter COMMAND mode and add '<,'>!rg -v span. If you don’t have rg for ripgrep, it will work just as well with grep. That will inVert the pattern, in this case, ‘span’ and remove lines that match!

We could also perform that same action with the g command, but it’s worth remembering that we have a plethora of CLI tools at our disposal from Vim.

Let’s look at another example.

Piping commands to external programs

Let’s say we are working on a piece of documentation that we are writing in Markdown. In another editor, when it is time to convert it to HTML, we would need a plugin or a different tool to handle the conversion. In Vim we can make use of other tools directly. In this scenario, I can pipe my buffer into Pandoc. Because we are using the CLI to do the conversion, we can also give it some extra meta info, for example a title. Or if I wanted this to be a standalone file, I can pass that argument so it gets the head etc. But even more useful, as I usually want to paste this into WordPress, I can actually pipe it straight to pbcopy and then when I execute this, the converted output is already in my clipboard!

Here is what that command would look like:

:%w !pandoc --standalone --metadata title="My title here" | pbcopy

You can also change the output style. I tend to export from markdown to html when writing code blocks. If you don’t want the pre tags wrapped in some other divs, you can pass the --no-highlights flag. And avoid Pandoc adding unwanted wrapping with the --wrap flag like this:

:%w !pandoc --no-highlight --wrap=none | pbcopy

Did you know you can convert Word files easily with Pandoc, for example ~/word-file.docx -o ~/markdown-file.md --wrap=none -t markdown-smart

Again, if one of these things is something you do often, it’s hard to remember the exact flag so I added a mapping for it:

-- Send the current markdown file into pandoc, convert it and paste it to clipboard
km.set(
  "v",
  "<leader>xp",
  ":'<,'> w !pandoc --no-highlight --wrap=none | pbcopy <CR>",
  { silent = true, desc = "Pandoc Export" }
)

Delete everything on every line after a certain character

I still find this much harder to think with Vim than with Sublime’s multiple cursors. However, if you can remember it, there is a nice terse syntax for Vim.

Suppose you have a bunch of lines that have a character, or sequence of characters and you want to delete everything after it.

1 ev: "P", 15 30

And you actually want to delete everything after the , but crucially keep the comma. You might thing of doing something like <,> norm f,D but that is going to take your comma with it.

You could work from the opposite end. So doing :norm $dF,x. That goes to the end of the line with $, then d deletes, F for backwards, , for the comma we want to go back to, then x to delete the last character under the cursor. Not super intuitive, but it does the job.

Instead, you could also accomplish it with, :%s/, .*/,. That’s searching for the comma, replacing everything after it with what we specify, which in this case is just the comma.

Or, you could use normal mode. So after selecting the area you are interested in, do :'<,'>norm f,C, which works in a similar vein. It’s moving forward to the comma and then Changing (big C changes everything from that point to the end of the line) everything to just a ,.

As is often the case in Vim, there are a few ways to solve the problem.

Operate on multiple non-consecutive lines

Let’s suppose you have something like a bunch of selectors in CSS:

.thing1 {
    background-image: url("1.svg");
}
.thing2 {
    background-image: url("2.svg");
}
.thing3 {
    background-image: url("3.svg");
}

And you want to update each of their lines and wrap them so they end up like this:

.thing1 {
    &::after {
        background-image: url("1.svg");
    }
}
.thing2 {
    &::after {
        background-image: url("2.svg");
    }
}
.thing3 {
    &::after {
        background-image: url("3.svg");
    }
}

How do we do that efficiently in Vim?

This has been a long term issue for me. I come from using multiple cursors in Sublime which I think is a more straightforward way of solving the issue. As is usually the case in Vim, when things seem too hard it is often because I am approaching the problem the wrong way.

In Sublime or any standard multi-cursor approach, I place a cursor everywhere I want to make the change and then make it.

With Vim, the most effective I approach I have found is the inverse. I make the change in one place and then choose all the other places I want to make it. It can seem more laborious but in reality I don’t think there is much in it. Although what Vim currently lacks is any kind of feedback as you write the macro as to what will happen across the other instances.

So, let’s go to the first applicable line.

Now, start recording a macro, which I typically do to the a register with qa. You will see a little recording message down the bottom left.

Now, make your edits to get this first line in the shape you want it. Try and use actual motions, rather than relying on cursor movements, as they typically translate across different text situations. So move by a word rather than 5 chars with you cursor (for example). When you are ready, press q again to end the recording.

Now we want to find all the instances we want to do this change on. You might make a selection, or make a selection and then operate on something particular in that selection which is usually what I find myself doing. So, I make a selection and then run :g/pattern/norm! @a which is doing a global search and then finding all instances that match the pattern (pattern in this case being a string which matches where you want to apply the multiple changes. With those found, the norm! @a part sticks Vim into normal mode and then runs the macro we assigned to the @a register.

Now all that sounds a bit much at first but once you have done it a few times it is pretty much equivalent to multi-cursors but just remember the principal is the other way around; you make the change you want to see, and then assign it to the places you want to make it.

Set a keymap to run a command and function

Turns out you run multiple functions from a mapping in Lua. Take a look at this. First it runs a vim command and then it runs another function. The key thing is wrapping your commands in an anonymous Lua function. Then you can pretty much do what you want.

vim.keymap.set("n", "n", function()
  vim.cmd("silent normal! nzz")
  require("lualine").refresh()
end)

That will run the keymap nzz in normal mode and then whatever command you need. In this case, I’m refreshing Lualine.

Deleting a sentence from anywhere in the sentence

I have used d) to delete a sentence for a long time, or d( to delete backwards to the beginning of a sentence. What I don’t think is quite so obvious is that you can do das for Delete Around Sentence. That works for anywhere in the sentence.

Conclusion

We’ve covered a bunch of tips here. Some very simple, some more complex. Hopefully you have learned at least one new thing here? Love any further ways any of these approaches can be improved. Let me know in the comments below.