Chapter 4. Opening Files

In the previous chapter, as a side-effect of learning about Command mode, we saw how to open files the old-fashioned Vim way, using the :edit command. Another old-school alternative is to open them directly from the terminal shell command line, using nvim filename.

Both of these are occasionally handy, but LazyVim pre-configures more modern ways of navigating and opening files.

4.1. Introducing File Pickers

LazyVim ships with two different “picker” plugins: tools for selecting items from a list. We’ll look at both in this chapter with some advice on which to choose. They look a bit differently, but can largely be used interchangeably.

By default, LazyVim ships with the Telescope picker enabled. It provides a picker interface with preview and fuzzy search capabilities. If you’ve used the command menu in many modern editors (or even Github or Slack), you may know what I’m talking about. The picker itself doesn’t care what you are picking, and it is used for a wide variety of tasks built into LazyVim or as third-party plugins, including opening files, selecting open buffers, project-wide search, and more.

The most common picker task you will perform is to open a file using fuzzy search. I use this command dozens, maybe hundreds of times per day, so it’s a good thing it’s got a really accessible keybinding.

The file picker is best illustrated while working in a code repository with a lot of files. So close Neovim with Space q q and use the cd command in your terminal to change to the directory of a project you’ve been working on recently (If you don’t have one close to hand, clone your favourite open source project and use that instead). Then type nvim to open Neovim again.

I had you exit to the terminal above because it’s easy to reason about, but it is also possible to change directories from inside LazyVim using the :cd command. Type :cd the/path/to/the/directory and hit Enter, remembering that you can use the Tab key to autocomplete the path. Now if you use :e to open files, they will be relative to the directory you specified. If you are using a file picker, they may be relative to that working directory or to the project containing the current file, as discussed shortly. Use :pwd to see what the current directory is.

Ok, so you’re in the root directory of a large project and you want to open an arbitrary file. Simply press Space twice (i.e. Space Space) to pop up the “Files In Current Project” picker. As I mentioned, this is the easiest keybinding to type on your entire keyboard. The Space bar on most keyboards is big, and you’re hitting it with your strongest digit: the thumb. As usual, just one Space will pop up the Space mode menu, and you can see that a second Space will present you with “Find Files (root dir)”.

For the project containing the current state of this book, the picker looks like this:

telescope current project dark
Figure 16. Telesscope Picker

The picker is divided into three main areas: the results list in the upper left, a preview of the currently selected file on the right, and the Input area, in this case labelled “Git Files”.

The input area is actively focused and currently in Insert mode, so you can just start typing the name of whatever file you want to open. This is a “fuzzy search”, (a concept popularized by Sublime Text) which means you can skip letters, saving you oh-so-precious milliseconds. For example, if I type ch3, my list gets filtered down to the following files:

telescope ch 3 dark
Figure 17. Pick Chapter 3

Only files whose paths contain those three characters in order, with possibly other characters in between, are visible. The picker has helpfully highlighted those three letters in the results so you can easily see why it matched.

Also notice that by default, the match is case insensitive. I typed the lowercase letter c, but it matched the uppercase C in the filename. This is usually sufficient to narrow the search results to what you need. However, if you do use any capitalized letters in your search, it switches to a case sensitive mode (this is sometimes referred to as “smart case”).

That means that Ch will match all the Chapters, but cH will not match anything at all. More interesting, chF will also not match anything at all because the presence of the capitalized F makes the whole thing case sensitive, and the chapters are all named with a capital C, so the lowercase c is not able to match them.

Sometimes you will start typing a word and realize you need to match something earlier in the path to distinguish it. For example, I started typing outline in these source files from Fablehenge:

telescope outline dark
Figure 18. Outline in Picker

“Outline” is a common word in this app. There are 243 matching files, and I realize I should probably have typed comp in front to narrow it to just files in the component directory. I could switch to Normal mode and edit the beginning of the line, but it’s faster to just type <space>comp. The picker will interpret the space as “filter the lines again, fuzzy matching this new word from the beginning”. Here we can see that only comp…​outline files have been matched:

telescope outline comp dark
Figure 19. Narrow picker to “comp”

This image might be surprising; the most promising match is obviously the selected one at the bottom of the list. The other 27 matching lines contain all the letters of the word “outline” and all the letters of the word “comp” in order from left to right. However, because of the fuzzy matching algorithm, the two can actually overlap! So on e.g. the second-to-last entry, the c of the matching “comp” is before the word outline, the o is in it, and the m and p both come after the word outline. The picker doesn’t care, though it will rank matches with the matching letters closer together as more important, so they’ll be visible at the bottom of the results.

You can use the up and down arrow keys to select a different file in the search results, and its preview will show up in the right-hand window. Once you find the file you want to open, press the Enter key to open it in the currently active Neovim window.

With Telescope, the input area even has its own Normal mode! You can get into it using a single press of the Escape key. Now if you press j or k, you’ll be able to select different files in the list without moving your hand to the arrow keys. Further, the h and l keys will allow you to move the cursor within the input box and you can use the i or a keys to enter Insert mode at the new location. The “but bigger” I and A keys allow you to move the cursor to the beginning or end of the line and enter Insert mode as well.

You can even use Seek mode, as we discussed in Chapter 3, though it works a bit differently in the picker. When you press the s key while in the Telescope picker’s Normal mode, you can skip the part where you enter a character to search for. Instead, LazyVim will immediately label every line in the picker with a character to the left of the filename:

telescope seek dark
Figure 20. Telescope Seek Labels

These characters are labels for each line in the picker. Simply press one of the shown letters on your keyboard, and whichever line the label associated with that letter is on will be selected. Then press Enter to actually open the file (or, if it is not a file picker, perform the default action for that picker).

Finally, if you are in the Telescope window and decide you don’t want to open any files after all (or you got the information you needed from looking at the preview), press Escape twice. Once to enter Normal mode in the Telescope picker, and a second time to close the picker.

If you need to scroll the preview window to see something lower down in the file, the same Control-d, Control-u, Control-f, and Control-b keys that we discussed in the Basic Navigation chapter can be used.

4.2. The Difference Between “Root” and “Cwd”

The <Space><Space> command is mapped to “Find Files (Root Directory)”. Two other ways to open the file picker are to use <Space>f to open the “file/find” menu, and follow it with either f again or F.

<Space>ff is the same as <Space><Space>. It opens “Find Files (Root Directory)” and is just another longer way to get there. I assume it exists in both places so that users can choose to map some other action to <Space><Space> and still be able to access the picker functionality through <Space>ff.

<Space>fF, where the second F is shifted, is similar; it is mapped to an action called “Find Files (cwd)”. If you run it in your project, you’ll probably find that it appears to do the exact same thing as “Find Files (Root Directory)” (depending on how your project is set up), so the purpose of two separate keybindings may be confusing.

4.2.1. Current Working Directory

“Cwd” stands for “Current Working Directory”, and by default, it refers to whatever directory your terminal was in when you typed nvim to open the editor. You can change the cwd for the entire editor by entering Command mode with : and then typing cd path/to/directory (remember, all commands are followed by a carriage return, so press Enter or Return afterwards). Now if you use <Space>fF, the list of files will be shown relative to the new directory you have changed into.

If you are unsure what directory you are in, you can use the :pwd (short for “print working directory”) command to have it pop up in a little notification window. cd and pwd are the same commands used by bash, zsh, and many other shells for changing and printing the working directory, so they may already be familiar to you.

We haven’t discussed splitting your editor or opening new tabs yet, but for future reference: It is actually possible to have different working directories for different windows. The command to change just the current window’s directory is :lcd, short for “local change directory”. This can be a powerful way to work on multiple projects at the same time (for example, if you are a full stack developer working on backend and frontend projects). However, the LazyVim concept of a “Root” directory can semi-automate this.

4.2.2. Root Directory

The root directory is not a Vim concept, but is instead a Language Server Protocol (LSP) concept. LSPs are the reason that VS Code became so popular so quickly; the idea was that the editor could call out to an external service running on your computer to find out useful things about the codebase. The LSP powers a lot of useful stuff such as go to definition and references, highlighting errors in your code, and showing documentation for a variable or class. It can even help with formatting and syntax highlighting.

The root directory is the directory that the LSP infers is the “home” directory of the currently open file. How the LSP does this is language (and language server) dependent. For example, in Javascript or Typescript projects it probably searches parent directories for the presence of a package.json or tsconfig.json file to detect the root directory. whereas in a Python project it might instead look for things like pyproject.toml or poetry.lock. and Rust projects use the directory that contains a Cargo.toml. Alternatively, some LSPs might just use the presence of a .git folder as the “root” of the project’s workspace.

The only reason this root directory is “often the same as your cwd” is that this is usually the folder you want to work from when you are working on a project, so it’s the one you cd into before you open Neovim.

This automatic root directory thing can be super useful if you are working on multiple projects. Instead of using lcd as discussed in the previous section, you can just open a file in a different project using :e or one of the file finding extensions we’ll discuss next. Then if you invoke the “Find files (root dir)” command using <Space><Space> or <Space>ff, it will look for other files in the same root directory as the one you just opened.

However, it can sometimes be confusing, especially if you are working in a monorepo or if you have root directories in places you don’t expect. For example, I have a fairly normal Svelte project that has a package.json file in it. This project uses Cypress for testing, and the Cypress folder contains a tsconfig.json file that causes the Typescript language server to interpret that as a separate root. So if I am working on one of the cypress test files and press <Space><Space>, the root directory is considered the Cypress folder and I can only open other Cypress tests. But often the thing I wanted to do was open a source file in the main folder to see why a test is failing. In this case, I have to press <Escape><Escape> to exit the Telescope picker, then <Space>fF to open the picker in current working directory mode instead.

4.3. Fzf.lua

LazyVim recently shipped a new opt-in picker experience as an alternative to Telescope. I am convinced this was done just to make my life as an author harder! They are quite similar, but different enough that they need to be documented separately.

The main advantage of Fzf.lua is said to be that it provides a “familiar” interface to people who are used to using the fzf tool on the command line. Fzf is a handy CLI tool for changing directories or opening files in a deeply nested filesystem by only typing a handful of characters using fuzzy matching. I’ve used it for years and highly recommend it. That said, I’ve never really cared whether my editor had the exact same experience as the CLI tool, so I don’t consider it a compelling argument.

The other reason Fzf.lua is suggested is that it is considered to be more performant than Telescope. I haven’t really noticed a difference on my machine but if you find Telescope is laggy, you might want to try out Fzf.lua.

There are a couple downsides to Fzf.lua, though. The main one is that it secretly runs in a Neovim terminal window, so Normal mode behaves really weird. Notably, pressing escape once closes the picker instead of entering Normal mode in the input area like Telescope does.

The second downside is that Fzf.lua doesn’t have the robust plugin ecosystem that Telescope does. In practice, there’s only one plugin that I miss when using Fzf.lua instead of Telescope, but if you become a Telescope power user, you might notice a lack of options with Fzf.lua.

If you want to try Fzf.lua, you need to learn about Lazy Extras. I go into more detail about Lazy Extras in the next chapter, but the short story is they are optional plugin configurations that you can enable with a single keypress. To enable Fzf.lua, do the following:

  • Type :LazyExtras<Enter>

  • Move your cursor to the line that contains editor.fzf

  • Press x to install the eXtra

  • Wait a moment for the related plugins to install

  • Restart Neovim

This will disable Telescope and enable Fzf.lua. You’ll see a slightly different picker layout when you open it:

fzf dark
Figure 21. Fzf picker

The main difference is that the input area is at the top of the left side in Fzf.lua instead of the bottom as it is with Telescope.

The picker behaves similarly to Telescope, so I’ll only highlight the differences here. The biggest one, as I already mentioned, is the weird Terminal-based Normal mode. If you are in the input area and want to enter Normal mode, you have to type the bizarre set of keybindings Control-\ followed by Control-n. This Normal mode does allow you to navigate around the window and most keybindings will behave the way you expect Normal mode to behave.

The most notable exception is that Seek mode doesn’t behave like it does in Telescope. If you want to get the behaviour of jumping to a line by a label using Fzf.lua, use the unmemorable Control-x keybinding instead.

Fzf.lua has several other keybindings configured by default; you can see a helpful menu of them by hitting F1. The only ones I use regularly are the annoying Emacs-style Control-a and Control-e to jump to the beginning or end of the input area, and Control-d and Control-u to scroll the results window.

Throughout the rest of this book, I will generally assume you are using Telescope rather than Fzf.lua, though if there are glaring differences between the two, I cover them. For now, let’s move on to a completely different file selecting experience.

4.4. The Neo-tree.nvim Plugin

Neo-tree creates a left-sidebar file explorer experience that should be familiar to users of many modern IDEs and editors. While, like many of those environments, Neo-tree works with the mouse, it is optimized for keyboard interactions, making it faster to work with once you learn “Neo-tree mode”.

I want to be upfront and honest here: I don’t personally use Neo-tree. I find that the file pickers we just discussed are the fastest way to open files, and when I need to manipulate the filesystem, I prefer to use mini.files, which we will discuss later in this chapter. The primary reason I prefer mini.files is that it uses the same keybindings as Vim Normal mode. Modes are great, but having more of them than necessary is not!

However, I suspect that many readers will prefer the familiar tree view experience Neo-tree provides, and since this plugin ships with LazyVim by default, I want to make sure it gets fair coverage in this book.

Let’s start by opening Neo-tree using the <Space>-e keybinding, where the mnemonic is “e for Explore”. If you pop up the Space mode menu, you’ll see that, as with the fuzzy picker, there are two ways to open the Neo-tree explorer: <Space>-e for Explore Neo-tree (root directory) and <Space>-E for Explore Neo-tree (cwd).

“Root directory” and “cwd” have the same meanings we discussed in the previous section, and you will notice the consistent relationship between lowercase and uppercase letters: <Space>ff and <Space>e both open the root directory, and <Space>fF and <Space>E both open the current working directory.

To hide the Neo-tree explorer window, just press <Space>e again while it is visible, or press q while it is focused.

When the explorer is opened, it shows all the files and folders in the relevant directory, with all the folders collapsed, except for the one containing the currently active file, if there is one. For example, while editing this file, my Neo-tree looks as follows:

neo tree dark
Figure 22. Neo-Tree

The cursor is on the file I’m currently editing. I can move that cursor up and down using the ubiquitous j and k keys.

Folders are collected to the top of the view. If you move the cursor to one of these folders, you can press the Enter key to see the files inside the folder. And if you move it to a file, you can open the file in the current Vim window with Enter as well.

You can also expand and collapse folders and open files by double clicking with the mouse, but my guess is you won’t want to do that once you learn proper keyboard navigation.

Speaking of keyboard navigation, yes, j and k to move up and down can be super slow if there are a lot of files to navigate. All of the commands that we discussed in Chapter 3 can be used to move faster. For example, 10j will move the cursor 10 lines down with just three keystrokes compared to pressing j 10 times, and Control-d or Control-u can be used to scroll the tree down or up. Most interestingly, s can be used to Seek to any line in the Neo-tree view, even if Neo-tree is not currently focused.

Neo-tree will show either the root or cwd as the topmost directory. If you need to navigate “up” the tree to a higher-level directory, you will need to use the Backspace key.

Backspace is often coded as <BS> in Vim, so if you see a keybinding or instructions telling you that <BS> does something, they aren’t full of (bull)! It just means Backspace.

In addition to navigating and opening files, you can even make changes to the file system using Neo-tree. For example, to delete a file, you can move the cursor over that file and hit the d key. You’ll be prompted with a popup window asking if you are sure. Hit y and then Enter to confirm it:

neo tree delete dark
Figure 23. Delete Confirmation in Neo-tree

To add a file or folder/directory, use the a key and enter a new name. Use a trailing slash (/) to indicate a folder. You can also use the A key from the explorer to add a folder without having to type a trailing slash.

The r key can be used to rename the file or folder under the cursor.

To copy or move a file, you can use Neo-tree’s pseudo-clipboard. I say “pseudo-” because you can’t use this to copy a file to be pasted in e.g. MacOS Finder or Windows Explorer; only to other places in the Neo-tree.

To cut a file with the intent of moving it somewhere else in the tree, use the x command. If, instead, you want to copy the file, use y. The mnemonic for y is yank, and is actually the same key you would use to copy text in the normal editor. To complete the move or copy, you’ll need to navigate to the destination folder and use the p key (which you may recall means “put” or “paste”).

Neo-tree also has a “Filter” mode that I find quite clumsy; it’s really just a cheap imitation Telescope/Fzf.lua picker in a smaller window, so I recommend using your chosen picker instead. If you want to use Neo-tree’s Filter mode, you can access it using / and enter some characters to limit the search results to files that match those characters. Then use the up and down arrows to navigate the list (j and k won’t work here because you’re in a sort of Insert mode context).

There is a ton of other cool stuff that Neo-tree can do. We will cover its use for buffer, git, and symbol navigation later, for example. In the meantime, you can use the ? (mnemonic “ask question for help”) key while the Neo-tree window is focused to get an overview, or :help neo-tree if you want to drink from the firehose.

4.5. The Mini.files Alternative

As I mentioned, I don’t actually use Neo-tree for file navigation. I find that it feels kind of “foreign and un-vim-like”. To me, it is a completely separate experience that just happens to be embedded in a Neovim window. That said, I also don’t like the tree view sidebar experience in VS Code and the editors it emulates / is emulated by, so it’s possible that tree views just aren’t right for me.

These are just my opinions, and one of the golden rules of text editors is “all opinions are valid” (otherwise there would be war). A large number of Neovim users love Neo-tree, and you should use it if it matches your mental model.

That said, I’m clearly not alone in these opinions, because LazyVim optionally provides a different file management experience with a plugin called mini.files. It is disabled by default.

Mini.files is part of a suite of fairly random Neovim packages known as mini.nvim. These plugins are independent from each other and provide a lot of common features that in many cases ought to ship with Neovim. Occasionally, the mini.nvim plugins are inferior to other plugins that they clone, but many are best in class. Mini.files is not the only mini.nvim plugin that ships with LazyVim, and we’ll touch on others later.

The mini.files file manager is kind of like a Neovim-native experience of the columnar view that is popular in MacOs finder, among other file managers. The main reason I like it is that editing the directory listing is just like editing a normal text buffer. I don’t have to remember that a means “after” in Normal mode, but it means “add file/folder” in Explorer mode. Instead, in mini.files, I use the o key to “create a new line below the current line”, and then enter a new file name in Neovim Insert mode. Later, I tell mini.files to sync my changes and it will create the file for the new row.

In order to use mini.files, you have to enable it as a Lazy Extra, similar to how we enabled the FZF picker. The exact steps are:

  • Type :LazyExtras<Enter>

  • Move your cursor to the line that contains mini.files (Seek mode is fastest)

  • Press x to install the eXtra

  • Wait a moment for the plugins to install

  • Restart Neovim

4.5.1. Using Mini.files

Once installed, you can show the mini.files view using <Space>fm and <Space>fM. By default, these are not quite the same as the cwd/root structure we’ve seen in Telescope, Fzf.lua, and Neo-tree. Instead, they are listed in the <Space>f menu as follows:

Listing 11. Mini.files keybindings
m -> Open mini.files (Directory of Current File)
M -> Open mini.files (cwd)

The default mini.files configuration doesn’t have an open in root option. I like having the ability to open the directory of the currently open file, but I don’t like losing the ability to open the root of the current project. I show how to address this in Chapter 5.

Instead of a sidebar, the mini.files menu shows up as columns of windows (known as Miller columns) side-by-side. For example, here’s what happens when I open mini.files to the current working directory of this book:

mini files dark
Figure 24. Mini.files

The left-hand panel shows the current working directory, and the middle column shows the contents of the book directory, where my cursor is currently on chapter 4. The right column shows the preview of that chapter 4 directory, which contains only one file.

Interacting with mini.files is very similar to interacting with a standard vim window. You can use the j and k keys to move the cursor up and down. If this places your cursor over a folder, the contents of that folder will immediately show up to the right, and if it is over a file, you will see a preview of the file.

If you want to move “into” a folder to interact with the contents of that folder instead, simply press the l key to move “right”.

Similarly, pressing h will move “out” of the current folder. If the cursor is in the left-most column, moving left will open a new left-most column, so you can navigate right up to the root of your file-system if you need to.

To open a file in the currently active Neovim window, press l on that file again. The behaviour here may be a bit surprising; the file will open under the mini.files view, but it won’t hide the file menu. This allows you to open multiple files before closing the navigator, which can be done with the q key.

The beautiful thing about mini.files compared to Neo-tree is that the little windows act like normal editors, and all the navigation features you have become used to are available. For example Seek mode can be used to navigate to a file. Press the s key and then any number of characters you want to search for. Any matches to the typed characters will be labelled and you can jump to them by typing the indicated label.

Even modifying the filesystem is exactly the same as editing a normal buffer. We haven’t really covered editing yet (I’m just as surprised as you are), but here’s a quick overview:

  • To rename a file or folder, navigate to the line that has it, and enter Insert mode to change or add text.

  • Deleting a file or folder uses the command dd which is the keybinding to delete an entire line of text in normal Neovim windows.

  • Copy a file or folder with yy, the command to copy (“yank”) a line of text.

  • Put/paste a deleted or yanked file with p.

We’ll discuss these commands and more in Chapter 6. The main point is that pretty much any navigation or editing command you learn in the future will work with mini.files.

Saving Filesystem Changes

Any modification that you make using these keybindings will not actually be saved on the filesystem until you type the = key, which is a (rare) mini.files specific keybinding. I think of it as meaning “make the filesystem equal to what I’ve typed”. This will pop up a little window telling you what actions mini.files wants to take on your behalf, such as deleting, moving, renaming, or copying files. You can confirm or decline the changes with a y or n (yes or no, of course).

I encourage you to play with both Neo-tree and mini.files until you can make a decision as to which of the two you prefer. Eventually, you will arrive at one of the following conclusions:

  • You prefer Neo-tree and don’t need mini.files. In this case, revisit the LazyExtras mode and disable mini.files with the x key.

  • You use Neo-tree for some interactions (possibly things we haven’t covered yet, such as navigating git, buffers, or symbols) and mini.files for others. In this case, you are probably content with the default LazyVim configuration of the mini.files extra.

  • You are my kind of weird and don’t want to use Neo-tree at all, preferring only mini.files. Disabling plugins is discussed in the next chapter.

4.6. Summary

In this chapter, we learned not one, but four different ways to open files and interact with the filesystem in LazyVim: Telescope, Fzf.lua, Neo-tree, and mini.files. Each provides a different mechanism for opening and managing files, and you will find some of them more comfortable than others.

As a side-effect of studying these filesystem tools, we got a tiny preview of configuring plugins and installing LazyVim extras. We will go into more detail on this in the next chapter.

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