Skip to content

Instantly share code, notes, and snippets.

@yegappan
Last active April 11, 2023 00:03
Show Gist options
  • Save yegappan/3b50ec9ea86ad4511d3a213ee39f1ee0 to your computer and use it in GitHub Desktop.
Save yegappan/3b50ec9ea86ad4511d3a213ee39f1ee0 to your computer and use it in GitHub Desktop.
Updating a quickfix/location list asynchronously without interfering with another plugin
Vim has many plugins (vim-go, ale, etc.) that use the quickfix/location list
feature. Some of these plugins process the output of an external command and
update the quickfix list asynchronously as the output becomes available.
Updating a quickfix or location list asynchronously opens up the possibility
that two or more plugins may try to update the same quickfix list with
different output. Also when a plugin is updating a quickfix list in the
background, the user may issue a command that creates or updates a quickfix
list. The plugin may then incorrectly use this new list to add the entries.
The various Vim commands that create or modify a quickfix list (cfile,
cgetfile, caddfile, cbuffer, cgetbuffer, caddbuffer, cexpr, cgetexpr,
caddexpr, make, grep, grepadd, vimgrep and vimgrepadd) operate only on the
current quickfix list. A plugin using these commands to update the quickfix
list can interfere with another plugin.
To avoid these issues, the Vim functions getqflist() and setqflist() can be
used to operate on a specific list in the quickfix stack. Each list in the
quickfix stack has an unique identifier, which doesn't change within a Vim
session. A list in the stack can be specified using the quickfix identifier
when calling the getqflist() and setqflist() functions. So if a Vim plugin
uses the identifier to operate on a specific quickfix list then it can avoid
interfering with the operations of another plugin using another quickfix list.
The identifier of a quickfix list can be obtained using:
let qfid = getqflist({'id' : 0}).id
When adding new entries, the plugin can use setqflist() with this identifier:
call setqflist([], 'a', {'id' : qfid, 'items' : newitems})
To parse the output of a command and add the quickfix entries, the plugin can
use the following:
call setqflist([], 'a', {'id' : qfid, 'lines' : cmdoutput})
Note that in the previous command, the current 'errorformat' option setting is
used to parse the command output. This setting might have been changed either
by the user or by some other plugin to some other value. To parse the command
output using a specific 'errorformat' setting, the plugin can use:
call setqflist([], 'a', {'id' : qfid, 'lines' : cmdoutput, 'efm' : myefm})
If more than 10 quickfix lists are added to the quickfix stack, then the
oldest quickfix list is removed from the stack. When a plugin is using a quickfix
list, if another plugin or user adds a new quickfix list to the stack, then there
is a possibility that the quickfix list that is in use is removed from the stack.
So the plugin should check whether the quickfix list it is using is still valid
using the following command:
if getqflist({'id' : qfid}).id == qfid
" List is still valid
endif
In summary, a plugin can use the following steps to asynchronously process
a command output and update a quickfix list:
1. Create an empty quickfix list:
call setqflist([], ' ', {'title' : 'Output from command abc'})
2. Save the newly created quickfix list identifier:
let qfid = getqflist({'id' : 0}).id
3. Start a command in the background using job_start()
4. In the job callback function, check if the quickfix list is still present:
if getqflist({'id' : qfid}).id == qfid
" Still present
" Update the list
else
" List is removed. Stop the background job.
job_stop(....)
endif
5. Process the command output and update the quickfix list using one of the following calls:
call setqflist([], 'a', {'id' : qfid, 'lines' : cmdoutput, 'efm' : myefm})
or
call setqflist([], 'a', {'id' : qfid, 'items' : newitems})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment