Skip to content

Add support for customizing the quickfix buffer contents#5465

Closed
yegappan wants to merge 1 commit intovim:masterfrom
yegappan:qfline
Closed

Add support for customizing the quickfix buffer contents#5465
yegappan wants to merge 1 commit intovim:masterfrom
yegappan:qfline

Conversation

@yegappan
Copy link
Member

No description provided.

@codecov-io
Copy link

codecov-io commented Jan 10, 2020

Codecov Report

Merging #5465 into master will increase coverage by 0.01%.
The diff coverage is 98.91%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #5465      +/-   ##
==========================================
+ Coverage   87.09%   87.10%   +0.01%     
==========================================
  Files         142      142              
  Lines      156609   156651      +42     
==========================================
+ Hits       136397   136450      +53     
+ Misses      20212    20201      -11     
Impacted Files Coverage Δ
src/quickfix.c 95.00% <98.91%> (+0.06%) ⬆️
src/gui_gtk.c 31.05% <0.00%> (-0.28%) ⬇️
src/term.c 81.80% <0.00%> (-0.12%) ⬇️
src/message.c 88.64% <0.00%> (-0.05%) ⬇️
src/terminal.c 83.47% <0.00%> (-0.04%) ⬇️
src/os_unix.c 69.56% <0.00%> (+0.04%) ⬆️
src/gui_gtk_x11.c 57.83% <0.00%> (+0.09%) ⬆️
src/sign.c 94.94% <0.00%> (+0.17%) ⬆️
src/if_xcmdsrv.c 88.90% <0.00%> (+0.17%) ⬆️
src/gui.c 63.85% <0.00%> (+0.55%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 3b6a6eb...28c260d. Read the comment docs.

@brammool
Copy link
Contributor

I wonder if we should make the function argument a dictionary, with the three entries that are currently the arguments. That makes it a lot easier to later add another argument.

The help text mostly refers to the "quickfix buffer". However, existing help text refers to "quickfix window", as it's the window that matters to the user. That it contains a special buffer is more an implementation detail.

Shortening "quickfix" to "qf" in the option name is a bit obscure, it would be the first. How about "quickfixtext"? Or "quickfixtextfunc" to be more accurate, but also quite long. The short version can be 'qftf', which is OK.

@skywind3000
Copy link

What can I do if I want to keep the original text in the quickfix ?? Do I have to write a callback function for this simple purpose ??

@vim-ml
Copy link

vim-ml commented Jan 11, 2020 via email

@skywind3000
Copy link

@yegappan , just one more step, provide a way to leave the original output as what it is. Let me explain why.

Compilers and linters are continually evolving, ten years ago, the output of compilers are still simply enough, just take gcc 4.2 (released in 2008) for example:

test3.cpp:4: error: invalid conversion from 'const char*' to 'int'

They are simply enough and you can interpret them line by line, each line represents a single message, not relevant to the previous or next lines. So you can format them into what ever you like.

But things got changed, let's see the gcc 9.4 output:

test.cpp: In function ‘int main()’:
test.cpp:4:9: error: invalid conversion from ‘const char*’ to ‘int’ [-fpermissive]
    4 |     x = "hello";
      |         ^~~~~~~
      |         |
      |         const char*

In these days, modern compilers and linters trend to use multiple well-formated lines to represent one error. The adjacent lines are relevant to each other. If you change one of them, you will hurt their readabilities and make them hard to understand.

What if another compiler output like this:

quickfix1

After some so called clever conversion, it will look like this in quickfix:

quickfix2

I would rather wish you to keep text untouched in the quickfix window. Any conversion would be unnecessary and will make the result confusion in this simple situation.

I believe the purpose of quickfix is to make information more understandable, not to confuse people. If we agree so, why are we still insist to reformat texts in quickfix ?

And if you think this is still not convincing enough, I'd like show you another example:

The output of rspec is well-formated, beautiful and easy to understand in the shell. But if you notice what neomake/asyncrun/dispatch.vim make out of it in the quickfix window:

图片

Do you still consider the converted text is more understandable than the original text ??

Maybe someone would suggest we should run this compilers or linters directly in a built-in terminal, or output them in another buffer instead of putting them in the quickfix window.

I suppose quickfix is the most brilliant infrastructure in vim. Plugins using quickfix can collaborate with each other:

  • neomake/asyncrun/dispatch can generate outputs in quickfix
  • errormarker can retrive information from qf and set error markers for lines with compile errors.
  • vim-preview can preview errors in the preview window when you press a p in the quickfix.

If every plugin invent their own quickfix window, that would be a disarster.

So, please give us an option to keep the original text in the quickfix window, errors and warnings can still be highlighted and remain selectable.

@brammool & @yegappan , do you think it is a reasonable suggestion ??

@vim-ml
Copy link

vim-ml commented Jan 12, 2020 via email

@vim-ml
Copy link

vim-ml commented Jan 12, 2020 via email

@vim-ml
Copy link

vim-ml commented Jan 12, 2020 via email

@yegappan yegappan force-pushed the qfline branch 2 times, most recently from b741cf4 to 8a11149 Compare January 14, 2020 10:30
@yegappan yegappan force-pushed the qfline branch 3 times, most recently from afbb130 to 1da47ee Compare January 23, 2020 15:49
@yegappan yegappan force-pushed the qfline branch 2 times, most recently from 393f6b0 to ce8c790 Compare February 8, 2020 22:47
@yegappan yegappan force-pushed the qfline branch 5 times, most recently from 03c683c to b567f1b Compare April 21, 2020 21:56
Copy link
Contributor

@puremourning puremourning left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just reviewed the docs because i was curious about this feature. I sort of left feeling that i didn't really get it.

function. This function will be called with a dict argument for every entry
in the quickfix list. The dict argument will have the following fields:

quickfix set to 1 when called for a quickfix list
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what else would it be set to ? Presumably 0 for location list ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. I will update the help text.


The example below tries to mimic the default behavior for the quickfix
and location list windows.
Example: >
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i suspect this example is too complex. Is it better to use a simpler, motivating example? Like why this feature was added in a nutshell?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To keep most of the contents of the quickfix/location list buffer but only modify
selected components like path name or other parts of the line, you still need to
handle all the cases. I will add comments to this example.


This can be customized by setting the 'quickfixtextfunc' option to a Vim
function. This function will be called with a dict argument for every entry
in the quickfix list. The dict argument will have the following fields:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how do you get the text of the current "index"? presumably using getqflist and specifying the index in the options dict. Worth saying that here to avoid confusion. It's clear in the example below, but:

  • the example is complex
  • the example contains no commentary explaining what it is doing or why

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. You can get the contents of a specific quickfix item using 'index' in
getqflst(). I will update the help text to explicitly state this. I will also
add comments to the example function.


<filename>|<lnum> col <n>|<text>

This can be customized by setting the 'quickfixtextfunc' option to a Vim
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worth introducing the "why" early - the what is fine, but not the why - e.g. what's a motivating use case for this ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There have been asks in the past for customizing the quickfix buffer contents.

For example, you search for a pattern from the top of a source tree ,
and populate the quickfix list with the search results (using a tool like ripgrep).
If you change the current directory now (because you have set 'autochdir' or
used the lcd or tcd commands), then the quickfix window will show the
complete path to the files. If the source tree is deeply nested, then the
results in the quickfix window will be hard to read. It will be useful in
this scenario to abbreviate the file path and show only the last few
characters to to show SRCROOT to replace the path before the
project root directory.

Similarly there was an ask to show the module information in the
quickfix buffer. This was supported by modifying the Vim source
code. Instead of modifying the source code, we can use this
function to customize the quickfix fix line.

Copy link
Member Author

@yegappan yegappan Apr 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An example to illustrate how to substitute the project source root directory
with SRCROOT in the quickfix buffer is below:

func Myqffunc(info)
" get the quickfix list entry
let e = getqflist({'id' : a:info.id, 'idx' : a:info.idx, 'items' : 1}).items[0]
" substitute the source root directory with SRCROOT
let s = fnamemodify(bufname(e.bufnr), ':p:.:s#/project/source/root#SRCROOT#')
" add the line number and the line text
let s ..= '|' .. e.lnum .. '| ' .. substitute(e.text, '^\s+', '', '')
return s
endfunc
set quickfixtextfunc=Myqffunc

@yegappan yegappan force-pushed the qfline branch 2 times, most recently from 533a173 to c2d0622 Compare April 30, 2020 22:44
let e = getqflist({'id' : a:info.id, 'idx' : a:info.idx,
\ 'items' : 1}).items[0]
" return the simplified file name
return fnamemodify(bufname(e.bufnr), ':p:.')
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why only return the file name instead of whole line?

@vim-ml
Copy link

vim-ml commented May 1, 2020 via email

@yegappan yegappan force-pushed the qfline branch 2 times, most recently from 2802eb8 to 415e4d7 Compare May 7, 2020 23:57
@lacygoill
Copy link

lacygoill commented Jun 7, 2020

@yegappan Thank you for working on this feature.

The more entries a quickfix list contains, the more costly the 'quickfixtextfunc' seems to be.

I tried to write what seems to be a simple function:

fu QuickFixTextFunc(info) abort
    if a:info.quickfix
        let e = getqflist({'id': a:info.id, 'idx': a:info.idx, 'items': 1}).items[0]
    else
        let e = getloclist(0, {'id': a:info.id, 'idx': a:info.idx, 'items': 1}).items[0]
    endif
    let fname = fnamemodify(bufname(e.bufnr), ':t')
    if strchars(fname, 1) <= 8
        let fname = printf('%8s', fname)
    else
        let fname = printf('%.7s', fname)..'…'
    endif
    let lnum = printf('%5d', e.lnum)
    let col = printf('%3d', e.col)
    return fname..'|'..lnum..' col '..col..'| '..e.text
endfu

And tested it with this command:

$ vim -Nu NONE -S <(curl -Ls https://gist.githubusercontent.com/lacygoill/57d3cd8b5313159f42af43704ebfa7a8/raw/a255cbc37efe18319690943129d0abdf661c1434/profile_quickfixtextfunc.vim)
:QfTf 8

On my machine, the test took about 2 minutes to complete. Here are the results:

   1000 entries:   0.001 seconds to run :copen
                   0.039 seconds to run :copen with 'qftf'  39 times slower
   2000 entries:   0.001 seconds to run :copen
                   0.084 seconds to run :copen with 'qftf'  84 times slower
   4000 entries:   0.003 seconds to run :copen
                   0.190 seconds to run :copen with 'qftf'  63 times slower
   8000 entries:   0.006 seconds to run :copen
                   0.457 seconds to run :copen with 'qftf'  76 times slower
  16000 entries:   0.012 seconds to run :copen
                   1.260 seconds to run :copen with 'qftf'  105 times slower
  32000 entries:   0.025 seconds to run :copen
                   4.983 seconds to run :copen with 'qftf'  199 times slower
  64000 entries:   0.054 seconds to run :copen
                  23.322 seconds to run :copen with 'qftf'  431 times slower
 128000 entries:   0.107 seconds to run :copen
                  97.002 seconds to run :copen with 'qftf'  906 times slower

As you can see, for a quickfix list with more than a hundred thousand entries, 'quickfixtextfunc' makes :copen around a thousand times slower. And the more entries, the slower the feature is.

Do you think something could be done to prevent the function from being called when the quickfix list contains too many entries? Maybe the option could be set to a colon separated list of values: the name of the function to format the entry in the quickfix window, and an optional number. When the size of a quickfix list goes beyond that number, the function would not be invoked to format the entries. They would be displayed as if the option was not set.

@brammool
Copy link
Contributor

brammool commented Jun 7, 2020

I'm not sure if it works, but you could try defining QuickFixTextFunc() with :def.
Will require a slightly different syntax. Much of the work is still done by getqflist(), thus compiling might not help that much.
Otherwise, please try profiling this to find out where time is spent.

@vim-ml
Copy link

vim-ml commented Jun 7, 2020 via email

@vim-ml
Copy link

vim-ml commented Jun 10, 2020 via email

@vim-ml
Copy link

vim-ml commented Jun 10, 2020 via email

@vim-ml
Copy link

vim-ml commented Jun 11, 2020 via email

@lacygoill
Copy link

I'm not sure if it works, but you could try defining QuickFixTextFunc() with :def.
Will require a slightly different syntax. Much of the work is still done by getqflist(), thus compiling might not help that much.

So, I finally converted the code into Vim9 script, and it's indeed much quicker. 5 times quicker on average.

Old code:

fu QuickFixTextFunc(info) abort
    if a:info.quickfix
        let qfl = getqflist({'id': a:info.id, 'items': 1}).items
    else
        let qfl = getloclist(a:info.winid, {'id': a:info.id, 'items': 1}).items
    endif
    let l = []
    for idx in range(a:info.start_idx - 1, a:info.end_idx - 1)
        let e = qfl[idx]
        let fname = fnamemodify(bufname(e.bufnr), ':t')
        if strchars(fname, 1) <= 8
            let fname = printf('%8s', fname)
        else
            let fname = printf('%.7s', fname)..'…'
        endif
        let lnum = printf('%5d', e.lnum)
        let col = printf('%3d', e.col)
        call add(l, fname..'|'..lnum..' col '..col..'| '..e.text)
    endfor
    return l
endfu

com -bar -nargs=? QfTf call QfTf(<args>)

fu QfTf(...) abort
    mess clear
    let m = get(a:, '1', 5)
    for n in repeat([1], m)->map('1000*v:val*pow(2, v:key)->float2nr()')
        set qftf=
        call Test(n)
        set qftf=QuickFixTextFunc
        call Test(n, 1)
    endfor
    sil %d
    sil pu=execute('mess')
    sil v/seconds/d_
    g/qftf/call FormatLine()
endfu

fu Test(n, ...) abort
    e! /tmp/file
    sil %d
    sil pu=repeat(['some text'], a:n)
    vim /text/gj %
    let time = reltime()
    copen
    let time = matchstr(reltimestr(reltime(time)), '.*\..\{,3}')
    redraw
    let entries = printf('%7d entries: ', a:n)
    if a:0
        echom printf("%s%s seconds to run :copen with 'qftf'", repeat(' ', strlen(entries)), time)
    else
        echom printf('%s%s seconds to run :copen', entries, time)
    endif
    cclose
endfu

fu FormatLine() abort
    let time_here = getline('.')->matchlist('\(\d\+\)\.\(\d\+\)')[1:2]->join('')->str2nr()
    let time_above = getline(line('.')-1)->matchlist('\(\d\+\)\.\(\d\+\)')[1:2]->join('')->str2nr()
    let ratio = time_above ? time_here / time_above : '??'
    exe 'norm! A  '..ratio..' times slower'
endfu

Results from :QfTf 10:

  1000 entries:   0.000 seconds to run :copen
                  0.024 seconds to run :copen with 'qftf'  ?? times slower
  2000 entries:   0.001 seconds to run :copen
                  0.049 seconds to run :copen with 'qftf'  49 times slower
  4000 entries:   0.003 seconds to run :copen
                  0.098 seconds to run :copen with 'qftf'  32 times slower
  8000 entries:   0.006 seconds to run :copen
                  0.203 seconds to run :copen with 'qftf'  33 times slower
 16000 entries:   0.012 seconds to run :copen
                  0.401 seconds to run :copen with 'qftf'  33 times slower
 32000 entries:   0.024 seconds to run :copen
                  0.797 seconds to run :copen with 'qftf'  33 times slower
 64000 entries:   0.049 seconds to run :copen
                  1.587 seconds to run :copen with 'qftf'  32 times slower
128000 entries:   0.102 seconds to run :copen
                  3.197 seconds to run :copen with 'qftf'  31 times slower
256000 entries:   0.206 seconds to run :copen
                  6.442 seconds to run :copen with 'qftf'  31 times slower
512000 entries:   0.409 seconds to run :copen
                 12.775 seconds to run :copen with 'qftf'  31 times slower

New code:

vim9script

def QuickFixTextFunc(info: dict<number>): list<string>
    let qfl: list<any>
    if info.quickfix
        qfl = getqflist(#{id: info.id, items: 0}).items
    else
        qfl = getloclist(info.winid, #{id: info.id, items: 0}).items
    endif
    let l: list<string> = []
    for idx in range(info.start_idx - 1, info.end_idx - 1)
        let e = qfl[idx]
        let fname = bufname(e.bufnr)->fnamemodify(':t')
        if strchars(fname, 1) <= 8
            fname = printf('%8s', fname)
        else
            fname = printf('%.7s', fname) .. '…'
        endif
        let lnum = printf('%5d', e.lnum)
        let col = printf('%3d', e.col)
        add(l, fname .. '|' .. lnum .. ' col ' .. col .. '| ' .. e.text)
    endfor
    return l
enddef

com -bar -nargs=? QfTf call QfTf(<args>)

def QfTf(m = 5)
    mess clear
    let alist: list<number> = repeat([1], m)->map('1000*v:val*pow(2, v:key)->float2nr()')
    for n in alist
        set qftf=
        call Test(n)
        set qftf=QuickFixTextFunc
        call Test(n, v:true)
    endfor
    :sil %d
    sil pu=execute('mess')
    sil v/seconds/d_
    g/qftf/call FormatLine()
enddef

def Test(n: number, qftf = v:false)
    e! /tmp/file
    :sil %d
    setline(1, repeat(['some text'], n))
    vim /text/gj %
    let time = reltime()
    copen
    let _time = reltime(time)->reltimestr()->matchstr('.*\..\{,3}')
    redraw
    let entries = printf('%7d entries: ', n)
    if qftf
        echom printf("%s%s seconds to run :copen with 'qftf'", repeat(' ', strlen(entries)), _time)
    else
        echom printf('%s%s seconds to run :copen', entries, _time)
    endif
    cclose
enddef

fu FormatLine()
    let time_here = getline('.')->matchlist('\(\d\+\)\.\(\d\+\)')[1:2]->join('')->str2nr()
    let time_above = getline(line('.')-1)->matchlist('\(\d\+\)\.\(\d\+\)')[1:2]->join('')->str2nr()
    let ratio = time_above ? time_here / time_above : '??'
    exe 'norm! A  '..ratio..' times slower'
endfu

Results from :QfTf 10:

1000 entries:   0.001 seconds to run :copen
                0.005 seconds to run :copen with 'qftf'  5 times slower
2000 entries:   0.001 seconds to run :copen
                0.010 seconds to run :copen with 'qftf'  10 times slower
4000 entries:   0.003 seconds to run :copen
                0.021 seconds to run :copen with 'qftf'  7 times slower
8000 entries:   0.006 seconds to run :copen
                0.042 seconds to run :copen with 'qftf'  7 times slower
16000 entries:  0.012 seconds to run :copen
                0.085 seconds to run :copen with 'qftf'  7 times slower
32000 entries:  0.027 seconds to run :copen
                0.169 seconds to run :copen with 'qftf'  6 times slower
64000 entries:  0.050 seconds to run :copen
                0.354 seconds to run :copen with 'qftf'  7 times slower
128000 entries: 0.101 seconds to run :copen
                0.693 seconds to run :copen with 'qftf'  6 times slower
256000 entries: 0.218 seconds to run :copen
                1.374 seconds to run :copen with 'qftf'  6 times slower
512000 entries: 0.413 seconds to run :copen
                2.773 seconds to run :copen with 'qftf'  6 times slower

The results are clear, Vim9 helps a lot.

When it'll be ready, maybe the help at :h quickfix-window-function could include a Vim9 example:

vim9script
" create a quickfix list from v:oldfiles
setqflist([], ' ', {'lines' : v:oldfiles, 'efm' : '%f', 'quickfixtextfunc' : 'QfOldFiles'})
def g:QfOldFiles(info: dict<number>): list<string>
    " get information about a range of quickfix entries
    let items = getqflist({'id' : info.id, 'items' : 1}).items
    let l: list<string> = []
    for idx in range(info.start_idx - 1, info.end_idx - 1)
        " use the simplified file name
        add(l, bufname(items[idx].bufnr)->fnamemodify(':p:.'))
    endfor
    return l
enddef

@lacygoill
Copy link

@yegappan Do you think it would be possible for 'quickfixtextfunc' to be assigned a lambda, in addition to a function name?

I have currently set the option to a function which aligns the fields in the quickfix window; that's what I usually want. But it badly interferes with one of my plugins which assumes another specific formatting in the quickfix window. For the latter, I need to disable the feature; I can do it like so:

call setqflist([], ' ', {'items': items, 'quickfixtextfunc': 's:disable_qftf'})
fu s:disable_qftf(info) abort
    return []
endfu

But it would be easier to read/write if I could use a lambda:

call setqflist([], ' ', { 'items': items, 'quickfixtextfunc': {-> []} })
                                                              ^-----^

Btw, returning an empty list makes Vim display the lines unchanged; this is very useful, but I don't think it's documented.

@vim-ml
Copy link

vim-ml commented Jul 11, 2020 via email

@bfrg
Copy link
Contributor

bfrg commented Jul 11, 2020

@yegappan Currently the quickfixtextfunc entry can be set in setqflist() but it can't be obtained from getqflist(). Imagine I filter a quickfix list that has a specific quickfixtextfunc. After filtering the list I need to reapply the same quickfixtextfunc. But there's no easy way to obtain it for the current list.

Another issue is that different quickfix formats might need different syntax files. When I permanently override the default quickfix format in my vimrc with set quickfixtextfunc=MyQuickfixTextFunc, I can simply also change the default syntax/qf.vim to my needs. But for locally specified quickfixtextfunc this is not easy. Maybe an additional entry 'filetype' could be specified in setqflist() as well as obtained from getqflist() in addition to the quickfixtextfunc?

@lacygoill
Copy link

lacygoill commented Jul 11, 2020

Another issue is that different quickfix formats might need different syntax files. When I permanently override the default quickfix format in my vimrc with set quickfixtextfunc=MyQuickfixTextFunc, I can simply also change the default syntax/qf.vim to my needs. But for locally specified quickfixtextfunc this is not easy. Maybe an additional entry 'filetype' could be specified in setqflist() as well as obtained from getqflist() in addition to the quickfixtextfunc?

Would it help if the option became global-local instead of simply global? You could then set 'qftf' from a filetype plugin.

@bfrg
Copy link
Contributor

bfrg commented Jul 11, 2020

Would it help if the option became global-local instead of simply global? You could then set 'qftf' from a filetype plugin.

I don't think that's possible because the buffer displayed in the quickfix window is generated using the quickfixtextfunc with the given quickfix items. And after the buffer is generated the filetype plugin (currently qf.vim) is sourced.

@lacygoill
Copy link

Another thing which may need to be documented at :h quickfix-window-function, is the case of script local functions. You can write this:

let items = [{'filename': $VIMRUNTIME..'/doc/index.txt',
    \ 'lnum': 1124, 'valid': 1, 'text': 'You found it, Arthur!'}]
call setqflist([], ' ', {'items': items, 'quickfixtextfunc': 's:func'})
"                                                             ^^
fu s:func(_)
    return []
endfu
cw

And it will work as expected. But if you close the quickfix window with :q, then reopen it with :cw, then E120 is raised:

E120: Using <SID> not in a script context: s:func

And if :cw is run from a function defined in another script:

nno cd :cw<cr>
fu Func()
    cw
endfu

Then E117 is raised:

Error detected while processing function Func:
line    1:
E117: Unknown function: s:func

I don't think it's a bug. It's probably just that the code which handles the 'quickfixtextfunc' property shares some code with the one handling the option. And in an option, you can't write s:. You need to translate the latter, with something like this:

fu s:sid() abort
    return matchstr(expand('<sfile>'), '.*\zs<SNR>\d\+_')
endfu
let s:sid = get(s:, 'sid', s:sid())

...

call setqflist([], ' ', {'items': items, 'quickfixtextfunc': s:sid .. 'func'})
                                                             ^------^

But it's a bit unexpected to see the code work initially, then fail on reopening the quickfix window:

This patch is an attempt at documenting the pitfall:

diff --git a/runtime/doc/quickfix.txt b/runtime/doc/quickfix.txt
index 293014b24..24402824a 100644
--- a/runtime/doc/quickfix.txt
+++ b/runtime/doc/quickfix.txt
@@ -1989,5 +1989,19 @@ Example: >
 	return l
     endfunc
 <
+Note that if the function is local to a script, you need to translate `s:`
+manually for the code to work as expected after closing and reopening the
+quickfix window: >
+
+    func s:SID()
+        return matchstr(expand('<sfile>'), '.*\zs<SNR>\d\+_')
+    endfunc
+    let s:SID = s:SID()
+
+    call setqflist([], ' ', {'items': items, 'quickfixtextfunc': s:SID .. 'QfOldFiles'})
+    func s:QfOldFiles(info)
+        ...
+    endfunc
+<
 
  vim:tw=78:ts=8:noet:ft=help:norl:

@bfrg
Copy link
Contributor

bfrg commented Jul 12, 2020

Why aren't you using a funcref?

call setqflist([], ' ', {'items': items, 'quickfixtextfunc': funcref('s:func')})

Just make sure the function is defined before the call to setqflist().

@lacygoill
Copy link

Ah, good idea; a funcref avoids the pitfall.

@lacygoill
Copy link

lacygoill commented Jul 16, 2020

Actually no, you can't provide a funcref:

vim9script
set qftf=GlobalOption
def GlobalOption(info: dict<number>): list<string>
    let l: list<string> = []
    for idx in range(info.start_idx - 1, info.end_idx - 1)
        add(l, 'global option used')
    endfor
    return l
enddef
def QfAttribute(info: dict<number>): list<string>
    return []
enddef
setqflist([], ' ', {'lines': v:oldfiles, 'efm': '%f', 'quickfixtextfunc': function('QfAttribute')})
cw

Which makes sense because – I guess – the implementation of the quickfix list attribute 'quickfixtextfunc' shares some code with the implementation of the global option. So you really have to manually translate the script local scope s:. Just like you would have for any '*func' option (e.g. 'operatorfunc', 'completefunc', ...) which expects a function name, and not a funcref.

@bfrg
Copy link
Contributor

bfrg commented Jul 16, 2020

So it seems when 'qftf' is set the funcref is ignored, but when 'qftf' it not set to any function, the funcref works.

@lacygoill
Copy link

For me the funcref never works, even when 'qftf' is not set:

vim9script
def QfAttribute(info: dict<number>): list<string>
    let l: list<string> = []
    for idx in range(info.start_idx - 1, info.end_idx - 1)
        add(l, 'quickfix attribute used')
    endfor
    return l
enddef
setqflist([], ' ', {'lines': v:oldfiles, 'efm': '%f', 'quickfixtextfunc': function('QfAttribute')})
cw

Although, no error is raised which is why I first thought it was working. But it's really not; if it was, I would read the text quickfix attribute used on each line; instead, I can read the names of the files in v:oldfiles.

@bfrg
Copy link
Contributor

bfrg commented Jul 16, 2020

Yes, you're right.

@vim-ml
Copy link

vim-ml commented Jul 20, 2020 via email

@vim-ml
Copy link

vim-ml commented Jul 20, 2020 via email

kevinhwang91 added a commit to kevinhwang91/neovim that referenced this pull request Dec 28, 2020
…ow contents

Problem:    It is not possible to customize the quickfix window contents.
Solution:   Add 'quickfixtextfunc'. (Yegappan Lakshmanan, closes vim/vim#5465)
vim/vim@858ba06
kevinhwang91 added a commit to kevinhwang91/neovim that referenced this pull request Dec 28, 2020
…ow contents

Problem:    It is not possible to customize the quickfix window contents.
Solution:   Add 'quickfixtextfunc'. (Yegappan Lakshmanan, closes vim/vim#5465)
vim/vim@858ba06
kevinhwang91 added a commit to kevinhwang91/neovim that referenced this pull request Dec 29, 2020
…ow contents

Problem:    It is not possible to customize the quickfix window contents.
Solution:   Add 'quickfixtextfunc'. (Yegappan Lakshmanan, closes vim/vim#5465)
vim/vim@858ba06
kevinhwang91 added a commit to kevinhwang91/neovim that referenced this pull request Dec 29, 2020
…ow contents

Problem:    It is not possible to customize the quickfix window contents.
Solution:   Add 'quickfixtextfunc'. (Yegappan Lakshmanan, closes vim/vim#5465)
vim/vim@858ba06
kevinhwang91 added a commit to kevinhwang91/neovim that referenced this pull request Dec 29, 2020
…ow contents

Problem:    It is not possible to customize the quickfix window contents.
Solution:   Add 'quickfixtextfunc'. (Yegappan Lakshmanan, closes vim/vim#5465)
vim/vim@858ba06
kevinhwang91 added a commit to kevinhwang91/neovim that referenced this pull request May 4, 2021
…ow contents

Problem:    It is not possible to customize the quickfix window contents.
Solution:   Add 'quickfixtextfunc'. (Yegappan Lakshmanan, closes vim/vim#5465)
vim/vim@858ba06
kevinhwang91 added a commit to kevinhwang91/neovim that referenced this pull request May 4, 2021
…ow contents

Problem:    It is not possible to customize the quickfix window contents.
Solution:   Add 'quickfixtextfunc'. (Yegappan Lakshmanan, closes vim/vim#5465)
vim/vim@858ba06
kevinhwang91 added a commit to kevinhwang91/neovim that referenced this pull request May 19, 2021
…ow contents

Problem:    It is not possible to customize the quickfix window contents.
Solution:   Add 'quickfixtextfunc'. (Yegappan Lakshmanan, closes vim/vim#5465)
vim/vim@858ba06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

10 participants