From 2ec5ad43c3b4adc709e9d4b0b0af9a0fe2497b86 Mon Sep 17 00:00:00 2001 From: Billie Cleek Date: Sun, 12 Jan 2020 08:41:53 -0800 Subject: [PATCH 1/4] register text properties for highlighting Register global text properties for highlighting text based on position. Each property name is the same as the highlight group that it used by the property. --- ftplugin/go.vim | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/ftplugin/go.vim b/ftplugin/go.vim index 711b053e13..d7b6c5c5d9 100644 --- a/ftplugin/go.vim +++ b/ftplugin/go.vim @@ -74,6 +74,18 @@ if get(g:, "go_textobj_enabled", 1) xnoremap [[ :call go#textobj#FunctionJump('v', 'prev') endif +if exists('*prop_type_add') + if empty(prop_type_get('goSameId')) + call prop_type_add('goSameId', {'highlight': 'goSameId'}) + endif + if empty(prop_type_get('goDiagnosticError')) + call prop_type_add('goDiagnosticError', {'highlight': 'goDiagnosticError'}) + endif + if empty(prop_type_get('goDiagnosticWarning')) + call prop_type_add('goDiagnosticWarning', {'highlight': 'goDiagnosticWarning'}) + endif +endif + " Autocommands " ============================================================================ " From aeee359d7b1ce272c67dc0be475e1f862ee95a42 Mon Sep 17 00:00:00 2001 From: Billie Cleek Date: Sun, 12 Jan 2020 08:58:28 -0800 Subject: [PATCH 2/4] refactor highlighting to use text properties Refactor highlight of goSameId, goDiagnosticWarning, and goDiagnosticError to use text properties instead of matches when text properties are available. --- autoload/go/guru.vim | 2 +- autoload/go/lsp.vim | 56 ++++++++++++++++++++--------------------- autoload/go/util.vim | 60 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 86 insertions(+), 32 deletions(-) diff --git a/autoload/go/guru.vim b/autoload/go/guru.vim index 47f75ba86e..9c5851fd41 100644 --- a/autoload/go/guru.vim +++ b/autoload/go/guru.vim @@ -477,7 +477,7 @@ function! s:same_ids_highlight(exit_val, output, mode) abort let l:matches = add(l:matches, [str2nr(pos[-2]), str2nr(pos[-1]), str2nr(poslen)]) endfor - call go#util#MatchAddPos('goSameId', l:matches) + call go#util#HighlightPositions('goSameId', l:matches) if go#config#AutoSameids() " re-apply SameIds at the current cursor position at the time the buffer diff --git a/autoload/go/lsp.vim b/autoload/go/lsp.vim index 5b11e0ff25..ecb3b608ac 100644 --- a/autoload/go/lsp.vim +++ b/autoload/go/lsp.vim @@ -248,6 +248,9 @@ function! s:newlsp() abort endif for l:diag in l:data.diagnostics + " TODO(bc): cache the raw diagnostics when they're not for the + " current buffer so that they can be processed when it is the + " current buffer and highlight the areas of concern. let [l:error, l:matchpos] = s:errorFromDiagnostic(l:diag, l:bufname, l:fname) let l:diagnostics = add(l:diagnostics, l:error) @@ -1189,38 +1192,35 @@ function! s:errorFromDiagnostic(diagnostic, bufname, fname) abort endfunction function! s:highlightMatches(errorMatches, warningMatches) abort - " TODO(bc): use text properties instead of matchaddpos - if exists("*matchaddpos") - " set buffer variables for errors and warnings to zero values - let b:go_diagnostic_matches = {'errors': [], 'warnings': []} - - if hlexists('goDiagnosticError') - " clear the old matches just before adding the new ones to keep flicker - " to a minimum. - call go#util#ClearGroupFromMatches('goDiagnosticError') - if go#config#HighlightDiagnosticErrors() - let b:go_diagnostic_matches.errors = copy(a:errorMatches) - call go#util#MatchAddPos('goDiagnosticError', a:errorMatches) - endif + " set buffer variables for errors and warnings to zero values + let b:go_diagnostic_matches = {'errors': [], 'warnings': []} + + if hlexists('goDiagnosticError') + " clear the old matches just before adding the new ones to keep flicker + " to a minimum. + call go#util#ClearGroupFromMatches('goDiagnosticError') + if go#config#HighlightDiagnosticErrors() + let b:go_diagnostic_matches.errors = copy(a:errorMatches) + call go#util#HighlightPositions('goDiagnosticError', a:errorMatches) endif + endif - if hlexists('goDiagnosticWarning') - " clear the old matches just before adding the new ones to keep flicker - " to a minimum. - call go#util#ClearGroupFromMatches('goDiagnosticWarning') - if go#config#HighlightDiagnosticWarnings() - let b:go_diagnostic_matches.warnings = copy(a:warningMatches) - call go#util#MatchAddPos('goDiagnosticWarning', a:warningMatches) - endif + if hlexists('goDiagnosticWarning') + " clear the old matches just before adding the new ones to keep flicker + " to a minimum. + call go#util#ClearGroupFromMatches('goDiagnosticWarning') + if go#config#HighlightDiagnosticWarnings() + let b:go_diagnostic_matches.warnings = copy(a:warningMatches) + call go#util#HighlightPositions('goDiagnosticWarning', a:warningMatches) endif - - " re-apply matches at the time the buffer is displayed in a new window or - " redisplayed in an existing window: e.g. :edit, - augroup vim-go-diagnostics - autocmd! * - autocmd BufWinEnter nested call s:highlightMatches(b:go_diagnostic_matches.errors, b:go_diagnostic_matches.warnings) - augroup end endif + + " re-apply matches at the time the buffer is displayed in a new window or + " redisplayed in an existing window: e.g. :edit, + augroup vim-go-diagnostics + autocmd! * + autocmd BufWinEnter nested call s:highlightMatches(b:go_diagnostic_matches.errors, b:go_diagnostic_matches.warnings) + augroup end endfunction " ClearDiagnosticsMatches removes all goDiagnosticError and diff --git a/autoload/go/util.vim b/autoload/go/util.vim index 2953dbf358..d0f6ebd43f 100644 --- a/autoload/go/util.vim +++ b/autoload/go/util.vim @@ -589,9 +589,63 @@ endfunction function! s:noop(...) abort dict endfunction -" go#util#MatchAddPos works around matchaddpos()'s limit of only 8 positions -" per call by calling matchaddpos() with no more than 8 positions per call. -function! go#util#MatchAddPos(group, pos) +" go#util#HighlightPositions highlights using text properties if possible and +" falls back to matchaddpos() if necessary. It works around matchaddpos()'s +" limit of only 8 positions per call by calling matchaddpos() with no more +" than 8 positions per call. +" +" pos should be a list of 3 element lists. The lists should be [line, col, +" length] as used by matchaddpos(). +function! go#util#HighlightPositions(group, pos) abort + if exists('*prop_add') + for l:pos in a:pos + " use a single line prop by default + let l:prop = {'type': a:group, 'length': l:pos[2]} + + " specify end line and column if needed. + let l:line = getline(l:pos[0]) + + " TODO(bc): use line2byte and byte2line to get the end position more + " efficiently once https://github.com/vim/vim/issues/5334 is resolved. + " e.g.: + " " l:max is the 1-based index within the buffer of the first character after l:pos. + " let l:max = line2byte(l:pos[0]) + l:pos[1] + l:pos[2] - 1 + " let l:end_lnum = byte2line(l:max) + + " "echom printf('processing (%s)', string(l:pos)) + " if l:pos[0] != l:end_lnum + " let l:end_col = l:max - line2byte(l:end_lnum) + " "echom string(l:pos) + " "echom printf('l:end_col = %d - %d = %d', l:max, line2byte(l:end_lnum), l:end_col) + " "echom printf('line2byte(l:pos[0]) = %d, l:pos[0] = %d, l:end_col = %d, l:end_lnum = %d', line2byte(l:pos[0]), l:pos[0], l:end_col, l:end_lnum) + " let l:prop = {'type': a:group, 'end_lnum': l:end_lnum, 'end_col': l:end_col} + if l:pos[1] + l:pos[2] - 1 > len(l:line) + " l:max is the 1-based index within the buffer of the first character + " after l:pos. + let l:max = line2byte(l:pos[0]) + l:pos[1] + l:pos[2] - 1 + + let l:end_lnum = l:pos[0] + let l:end_col = l:pos[1] + l:pos[2] - 1 + while line2byte(l:end_lnum+1) < l:max + let l:end_lnum += 1 + let l:end_col -= line2byte(l:end_lnum) + endwhile + let l:prop = {'type': a:group, 'end_lnum': l:end_lnum, 'end_col': l:end_col} + endif + call prop_add(l:pos[0], l:pos[1], l:prop) + endfor + return + endif + + if exists('*matchaddpos') + return s:matchaddpos(a:group, a:pos) + endif +endfunction + + +" s:matchaddpos works around matchaddpos()'s limit of only 8 positions per +" call by calling matchaddpos() with no more than 8 positions per call. +function! s:matchaddpos(group, pos) abort let l:partitions = [] let l:partitionsIdx = 0 let l:posIdx = 0 From 38da4fb2285559d76547bc0eca508fb193c4f0a7 Mon Sep 17 00:00:00 2001 From: Billie Cleek Date: Sun, 12 Jan 2020 09:12:12 -0800 Subject: [PATCH 3/4] refactor clearing highlighting Refactor clearing highlighting for goSameId, goDiagnosticError and goDiagnosticWarning to clear text properties instead of matches when text properties are available. --- autoload/go/guru.vim | 2 +- autoload/go/lsp.vim | 12 ++++++------ autoload/go/util.vim | 12 +++++++++--- ftplugin/go.vim | 4 ++-- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/autoload/go/guru.vim b/autoload/go/guru.vim index 9c5851fd41..fca7ab8ef4 100644 --- a/autoload/go/guru.vim +++ b/autoload/go/guru.vim @@ -492,7 +492,7 @@ endfunction " ClearSameIds returns 0 when it removes goSameId groups and non-zero if no " goSameId groups are found. function! go#guru#ClearSameIds() abort - let l:cleared = go#util#ClearGroupFromMatches('goSameId') + let l:cleared = go#util#ClearHighlights('goSameId') if !l:cleared return 1 diff --git a/autoload/go/lsp.vim b/autoload/go/lsp.vim index ecb3b608ac..b61da50b13 100644 --- a/autoload/go/lsp.vim +++ b/autoload/go/lsp.vim @@ -1198,7 +1198,7 @@ function! s:highlightMatches(errorMatches, warningMatches) abort if hlexists('goDiagnosticError') " clear the old matches just before adding the new ones to keep flicker " to a minimum. - call go#util#ClearGroupFromMatches('goDiagnosticError') + call go#util#ClearHighlights('goDiagnosticError') if go#config#HighlightDiagnosticErrors() let b:go_diagnostic_matches.errors = copy(a:errorMatches) call go#util#HighlightPositions('goDiagnosticError', a:errorMatches) @@ -1208,7 +1208,7 @@ function! s:highlightMatches(errorMatches, warningMatches) abort if hlexists('goDiagnosticWarning') " clear the old matches just before adding the new ones to keep flicker " to a minimum. - call go#util#ClearGroupFromMatches('goDiagnosticWarning') + call go#util#ClearHighlights('goDiagnosticWarning') if go#config#HighlightDiagnosticWarnings() let b:go_diagnostic_matches.warnings = copy(a:warningMatches) call go#util#HighlightPositions('goDiagnosticWarning', a:warningMatches) @@ -1223,11 +1223,11 @@ function! s:highlightMatches(errorMatches, warningMatches) abort augroup end endfunction -" ClearDiagnosticsMatches removes all goDiagnosticError and +" ClearDiagnosticsHighlights removes all goDiagnosticError and " goDiagnosticWarning matches. -function! go#lsp#ClearDiagnosticMatches() abort - call go#util#ClearGroupFromMatches('goDiagnosticError') - call go#util#ClearGroupFromMatches('goDiagnosticWarning') +function! go#lsp#ClearDiagnosticHighlights() abort + call go#util#ClearHighlights('goDiagnosticError') + call go#util#ClearHighlights('goDiagnosticWarning') endfunction " restore Vi compatibility settings diff --git a/autoload/go/util.vim b/autoload/go/util.vim index d0f6ebd43f..a1a36053d7 100644 --- a/autoload/go/util.vim +++ b/autoload/go/util.vim @@ -551,11 +551,17 @@ function! go#util#SetEnv(name, value) abort return function('go#util#SetEnv', [a:name, l:oldvalue], l:state) endfunction -function! go#util#ClearGroupFromMatches(group) abort - if !exists("*matchaddpos") - return 0 +function! go#util#ClearHighlights(group) abort + if exists('*prop_remove') + return prop_remove({'type': a:group, 'all': 1}) endif + if exists("*matchaddpos") + return s:clear_group_from_matches(a:group) + endif +endfunction + +function! s:clear_group_from_matches(group) abort let l:cleared = 0 let m = getmatches() diff --git a/ftplugin/go.vim b/ftplugin/go.vim index d7b6c5c5d9..a7b15f4636 100644 --- a/ftplugin/go.vim +++ b/ftplugin/go.vim @@ -129,10 +129,10 @@ augroup vim-go-buffer " clear diagnostics when the buffer is unloaded from its last window so that " loading another buffer (especially of a different filetype) in the same " window doesn't highlight th previously loaded buffer's diagnostics. - autocmd BufWinLeave call go#lsp#ClearDiagnosticMatches() + autocmd BufWinLeave call go#lsp#ClearDiagnosticHighlights() " clear diagnostics when a new buffer is loaded in the window so that the " previous buffer's diagnostcs aren't used. - autocmd BufWinEnter call go#lsp#ClearDiagnosticMatches() + autocmd BufWinEnter call go#lsp#ClearDiagnosticHighlights() autocmd BufEnter \ if go#config#AutodetectGopath() && !exists('b:old_gopath') From e427e96dfdb60523a7ca6d90fbc841ecce509592 Mon Sep 17 00:00:00 2001 From: Billie Cleek Date: Wed, 15 Jan 2020 17:24:54 -0800 Subject: [PATCH 4/4] util: use byte2line to highlight when possible --- autoload/go/util.vim | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/autoload/go/util.vim b/autoload/go/util.vim index a1a36053d7..89e8dc583b 100644 --- a/autoload/go/util.vim +++ b/autoload/go/util.vim @@ -611,25 +611,19 @@ function! go#util#HighlightPositions(group, pos) abort " specify end line and column if needed. let l:line = getline(l:pos[0]) - " TODO(bc): use line2byte and byte2line to get the end position more - " efficiently once https://github.com/vim/vim/issues/5334 is resolved. - " e.g.: - " " l:max is the 1-based index within the buffer of the first character after l:pos. - " let l:max = line2byte(l:pos[0]) + l:pos[1] + l:pos[2] - 1 - " let l:end_lnum = byte2line(l:max) - - " "echom printf('processing (%s)', string(l:pos)) - " if l:pos[0] != l:end_lnum - " let l:end_col = l:max - line2byte(l:end_lnum) - " "echom string(l:pos) - " "echom printf('l:end_col = %d - %d = %d', l:max, line2byte(l:end_lnum), l:end_col) - " "echom printf('line2byte(l:pos[0]) = %d, l:pos[0] = %d, l:end_col = %d, l:end_lnum = %d', line2byte(l:pos[0]), l:pos[0], l:end_col, l:end_lnum) - " let l:prop = {'type': a:group, 'end_lnum': l:end_lnum, 'end_col': l:end_col} - if l:pos[1] + l:pos[2] - 1 > len(l:line) - " l:max is the 1-based index within the buffer of the first character - " after l:pos. - let l:max = line2byte(l:pos[0]) + l:pos[1] + l:pos[2] - 1 - + " l:max is the 1-based index within the buffer of the first character after l:pos. + let l:max = line2byte(l:pos[0]) + l:pos[1] + l:pos[2] - 1 + + if has('patch-8.2.115') + " Use byte2line as long as 8.2.115 (which resolved + " https://github.com/vim/vim/issues/5334) is available. + let l:end_lnum = byte2line(l:max) + + if l:pos[0] != l:end_lnum + let l:end_col = l:max - line2byte(l:end_lnum) + let l:prop = {'type': a:group, 'end_lnum': l:end_lnum, 'end_col': l:end_col} + endif + elseif l:pos[1] + l:pos[2] - 1 > len(l:line) let l:end_lnum = l:pos[0] let l:end_col = l:pos[1] + l:pos[2] - 1 while line2byte(l:end_lnum+1) < l:max