Thursday, November 16, 2017

Updating a quickfix/location list asynchronously without interfering with another plugin

Hi all,

There was a recent thread in reddit/r/vim about two Vim plugins updating
the same quickfix list at the same time thereby interfering with each other:

https://www.reddit.com/r/vim/comments/7c5f1a/two_plugins_writing_to_quickfixloc_list_at_the/

As many other plugin authors may need to know how to use the new Vim quickfix
features to avoid this, I am sending this e-mail.

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 the quickfix list asynchronously opens up a possibility that two or
more plugins may try to update the same quickfix list with different output.
Also when a plugin is updating the 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 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 any list in the quickfix stack. A list in the stack can be
specified using the quickfix identifier. So if a Vim plugin uses these
functions to operate on a specific quickfix list then it can avoid interfering
with the operation of another plugin operating on 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 a more than 10 quickfix lists are added to the quickfix stack, then the
oldest quickfix list is removed. The plugin should check whether the quickfix
list it is using is still valid using the following:

if has_key(getqflist({'id' : qfid}), 'id')
" 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 has_key(getqflist({'id' : qfid}), 'id')
" 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})


- Yegappan

--
--
You received this message from the "vim_use" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

---
You received this message because you are subscribed to the Google Groups "vim_use" group.
To unsubscribe from this group and stop receiving emails from it, send an email to vim_use+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

No comments: