Buffers
Windows allow you to view and manage multiple buffers at once.
For example, you may be copying code from file A and pasting it into file B line-by-one with slight modifications. It would be fitting to have two windows open side-by-side for this task.
Difference between Windows and Buffers
-
A buffer is the in-memory text of a file.
For example you can open 10 files with:
Terminal window # expands to: hx file1 file2 ... file10hx file{1..10}There will be 10 buffers stored in memory, but only 1 opened window.
-
A window is a viewport on a buffer.
If you open those 10 files again with
--vsplit
(probably a bad idea!):Terminal window hx --vsplit file{1..10}It’ll store 10 buffers in memory, as well as open 10 windows.
Opening Multiple Files
Let’s start from the command line, where we used to open files using hx <filename>
.
This time, let’s open several files!
hx file1 file2 file3
If you run the above command, you’ll just see an empty screen:
1 ~ NOR file1 1 sel 1:1 Loaded 3 files.
But notice the Loaded 3 files.
message at the bottom left? We’ve got all of those files open.
Let’s leave a message in our first buffer:
1 this is buffer #1 ~ NOR file1 [+] 1 sel 1:18
Navigating Buffers
To go to the next buffer, use the command gn for goto next.
1 ~ NOR file2 1 sel 1:1
Leave another message here, and then let’s do the same on the third buffer:
1 this is buffer #3 ~ NOR file3 [+] 1 sel 1:18
You can go to the previous buffers by using gp. So try to cycle between them with the commands we learned now:
- gn to go to the next buffer
- gp to go to the previous buffer
Bufferline
So this is useful, we can have multiple files open. But it’s hard to really get a feel for where exactly we are in terms of buffers.
Enable the bufferline by using :set bufferline always
. This adds a bar at the top showing all of our three files:
file1[+] file2[+] file3[+] 1 this is buffer #3 ~ NOR file3 [+] 1 sel 1:18
With the bufferline, we can tell right off the bat which buffer is the currently active one.
Opening buffers with a glob
Let’s save all files and quit. We could use :wqa
to write quit all but we’ll use :xa
which is exactly the same but 1 character shorter!
Go ahead and open those files again, this time we won’t manually type out their names but rather use a glob ↗.
We want a glob which expands to file1 file2 file3
, what kind of pattern can we notice with those files? Well, they all begin with the string file
.
So we could do something like this:
hx file*
This will match every file in the current directory which begins with the string file
and then has whatever at the end.
The “whatever” is exactly what the asterk — *
represents. *
is a wildcard that matches literally anything, so it will match file1
, file2
and file3
(as well as anything else that begins with file
).
When we execute that command, helix will open the same 3 buffers. We’ll use :set bufferline always
again:
file1 file2 file3 1 this is buffer #1 ~ NOR file1 1 sel 1:7
Opening buffers with more complex filtering logic
As you’ve just seen, hx
command + globs is powerful. But this is only the beginning!
This command will open every single file with a .rs
extension in the current directory, recursively. So it will even open super nested files like ./some/directory/some/where/very/nested/main.rs
:
hx **/*.rs
Or as another example, we want to open a buffer in Helix for every file which contains the string export default async function
, using the GNU utilities find
↗ and grep
↗ we can do just that:
hx $(find . -type f -exec grep -l \ "export default async function" {} +)
Let’s dissect the command:
find .
: Search every file and directory in the current directory and any sub-directories.-type f
: Match only files, and not directories.-exec grep -l "export default async function" {}
: Runs the following command on each file: searches for the stringexport default async function
, outputting the names of files that it matches.+
groups the files.- The entire command
find . -type f -exec grep -l "export default async function" {} +
above will output the names of every file matched file as follows:
./file-one.js./nested/another-file.js./somewhere/deep/nested/file.ts
Placing it inside of $( ... )
uses shell variable expansion ↗ to effectively replace the entire command with the following identical output:
hx ./file-one.js \ ./nested/another-file.js \ ./somewhere/deep/nested/file.ts
Which then opens each of those files in Helix as a separate buffer.
This functionality comes in handy when you need to complete a certain task on every single file matching a pattern, but the task cannot be automated as each file requires manual inspection.
Opening buffers at specific line and column numbers
Finally, it’s useful to know that Helix can open files at specific line numbers, aswell as column numbers. For example this command opens the main.rs
file at line 122:
hx main.rs:122
The following will open on line 122, column 6:
hx main.rs:122:6
We can use the above two facts to make some truly powerful commands. I make a lot of spelling mistakes.
Instead of manually going through 40+ markdown files in order to fix these spelling mistakes, I run the following command which we’ll disect in a moment:
hx $(pnpm dlx cspell **/*.mdx | rg "^\S+")
That command will find every spelling mistake by using the cspell
tool.
The cspell
which can be used as follows to spellcheck against every .mdx
file (these files which contain this site’s content):
pnpm dlx cspell **/*.mdx
It outputs information in the following format:
usage/text-objects.mdx:43:53 - Unknown word (nside)usage/text-objects.mdx:43:62 - Unknown word (textobject)
Since Helix can work with the first part of the text before the whitespace, we want to filter out everything until the first space.
We can do that by matching “a sequence of non-whitespace characters at the beginning”. In terms of regex, it would be the same as this ^\S+
.
^
means “at the beginning”\S
means “a non-whitespace character”+
means “match at least one occurrence of”
So this regex will extract the following from the above example:
usage/text-objects.mdx:43:53usage/text-objects.mdx:43:62
To apply the regex to the output of cspell
tool, we use ripgrep
↗ which is a modern alternative to grep
, so the following command:
pnpm dlx cspell **/*.mdx | rg -o "^\S+"
Will produce an output like this:
basics.mdx:6:22 basics.mdx:82:56 basics.mdx:236:51 basics.mdx:297:72 basics.mdx:305:109 basics.mdx:309:104 installation.mdx:6:40
To use that in helix, we simply enclose the entire command in $( ... )
to make use of shell expansion:
hx $(pnpm dlx cspell **/*.mdx | rg -o "^\S+")
And it will open every single spelling error as a buffer, we’ll be able to traverse them with gn and gp. Extremely powerful.
Viewing multiple buffers at once
So far we explored having multiple files open in background, but we’ve only really seen a single buffer at the same time. We had to press commands like gp and gn to switch between them.
It’s possible to open multiple of them at once. For instance, if we have our file1 file2 file3
opened:
file1 file2 file3 1 this is buffer #1 ~ NOR file1 1 sel 1:7
We can open a “split” by using Ctrl + w + s. Let’s have three opened:
file1 file2 file3 1 this is buffer #1 ~ file1 1 sel 1:1 1 this is buffer #1 ~ file1 1 sel 1:1 1 this is buffer #1 ~ NOR file1 1 sel 1:1
Our cursor is at the bottom split. To navigate to the upper split, we use motions which are similar to h, j, k and l:
- Ctrl + w + h moves to buffer above
- Ctrl + w + j moves to buffer below
- Ctrl + w + k moves to buffer on the right
- Ctrl + w + l moves to buffer on the left
Each of these buffers has a dedicated statusline. The statusline of the currently active buffer will have its mode shown. For example, we are currently on the second buffer:
file1 file2 file3 1 this is buffer #1 ~ file1 1 sel 1:1 1 this is buffer #1 ~ NOR file1 1 sel 1:1 1 this is buffer #1 ~ file1 1 sel 1:5
Inside of these buffers, we can use them just like normal. So we can switch between the different buffers with gn to go to the next one, and gp to go to the previous one.
Let’s make all of the buffers represent different files:
file1 file2 file3 1 this is buffer #1 ~ NOR file1 1 sel 1:1 1 this is buffer #3 ~ file3 1 sel 1:1 1 this is buffer #2 ~ file2 1 sel 1:1
Close them one-by-one by using Ctrl + w + q.
Opening splits from the command line
The --vsplit
and --hsplit
flags can be used with Helix to open all of the files as either vertical or horizontal splits.
Let’s quit completely with :xa
yet again, and run the following command:
hx --vsplit file1 file2
That command opened two files, but this time in a vertical split instead of horizontal split.
1 this is buffer #1 │ 1 this is buffer #2 ~ │ ~ │ file1 1 sel 1:1 │ NOR file2 1 sel 1:1 Loaded 2 files.
Let’s close the buffer #2 with Ctrl + w + q to explore our next command.
ga lets us goto the last accessed file. That is, consider this chain of actions:
- Open file
main.rs
- Open file
lib.rs
, this is our current buffer. - Pressing ga will open the buffer corresponding to the
main.rs
- Pressing ga again will open the
lib.rs
buffer. - Pressing ga again will open the
main.rs
buffer. - Pressing ga again will open the
lib.rs
buffer. - Pressing ga again will open the
main.rs
buffer.
Next steps
Now that you know how to open multiple buffers and work with windows, let’s explore the various useful “pickers” available in Helix.
You may also be interested in the windows mode keymap reference, which includes a list of all the keymappings available to manipulate buffers.