Chapter 12. Searching…​

It’s kind of amazing that we’ve gotten this far without covering searching. Find and replace in Vim has always been far more powerful and nuanced than in most editors, which just give you a little dialog with three fields and, if you’re lucky, a check box to specify regular expressions.

LazyVim extends Neovim’s powerful search feature to make it easier to use and prettier. You know about the Seek (s) and Treesitter (S) modes for navigating to and selecting objects you can see, as well as their remote operator-pending objects counterparts: r and R. These work great when the text you are looking for is currently visible. However, when you need to search a file and have it automatically scroll to search results, they are insufficient.

12.1. Search in Current File

To search for a pattern in Vim use the / command in Normal mode. The mnemonic is that the / key is also the question mark key, and searching for something is a kind of question.

Many tools that are not considered “modal” have adopted Vi’s / as a command to invoke search. For example, the exceptional Linear task tracking tool uses / to begin a search, as does the ubiquitous GitHub.

The first time you type a / in Normal mode, you might lose your cursor! It doesn’t pop up a new window. Instead, / takes over the current file’s status bar with a magnifying glass icon:

search dat dark
Figure 55. Search “dat”

In this image, I’ve typed /dat. The / initiates Search mode, and then I searched for dat. My cursor is in the search box.

As you can see, LazyVim has helpfully highlighted all the (visible) matches for “dat”. The "primary" result will always be the first matching result after the point where your cursor was when you hit /.

Your cursor will jump to the primary result if you press Enter to confirm your search. Press Escape to cancel it, as usual.

At this point, you are back in Normal mode and can edit the buffer normally. Before doing so, however, notice that all the highlighted results are still highlighted. You can easily jump to the next result using the n (for next) key. This command accepts a count, so you can use 3n to jump to the third result after the current cursor position.

The search will wrap to the top of the document if there are no more matching results at the bottom. If you know how far you need to jump, you can use a count with the / command as well, as in 3/something to jump forward to the third something. Figuring out how far you need to jump requires some mental agility, though, so it’s usually faster to use Seek mode.

If you n too far, you can use Shift-N to move the cursor to the previous result instead. And if you know you need to search backwards to a previous result, you can initiate the search with ? (i.e. Shift-/) instead of just /.

If you have used Vim before, I should warn you that this behaviour of n and N is different from the default Neovim behaviour. They used to “repeat the last / or ? command,” so n would continue up the document if you started with ?. The LazyVim model is easier to remember; n always means “next down” and N always means “previous up”.

12.2. Ignore Case

If you enter your search term as all lowercase letters, LazyVim will ignore case by default, but if you include a capital letter in your search term, it will enable case sensitivity. So searching for in will match in and In, but searching for In will only match In.

If you expressly want to search for only lowercase matches, you can modify the search term by inserting the two characters \C (that C is capitalized) somewhere in it.

Conveniently, it doesn’t have to be at the beginning of the search term; if there is a \C anywhere in the search string, it will make the whole search case sensitive. So, imagine you were looking for the lowercase word “initiate”. If you start typing in and realize it’s matching a bunch of unnecessary In because ignore case is enabled, you can append \C (so you end up with in\C) to switch to ignore case mode before typing iti (so the total search string is in\Citi).

If you want to disable ignore case temporarily, type the colon command :set noignorecase. This will only last until you exit Neovim, or explicitly enable it again with :set ignorecase.

If you want to make the change permanent, open your options.lua file and add vim.opt.ignorecase = false somewhere in it. Note that now if you want to make any specific search case insensitive, you need to use lowercase \c instead of \C in the search phrase.

The \C trick seems kind of weird at first, but when you think about the alternative used in most code editors, where you have to move your hand to your mouse, target a tiny checkbox with a label like wW, and click it, then refocus the search box and continue typing, you’ll probably decide that \C is faster.

12.3. Regular Expressions

Vim searches use regular expressions by default. But they are kind of strange regular expressions.

Ok, I admit that all regular expressions are kind of strange. Vim’s are only strange in comparison to the PCRE-style regular expressions that are common in most modern programming languages. Luckily, if you are searching text, you probably don’t need the full complexity the Perl-compatible expressions offer.

I don’t have space in this book to instruct in regular expression syntax, so I’ll just mention some of the main go-tos and leave you to look up the rest:

  • . matches any single character. If you need to search for a literal period, escape it with \..

  • \S matches any non-whitespace character.

  • The * character matches the preceding expression zero or more times. Notably, .* will match any string of characters of arbitrary length.

  • The \+ string will match the preceding expression one or more times. (This is notably different from most regular expression parsers I’ve seen, where you don’t need the \ before the + to match one or more). It can be combined with e.g. \S to match any word without spaces: \S\+.

  • \= can be used to match the preceding pattern zero or one times. Useful for things like https\=: where the “s” is optional. This pattern is usually ? in most regular expression engines, and in fact \? also works for this. However, it would confuse Vim when the command to invoke search backwards is ?, so \= wins.

  • \\ matches a literal backslash and \/ matches a literal forward slash.

In general, if you know PCRE-style regular expressions, you’ll find you need a lot more backslashes in Vim. That said, the vast majority of code editor searches are covered by the above.

If you want to “disable” regular expression matching for a specific search, place \V at the beginning of the line (or in the middle of the line if you only need to disable it for the remaining part of the search). The “V” stands for “very nomagic”, and if you want to be extremely confused, type :help magic. It is so confusing, in fact, that you will prefer to learn to just use regular expressions (yes, I am aware how very confusing that is. Vim’s interpretation of magic is worse).

If you desperately need a regular expression to do something you can ask ChatGPT skim through :help regular expressions to find the syntax you need. You will come away either enlightened or frustrated.

12.4. Search In Project

If you need to search for a word across your entire codebase, instead of just in one file, use the command <Space>/ instead of just /. It will pop up the ever-so-familiar picker, this time in live_grep mode.

Make sure you have ripgrep installed and available on your path as rg, as that is what the pickers use under the hood.

Type the string you are looking for. The results will show up in the left side and the file will display in a preview on the right so you can be sure you found the right one:

live grep dark
Figure 56. Live Grep Picker

Remember that you can add labels to Telescope results by pressing <Esc>s to enter Seek mode, or to Fzf.lua results using Control-x. I find this more useful in the live_grep window because unlike most pickers, a space in live_grep is sent as a literal space to ripgrep, instead of allowing me to narrow the search results by searching for something earlier in the line.

Since this is a picker, you can press ctrl-t while it is open to put all the search results into the Trouble window so you can navigate them while editing (using ]q and [q).

Annoyingly, this search mode is completely different from Vim’s built-in search. It just passes your pattern to ripgrep and behaves the way ripgrep does. And ripgrep doesn’t know about things like Vim’s strange regular expression engine. It does support regular expressions, but they use maddeningly different syntax from Vim. Which is to say, the same syntax as pretty much everything that isn’t Vim. It’s Vim that’s maddening here, not ripgrep. Just so we’re all clear.

Ripgrep itself accepts a multitude of command-line options, but by default, the live_grep feature doesn’t support passing arguments to ripgrep to tweak your query. The Telescope project does provide a telescope-live-grep-args extension that you can enable if you want to be a live_grep power user. Configuring this extension is covered in Chapter 19, as it is a rare, but excellent example of LazyVim’s abstractions getting in the way.

12.5. Summary

This chapter was all about search: the / shortcut to enter Search mode, and the <Space>/ shortcut to enter find in project mode. The latter is not as powerful as it could be, so we also set up the telescope-live-grep-args plugin to give us a little more control over the project-wide search.

Searching is, of course, only half the story. In the next chapter, we’ll cover replacing text, both as part of a search operation and at a more project-wide level.

Reminder

This book is freely available. It contains a textbook's worth of material. If you are deriving value from this content, I really appreciate any support you can give (both to me, and to those who can't afford to give it) by purchasing a copy of the book or through donation. Click here to see various purchase and donation options.

Copyright © 2024 Dusty Phillips