1. Introduction
When using editors to read or modify files, we might encounter a situation in which a file changes outside our current session. In these cases, the editor should optimally update the file as soon as it’s modified.
In this tutorial, we explore automatic commands and check out how the Vi editor handles outside changes to the currently opened file. First, we talk about background refreshing in the terminal and shell. Next, we see how the same works in the editor. After that, we delve into the standard way to create Vi automatic commands. Finally, we discuss and improve the default file reload mechanism of Vi.
For brevity, we use vi (Vi) when referencing both the Vi and Vim editors.
We tested the code in this tutorial on Debian 11 (Bullseye) with GNU Bash 5.1.4. It should work in most POSIX-compliant environments.
2. Terminal and Shell Refreshing
For the most part, a Linux terminal and shell, i.e., the command-line interface (CLI), is a single reactive thread. Basically, this means code usually runs only as a consequence of user actions or scripts unless the jobs mechanism or similar is involved.
Still, we can have a terminal user interface (TUI), often employed by terminal emulators like screen and tmux. In these environments, many threads commonly run simultaneously for different tasks:
- pane control
- shortcut processing
- status refresh
- title refresh
- background command execution
For example, we can display an active clock with the current time on the tmux pane in focus by using Ctrl+B, t:
██████ ██████ ██████ ██████
█ █ █ █ █ █
█ █ ██████ ██████ ██████
█ █ █ █ █ █ █ █
██████ ██████ ██████ ██████
The clock will refresh without any user interaction. Similar possibilities exist at lower levels as well but would require simulation in a raw single-threaded terminal.
3. Vi Editor Refresh
After partially understanding the refresh mechanism of terminals and shells, let’s move on to vi and its options for refreshing.
By default, Vi also has a single thread, which runs all operations in order.
3.1. Background Commands
Indeed, we can still run shell commands in the background using vi. Parallel execution there is achieved in several ways:
- system job control
- exec* family of functions
- integrated job_start() mechanics
In essence, this means that the shell takes care of the additional commands in the background. Plugins like asynctasks.vim and tasks.vim are available to leverage this.
3.2. Screen Refresh
Usually, Vi only changes the buffer and screen contents after manual edits and commands.
For instance, we can manually enter Insert mode and add characters or make a background command send its output back to the buffer:
:r!echo 'Gets inserted at the current buffer position.'
However, in addition, Vi provides events, which can trigger actions.
4. Vi autocmd
Usually, any automatic update and refresh mechanisms, as well as other automated actions, rely on triggers and events. Vi is no exception with its :autocmd (automatic command) statement, which links an event or trigger to an action.
The basic syntax includes the trigger event’s name (event), followed by a modifier pattern (aupat) and any actions as commands (cmd):
:au[tocmd] [group] {event} {aupat} {cmd}
In addition, we can specify a group for more granular execution control.
4.1. Events
The first mandatory part of an automatic command is the event that triggers it.
Indeed, we can use many read and write triggers like BufNewFile, BufRead, and BufWrite. Other triggers include:
- buffer operations
- options changes like file type and syntax
- startup and exit events
- many others
For instance, we can make Vi set the cindent option when working with *.c files:
:autocmd BufRead *.c set cindent
Notably, since the event is a buffer read, opening a non-existent .c file wouldn’t trigger it. Here, the simple *.c pattern matches all files with the .c extension. Let’s see other examples.
4.2. Patterns
The aupat pattern can be a comma-separated list, which tests for the file or buffer-local (<buffer*>) matches. Moreover, the former can check the filename alone, but also the full path with symbolic links resolved if a / slash is present.
Also, the usual file patterns apply, but an * asterisk also matches path separators.
For buffer-local matches, <buffer**> designates the current buffer, while <buffer=N> refers to buffer N.
4.3. Automatic Command Entries
In addition to defining them, we can also perform other operations with automatic commands:
- :au[tocmd] without a cmd argument lists commands that match the provided definition
- :au[tocmd]! removes an entry based on the group, event, pattern, command, and other attributes
For example, we can simply list BufEnter commands:
:autocmd BufEnter
--- Auto-Commands ---
filetypedetect BufEnter
*.xpm if getline(1) =~ "XPM2" | setf xpm2 | else |
setf xpm | endif
[...]
Here, we can also use buffer-local patterns.
4.4. Groups
After defining a set of autocmd commands with a given group, we can use that group’s name to identify the set. By default, commands go into a standard nameless group.
Further, the :aug[roup] command can select the current group to add or remove entries:
:augroup acgroup
: autocmd! BufEnter
: autocmd BufEnter *.c syntax off
:augroup END
In this case, we select acgroup as the one to modify. Next, we remove any commands that trigger on a BufEnter event and create a new one to turn off syntax highlighting for .c files. Finally, :augroup END restores the standard nameless group as the default for future operations.
5. File Reload
While the current buffer changes as we type or otherwise use Vi, the actual file contents can get modified in the background without refreshing inside the editor.
After exploring the features above, we’re now equipped to deal with this issue.
5.1. edit Command
In simple cases, we can just use the :e[dit] command to re-edit or reload the current buffer:
:edit
Yet, there are several drawbacks:
- works only on the current buffer
- current changes to the buffer without autowriteall being set prevent reloads
- requires manual execution
Because of these, we turn to another potential solution.
5.2. checktime Command
Conveniently, Vi stores the timestamp of the last modification.
Furthermore, the :checkt[ime] command uses this timestamp to check whether any open buffer was changed outside of the editor:
:checktime
W12: Warning: File "/file" has changed and the buffer was changed in Vim as well
See ":help W12" for more info.
[O]K, (L)oad File:
Here, we see the warning when there are external changes with the option to [O]K the current contents in Vi or (L)oad the new modifications.
While this solution is manual as well, it works for all buffers and is more robust than :edit. Of course, we can also automate it:
:autocmd VimResume * checktime
In this case, on restoring Vi to the foreground, :checktime runs directly. In fact, many versions of Vi do this by default.
5.3. autoread Option
The autoread option is the standard way to automatically reread files after outside edits:
:set autoread
Still, even autoread doesn’t run without being triggered by actions like running shell commands:
:!:
[...]
W12: Warning: File "/file" has changed and the buffer was changed in Vim as well
See ":help W12" for more info.
[O]K, (L)oad File:
This isn’t optimal, but we can build on top of it, combining it with our knowledge of autocmd.
6. autoread Improved
Let’s implement a couple of automatic commands, which make Vi more sensitive to external changes to currently-edited files:
autocmd FocusGained,BufEnter,CursorHold,CursorHoldI * if mode() !~ '(c|r.?|!|t)' && getcmdwintype() == '' | checktime | endif
autocmd FileChangedShellPost * echohl WarningMsg | echo "Buffer refreshed with external changes." | echohl None
Essentially, the first autocmd contains the main triggers:
- FocusGained, got input focus
- BufEnter, entered a buffer
- CursorHold, user doesn’t press a key for a while
- CursorHoldI, like CursorHold, but in Insert mode
The exact time to wait on the last two events can be configured with the updatetime variable.
For each event above, in every case (*), we have two conditions:
- the current mode() shouldn’t match command-line editing (c), any Replace mode (r*), shell or external command execution (!), or Terminal-Job mode (t)
- getcmdwintype() should return an empty string (due to mode restrictions)
Once triggered by the events specified, if the conditions are met, the first autocmd runs :checktime.
The second autocmd simply issues a warning message with some highlighting on each FileChangedShellPost external update.
Finally, by adding the commands above to our ~/.vimrc or /etc/vim/vimrc files, we can configure the settings permanently.
7. Summary
In this article, we looked at background refreshing and how to automate commands in Vi. By combining these concepts, we improved the usual file reload mechanism of the editor.