Skip to content

Instantly share code, notes, and snippets.

@BoltsJ
Last active October 23, 2024 01:04
Show Gist options
  • Save BoltsJ/5942ecac7f0b0e9811749ef6e19d2176 to your computer and use it in GitHub Desktop.
Save BoltsJ/5942ecac7f0b0e9811749ef6e19d2176 to your computer and use it in GitHub Desktop.
Automatically place signs based on the quickfix list.
if exists('g:loaded_qfsign')
finish
endif
let g:loaded_qfsign=1
sign define QFErr texthl=QFErrMarker text=E
sign define QFWarn texthl=QFWarnMarker text=W
sign define QFInfo texthl=QFInfoMarker text=I
augroup qfsign
autocmd!
autocmd QuickFixCmdPre [^l]* call s:clear_signs()
autocmd QuickFixCmdPost [^l]* call s:place_signs()
augroup END
nnoremap <Plug>(QfSignPlace) :silent call <SID>place_signs()<CR>
nnoremap <Plug>(QfSignClear) :silent call <SID>clear_signs()<CR>
let s:sign_count = 0
function! s:place_signs() abort
let l:errors = getqflist()
for l:error in l:errors
if l:error.bufnr < 0
continue
endif
let s:sign_count = s:sign_count + 1
if l:error.type ==# 'E'
let l:err_sign = 'sign place ' . s:sign_count
\ . ' line=' . l:error.lnum
\ . ' name=QFErr'
\ . ' buffer=' . l:error.bufnr
elseif l:error.type ==# 'W'
let l:err_sign = 'sign place ' . s:sign_count
\ . ' line=' . l:error.lnum
\ . ' name=QFWarn'
\ . ' buffer=' . l:error.bufnr
else
let l:err_sign = 'sign place ' . s:sign_count
\ . ' line=' . l:error.lnum
\ . ' name=QFInfo'
\ . ' buffer=' . l:error.bufnr
endif
silent! execute l:err_sign
endfor
endfunction
function! s:clear_signs() abort
while s:sign_count > 0
execute 'sign unplace ' . s:sign_count
let s:sign_count = s:sign_count - 1
endwhile
redraw!
endfunction
@OlegPQ
Copy link

OlegPQ commented Nov 25, 2021

Hi @BoltsJ , thanks for the script and for the youtube channel!
question: is it possible to do so that the signs would be hidden/shown by a combination of keys?

@BoltsJ
Copy link
Author

BoltsJ commented Dec 1, 2021

I added some <Plug> mappings that can be mapped, but this gist is mostly just a minimal example of working with signs.

@pbnj
Copy link

pbnj commented Dec 5, 2022

This is really neat! Thank you for sharing.

A few improvements I've made that I would like to share:


Filter out text unmatched by errorformat

In cases where linter/compiler output doesn't match errorformat exactly, getqflist() still reports it, causing the unmatched output to be calculated/reported in the final else control flow.

For example, consider the following tflint.vim compiler configuration:

CompilerSet makeprg=tflint\ --format=compact\ $*
CompilerSet errorformat=%f:%l:%c:\ %trror\ -\ %m,%f:%l:%c:\ %tarning\ -\ %m,%f:%l:%c:\ -\ %totice\ %m

And the following terraform file:

data "" "" {}

Running :make % produces:

2 issue(s) found:

data.tf:1:1: Warning - Missing version constraint for provider "" in "required_providers" (terraform_required_providers)
data.tf:1:1: Warning - data "" "" is declared but not used (terraform_unused_declarations)

Running :echo getqflist() in vim produces (note the first 2 elements in the array):

[
  {
    "lnum": 0,
    "bufnr": 0,
    "end_lnum": 0,
    "pattern": "",
    "valid": 0,
    "vcol": 0,
    "nr": -1,
    "module": "",
    "type": "",
    "end_col": 0,
    "col": 0,
    "text": "2 issue(s) found:"
  },
  {
    "lnum": 0,
    "bufnr": 0,
    "end_lnum": 0,
    "pattern": "",
    "valid": 0,
    "vcol": 0,
    "nr": -1,
    "module": "",
    "type": "",
    "end_col": 0,
    "col": 0,
    "text": ""
  },
  {
    "lnum": 1,
    "bufnr": 1,
    "end_lnum": 0,
    "pattern": "",
    "valid": 1,
    "vcol": 0,
    "nr": -1,
    "module": "",
    "type": "W",
    "end_col": 0,
    "col": 1,
    "text": "Missing version constraint for provider \"\" in \"required_providers\" (terraform_required_providers)"
  },
  {
    "lnum": 1,
    "bufnr": 1,
    "end_lnum": 0,
    "pattern": "",
    "valid": 1,
    "vcol": 0,
    "nr": -1,
    "module": "",
    "type": "W",
    "end_col": 0,
    "col": 1,
    "text": "data \"\" \"\" is declared but not used (terraform_unused_declarations)"
  }
]

A simple fix is to check if bufnr <= 0 (is buffer number of 0 even possible?), like:

if l:error.bufnr <= 0
      continue
endif

Alternative, you can check if lnum is 0 (because why/how would you show signs for something with no line number?), like:

if l:error.bufnr < 0 || l:error.lnum == 0
      continue
endif

Sign Priority

Signs were not showing up in my signcolumn. This was because Vim assigns a default priority of 10 for signs (:h sign-place). Priority helps Vim determine which sign to show when multiple signs are placed on the same line. This scenario happes easily & frequently with plugins like vim-signify or vim-gitgutter.

I fixed this by assigning a priority of 99 to errors, 98 to warnings, and 97 to info, because I much rather see issues with code than git status indicators in my signcolumn, like this:

    if l:error.type ==# 'E'
      let l:qf_sign = 'sign place ' . s:sign_count
            \ . ' priority=99'
            " ...
    elseif l:error.type ==# 'W'
      let l:qf_sign = 'sign place ' . s:sign_count
            \ . ' priority=98'
            " ...
    else
      let l:qf_sign = 'sign place ' . s:sign_count
            \ . ' priority=97'
            " ...

Note: priority must be specified/assigned before file= or buffer=.

" this works
:sign place priority=99 line=1 name=QFErr buffer=1

" this does not ¯\_(ツ)_/¯
:sign place line=1 name=QFErr buffer=1 priority=99

Support location lists

" ...

  autocmd QuickFixCmdPre * call s:clear_signs()
  autocmd QuickFixCmdPost [^l]* call s:place_signs('qf')
  autocmd QuickFixCmdPost l* call s:place_signs('ll')

" ...

function! s:place_signs(list_type) abort
  if a:list_type == 'qf'
    let l:errors = getqflist()
  elseif a:list_type == 'll'
    let l:errors = getloclist(winnr())
  endif

  " ...

Surface counts to vimrc

I placed this script in my ~/.vim/plugin/ directory and wanted to surface the count of errors/warnings/infos to show in my statusline.

" ...
let s:sign_count = 0

" Surface counts to .vimrc
let g:qfsigns_error = 0
let g:qfsigns_warn = 0
let g:qfsigns_info = 0

" ...

function! s:place_signs(list_type) abort
  let l:qfsigns_error = 0
  let l:qfsigns_warn = 0
  let l:qfsigns_info = 0
  
  for l:error in l:errors

  " ...

    if l:error.type ==# 'E'
      let l:qfsigns_error = l:qfsigns_error + 1
    " ...
    elseif l:error.type ==# 'W'
      let l:qfsigns_warn = l:qfsigns_warn + 1
    " ...
    else
      let l:qfsigns_info = l:qfsigns_info + 1
    " ...

  endfor

  let g:qfsigns_error = l:qfsigns_error
  let g:qfsigns_warn = l:qfsigns_warn
  let g:qfsigns_info = l:qfsigns_info
endfunction

Then in my vimrc:

set statusline+=%#QFErrMarker#%{(g:qfsigns_error>0)?'[E:'.g:qfsigns_error.']':''}%*
set statusline+=%#QFWarnMarker#%{(g:qfsigns_warn>0)?'[W:'.g:qfsigns_warn.']':''}%*
set statusline+=%#QFInfoMarker#%{(g:qfsigns_info>0)?'[I:'.g:qfsigns_info.']':''}%*

Everything put together: https://gist.github.com/pbnj/dc19a2d0f579933daef7f0fd9604dc0c

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment