-
Notifications
You must be signed in to change notification settings - Fork 78
/
reference.vim
162 lines (149 loc) · 5.15 KB
/
reference.vim
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
function! lsc#reference#goToDefinition() abort
call lsc#file#flushChanges()
call lsc#server#userCall('textDocument/definition',
\ lsc#params#documentPosition(),
\ lsc#util#gateResult('GoToDefinition', function('<SID>GoToDefinition')))
endfunction
function! s:GoToDefinition(result) abort
if type(a:result) == type(v:null) ||
\ (type(a:result) == v:t_list && len(a:result) == 0)
call lsc#message#error('No definition found')
return
endif
if type(a:result) == type([])
let location = a:result[0]
else
let location = a:result
endif
let file = lsc#uri#documentPath(location.uri)
let line = location.range.start.line + 1
let character = location.range.start.character + 1
if exists('*gettagstack') && exists('*settagstack')
let from = [bufnr('%'), line('.'), col('.'), 0]
let tagname = expand('<cword>')
let winid = win_getid()
call settagstack(winid, {'items': [{'from': from, 'tagname': tagname}]}, 'a')
call settagstack(winid, {'curidx': len(gettagstack(winid)['items']) + 1})
endif
call s:goTo(file, line, character)
endfunction
function! lsc#reference#findReferences() abort
call lsc#file#flushChanges()
let params = lsc#params#documentPosition()
let params.context = {'includeDeclaration': v:true}
call lsc#server#userCall('textDocument/references', params,
\ function('<SID>setQuickFixLocations', ['references']))
endfunction
function! lsc#reference#findImplementations() abort
call lsc#file#flushChanges()
call lsc#server#userCall('textDocument/implementation',
\ lsc#params#documentPosition(),
\ function('<SID>setQuickFixLocations', ['implementations']))
endfunction
function! s:setQuickFixLocations(label, results) abort
if empty(a:results)
call lsc#message#show('No '.a:label.' found')
return
endif
call map(a:results, {_, ref -> s:QuickFixItem(ref)})
call sort(a:results, 'lsc#util#compareQuickFixItems')
call setqflist(a:results)
copen
endfunction
" Convert an LSP Location to a item suitable for the vim quickfix list.
"
" Both representations are dictionaries.
"
" Location:
" 'uri': file:// URI
" 'range': {'start': {'line', 'character'}, 'end': {'line', 'character'}}
"
" QuickFix Item: (as used)
" 'filename': file path if file is not open
" 'lnum': line number
" 'col': column number
" 'text': The content of the referenced line
"
" LSP line and column are zero-based, vim is one-based.
function! s:QuickFixItem(location) abort
let item = {'lnum': a:location.range.start.line + 1,
\ 'col': a:location.range.start.character + 1}
let file_path = lsc#uri#documentPath(a:location.uri)
let item.filename = fnamemodify(file_path, ':.')
let bufnr = lsc#file#bufnr(file_path)
if bufnr != -1 && bufloaded(bufnr)
let item.text = getbufline(bufnr, item.lnum)[0]
else
let item.text = readfile(file_path, '', item.lnum)[item.lnum - 1]
endif
return item
endfunction
function! s:goTo(file, line, character) abort
if a:file != lsc#file#fullPath()
let relative_path = fnamemodify(a:file, ':~:.')
exec 'edit '.relative_path
" 'edit' already left a jump
call cursor(a:line, a:character)
redraw
else
" Move with 'G' to ensure a jump is left
exec 'normal! '.a:line.'G'.a:character.'|'
endif
endfunction
function! lsc#reference#hover() abort
call lsc#file#flushChanges()
let params = lsc#params#documentPosition()
call lsc#server#userCall('textDocument/hover', params,
\ function('<SID>showHover'))
endfunction
function! s:showHover(result) abort
if empty(a:result) || empty(a:result.contents)
echom 'No hover information'
return
endif
let contents = a:result.contents
if type(contents) == v:t_list
let contents = contents[0]
endif
if type(contents) == v:t_dict
let contents = contents.value
endif
let lines = split(contents, "\n")
call lsc#util#displayAsPreview(lines, function('lsc#util#noop'))
endfunction
" Request a list of symbols in the current document and populate the quickfix
" list.
function! lsc#reference#documentSymbols() abort
call lsc#file#flushChanges()
call lsc#server#userCall('textDocument/documentSymbol',
\ lsc#params#textDocument(),
\ function('<SID>setQuickFixSymbols'))
endfunction
function! s:setQuickFixSymbols(results) abort
if empty(a:results)
call lsc#message#show('No symbols found')
return
endif
call map(a:results, {_, symbol -> lsc#convert#quickFixSymbol(symbol)})
call sort(a:results, 'lsc#util#compareQuickFixItems')
call setqflist(a:results)
copen
endfunction
" If the server supports `textDocument/documentHighlight` and they are enabled,
" use the active highlights to move the cursor to the next or previous referene
" in the same document to the symbol under the cursor.
function! lsc#reference#findNext(direction) abort
if exists('w:lsc_references')
let idx = lsc#cursor#isInReference(w:lsc_references)
if idx != -1 &&
\ idx + a:direction >= 0 &&
\ idx + a:direction < len(w:lsc_references)
let target = w:lsc_references[idx + a:direction].ranges[0][0:1]
endif
endif
if !exists('l:target')
return
endif
" Move with 'G' to ensure a jump is left
exec 'normal! '.target[0].'G'.target[1].'|'
endfunction