-
-
Notifications
You must be signed in to change notification settings - Fork 2.7k
6. Tutorial: 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-line, making small edits. 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:
# expands to: hx file1 file2 ... file10 hx file{1..10}
There will be 10 buffers stored in memory, but only 1 open window.
-
A window is a viewport on a buffer.
If you open those 10 files again with
--vsplit
(probably a bad idea!):hx --vsplit file{1..10}
It'll store 10 buffers in memory, as well as open 10 windows.
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.
But notice the Loaded 3 files.
message at the bottom left? We've got all of those files open.
Leave a message in the first buffer:
this is buffer #1
To go to the next buffer, use the command gn for goto next.
Leave another message here.
this is buffer #2
And then let's do the same on the third buffer:
this is buffer #3
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
When enabled, the bufferline shows all buffers in a bar at the top of the screen.
Enable the bufferline with set bufferline always
or :set bufferline multiple
. Now we can see all three of our files, indicating the buffers that are currently open and which one is focused.
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 asterisk -- *
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.
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.
Finally, it's useful to know that Helix can open files at specific line numbers, as well 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. Instead of manually going through 40+ markdown files in order to fix these spelling mistakes, run the following command which we'll dissect 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" with this regex: ^\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:53
usage/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.
Ripgrep also includes a --vimgrep
flag, which will output each match beginning with the exact syntax that we want:
filename:line_number:column_number
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.
We can open a "split" by using Ctrl + w + s.
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.
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.
Make all of the buffers represent different files.
Now close them one-by-one by using Ctrl + w + q.
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.
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.