+let s:use_vim_textprops = lsp#utils#_has_textprops() && !has('nvim')
+let s:prop_id = 11
+
+function! lsp#internal#document_highlight#_enable() abort
+ " don't event bother registering if the feature is disabled
+ if !g:lsp_document_highlight_enabled | return | endif
+
+ " Highlight group for references
+ if !hlexists('lspReference')
+ highlight link lspReference CursorColumn
+ endif
+
+ " Note:
+ " - update highlight references when CusorMoved or CursorHold
+ " - clear highlights when InsertEnter or BufLeave
+ " - debounce highlight requests
+ " - automatically switch to latest highlight request via switchMap()
+ " - cancel highlight request via takeUntil() when BufLeave
+ let s:Dispose = lsp#callbag#pipe(
+ \ lsp#callbag#merge(
+ \ lsp#callbag#fromEvent(['CursorMoved', 'CursorHold']),
+ \ lsp#callbag#pipe(
+ \ lsp#callbag#fromEvent(['InsertEnter', 'BufLeave']),
+ \ lsp#callbag#tap({_ -> s:clear_highlights() }),
+ \ )
+ \ ),
+ \ lsp#callbag#filter({_ -> g:lsp_document_highlight_enabled }),
+ \ lsp#callbag#debounceTime(g:lsp_document_highlight_delay),
+ \ lsp#callbag#map({_->{'bufnr': bufnr('%'), 'curpos': getcurpos()[0:2], 'changedtick': b:changedtick }}),
+ \ lsp#callbag#distinctUntilChanged({a,b -> a['bufnr'] == b['bufnr'] && a['curpos'] == b['curpos'] && a['changedtick'] == b['changedtick']}),
+ \ lsp#callbag#filter({_->mode() is# 'n' && getbufvar(bufnr('%'), '&buftype') !=# 'terminal' }),
+ \ lsp#callbag#switchMap({_->
+ \ lsp#callbag#pipe(
+ \ s:send_highlight_request(),
+ \ lsp#callbag#materialize(),
+ \ lsp#callbag#filter({x->lsp#callbag#isNextNotification(x)}),
+ \ lsp#callbag#map({x->x['value']}),
+ \ lsp#callbag#takeUntil(
+ \ lsp#callbag#fromEvent('BufLeave')
+ \ )
+ \ )
+ \ }),
+ \ lsp#callbag#filter({_->mode() is# 'n'}),
+ \ lsp#callbag#subscribe({x->s:set_highlights(x)}),
+ \)
+endfunction
+
+function! lsp#internal#document_highlight#_disable() abort
+ if exists('s:Dispose')
+ call s:Dispose()
+ unlet s:Dispose
+ endif
+endfunction
+
+function! s:send_highlight_request() abort
+ let l:capability = 'lsp#capabilities#has_document_highlight_provider(v:val)'
+ let l:servers = filter(lsp#get_allowed_servers(), l:capability)
+
+ if empty(l:servers)
+ return lsp#callbag#empty()
+ endif
+
+ return lsp#request(l:servers[0], {
+ \ 'method': 'textDocument/documentHighlight',
+ \ 'params': {
+ \ 'textDocument': lsp#get_text_document_identifier(),
+ \ 'position': lsp#get_position(),
+ \ },
+ \ })
+endfunction
+
+function! s:set_highlights(data) abort
+ let l:bufnr = bufnr('%')
+
+ call s:clear_highlights()
+
+ if mode() !=# 'n' | return | endif
+
+ if lsp#client#is_error(a:data['response']) | return | endif
+
+ " Get references from the response
+ let l:reference_list = a:data['response']['result']
+ if empty(l:reference_list)
+ return
+ endif
+
+ " Convert references to vim positions
+ let l:position_list = []
+ for l:reference in l:reference_list
+ call extend(l:position_list, lsp#utils#range#lsp_to_vim(l:bufnr, l:reference['range']))
+ endfor
+
+ call sort(l:position_list, function('s:compare_positions'))
+
+ " Ignore response if the cursor is not over a reference anymore
+ if s:in_reference(l:position_list) == -1 | return | endif
+
+ " Store references
+ if s:use_vim_textprops
+ let b:lsp_reference_positions = l:position_list
+ let b:lsp_reference_matches = []
+ else
+ let w:lsp_reference_positions = l:position_list
+ let w:lsp_reference_matches = []
+ endif
+
+ " Apply highlights to the buffer
+ call s:init_reference_highlight(l:bufnr)
+ if s:use_vim_textprops
+ for l:position in l:position_list
+ try
+ " TODO: need to check for valid range before calling prop_add
+ " See https://github.com/prabirshrestha/vim-lsp/pull/721
+ silent! call prop_add(l:position[0], l:position[1], {
+ \ 'id': s:prop_id,
+ \ 'bufnr': l:bufnr,
+ \ 'length': l:position[2],
+ \ 'type': 'vim-lsp-reference-highlight'})
+ call add(b:lsp_reference_matches, l:position[0])
+ catch
+ call lsp#log('document_highlight', 'set_highlights', v:exception, v:throwpoint)
+ endtry
+ endfor
+ else
+ for l:position in l:position_list
+ let l:match = matchaddpos('lspReference', [l:position], -5)
+ call add(w:lsp_reference_matches, l:match)
+ endfor
+ endif
+endfunction
+
+function! s:clear_highlights() abort
+ if s:use_vim_textprops
+ if exists('b:lsp_reference_matches')
+ let l:bufnr = bufnr('%')
+ for l:line in b:lsp_reference_matches
+ silent! call prop_remove(
+ \ {'id': s:prop_id,
+ \ 'bufnr': l:bufnr,
+ \ 'all': v:true}, l:line)
+ endfor
+ unlet b:lsp_reference_matches
+ unlet b:lsp_reference_positions
+ endif
+ else
+ if exists('w:lsp_reference_matches')
+ for l:match in w:lsp_reference_matches
+ silent! call matchdelete(l:match)
+ endfor
+ unlet w:lsp_reference_matches
+ unlet w:lsp_reference_positions
+ endif
+ endif
+endfunction
+
+" Compare two positions
+function! s:compare_positions(p1, p2) abort
+ let l:line_1 = a:p1[0]
+ let l:line_2 = a:p2[0]
+ if l:line_1 != l:line_2
+ return l:line_1 > l:line_2 ? 1 : -1
+ endif
+ let l:col_1 = a:p1[1]
+ let l:col_2 = a:p2[1]
+ return l:col_1 - l:col_2
+endfunction
+
+" If the cursor is over a reference, return its index in
+" the array. Otherwise, return -1.
+function! s:in_reference(reference_list) abort
+ let l:line = line('.')
+ let l:column = col('.')
+ let l:index = 0
+ for l:position in a:reference_list
+ if l:line == l:position[0] &&
+ \ l:column >= l:position[1] &&
+ \ l:column < l:position[1] + l:position[2]
+ return l:index
+ endif
+ let l:index += 1
+ endfor
+ return -1
+endfunction
+
+function! s:init_reference_highlight(buf) abort
+ if s:use_vim_textprops
+ let l:props = {
+ \ 'bufnr': a:buf,
+ \ 'highlight': 'lspReference',
+ \ 'combine': v:true,
+ \ 'priority': lsp#internal#textprop#priority('document_highlight')
+ \ }
+ if prop_type_get('vim-lsp-reference-highlight', { 'bufnr': a:buf }) == {}
+ call prop_type_add('vim-lsp-reference-highlight', l:props)
+ endif
+ endif
+endfunction
+
+" Cyclically move between references by `offset` occurrences.
+function! lsp#internal#document_highlight#jump(offset) abort
+ if s:use_vim_textprops && !exists('b:lsp_reference_positions') ||
+ \ !s:use_vim_textprops && !exists('w:lsp_reference_positions')
+ echohl WarningMsg
+ echom 'References not available'
+ echohl None
+ return
+ endif
+
+ " Get index of reference under cursor
+ let l:index = s:use_vim_textprops ? s:in_reference(b:lsp_reference_positions) : s:in_reference(w:lsp_reference_positions)
+ if l:index < 0
+ return
+ endif
+
+ let l:n = s:use_vim_textprops ? len(b:lsp_reference_positions) : len(w:lsp_reference_positions)
+ let l:index += a:offset
+
+ " Show a message when reaching TOP/BOTTOM of the file
+ if l:index < 0
+ echohl WarningMsg
+ echom 'search hit TOP, continuing at BOTTOM'
+ echohl None
+ elseif l:index >= (s:use_vim_textprops ? len(b:lsp_reference_positions) : len(w:lsp_reference_positions))
+ echohl WarningMsg
+ echom 'search hit BOTTOM, continuing at TOP'
+ echohl None
+ endif
+
+ " Wrap index
+ if l:index < 0 || l:index >= (s:use_vim_textprops ? len(b:lsp_reference_positions) : len(w:lsp_reference_positions))
+ let l:index = (l:index % l:n + l:n) % l:n
+ endif
+
+ " Jump
+ let l:target = (s:use_vim_textprops ? b:lsp_reference_positions : w:lsp_reference_positions)[l:index][0:1]
+ normal! m`
+ call cursor(l:target[0], l:target[1])
+endfunction