]> Sergey Matveev's repositories - vim-lsp.git/blob - autoload/lsp/inlayhints.vim
Skip displaying inlay hints if it is disabled for a language server
[vim-lsp.git] / autoload / lsp / inlayhints.vim
1 vim9script
2
3 # Functions for dealing with inlay hints
4
5 import './util.vim'
6 import './buffer.vim' as buf
7 import './options.vim' as opt
8
9 # Initialize the highlight group and the text property type used for
10 # inlay hints.
11 export def InitOnce()
12   hlset([
13     {name: 'LspInlayHintsType', default: true, linksto: 'Label'},
14     {name: 'LspInlayHintsParam', default: true, linksto: 'Conceal'}
15   ])
16   prop_type_add('LspInlayHintsType', {highlight: 'LspInlayHintsType'})
17   prop_type_add('LspInlayHintsParam', {highlight: 'LspInlayHintsParam'})
18
19   autocmd_add([{group: 'LspOptionsChanged',
20                 event: 'User',
21                 pattern: '*',
22                 cmd: 'LspInlayHintsOptionsChanged()'}])
23 enddef
24
25 # Clear all the inlay hints text properties in the current buffer
26 def InlayHintsClear(bnr: number)
27   prop_remove({type: 'LspInlayHintsType', bufnr: bnr, all: true})
28   prop_remove({type: 'LspInlayHintsParam', bufnr: bnr, all: true})
29 enddef
30
31 # LSP inlay hints reply message handler
32 export def InlayHintsReply(lspserver: dict<any>, bnr: number, inlayHints: any)
33   if inlayHints->empty()
34     return
35   endif
36
37   InlayHintsClear(bnr)
38
39   if mode() != 'n'
40     # Update inlay hints only in normal mode
41     return
42   endif
43
44   for hint in inlayHints
45     var label = ''
46     if hint.label->type() == v:t_list
47       label = hint.label->copy()->map((_, v) => v.value)->join(', ')
48     else
49       label = hint.label
50     endif
51
52     var kind = hint->has_key('kind') ? hint.kind->string() : '1'
53     try
54       lspserver.decodePosition(bnr, hint.position)
55       var byteIdx = util.GetLineByteFromPos(bnr, hint.position)
56       if kind == "'type'" || kind == '1'
57         prop_add(hint.position.line + 1, byteIdx + 1,
58           {type: 'LspInlayHintsType', text: label, bufnr: bnr})
59       elseif kind == "'parameter'" || kind == '2'
60         prop_add(hint.position.line + 1, byteIdx + 1,
61           {type: 'LspInlayHintsParam', text: label, bufnr: bnr})
62       endif
63     catch /E966\|E964/ # Invalid lnum | Invalid col
64       # Inlay hints replies arrive asynchronously and the document might have
65       # been modified in the mean time.  As the reply is stale, ignore invalid
66       # line number and column number errors.
67     endtry
68   endfor
69 enddef
70
71 # Timer callback to display the inlay hints.
72 def InlayHintsTimerCb(lspserver: dict<any>, bnr: number, timerid: number)
73   lspserver.inlayHintsShow(bnr)
74   setbufvar(bnr, 'LspInlayHintsNeedsUpdate', false)
75 enddef
76
77 # Update all the inlay hints.  A timer is used to throttle the updates.
78 def LspInlayHintsUpdate(bnr: number)
79   if !bnr->getbufvar('LspInlayHintsNeedsUpdate', true)
80     return
81   endif
82
83   var timerid = bnr->getbufvar('LspInlayHintsTimer', -1)
84   if timerid != -1
85     timerid->timer_stop()
86     setbufvar(bnr, 'LspInlayHintsTimer', -1)
87   endif
88
89   var lspserver: dict<any> = buf.BufLspServerGet(bnr, 'inlayHint')
90   if lspserver->empty()
91     return
92   endif
93
94   if get(g:, 'LSPTest')
95     # When running tests, update the inlay hints immediately
96     InlayHintsTimerCb(lspserver, bnr, -1)
97   else
98     timerid = timer_start(300, function('InlayHintsTimerCb', [lspserver, bnr]))
99     setbufvar(bnr, 'LspInlayHintsTimer', timerid)
100   endif
101 enddef
102
103 # Text is modified. Need to update the inlay hints.
104 def LspInlayHintsChanged(bnr: number)
105   setbufvar(bnr, 'LspInlayHintsNeedsUpdate', true)
106 enddef
107
108 # Trigger an update of the inlay hints in the current buffer.
109 export def LspInlayHintsUpdateNow(bnr: number)
110   setbufvar(bnr, 'LspInlayHintsNeedsUpdate', true)
111   LspInlayHintsUpdate(bnr)
112 enddef
113
114 # Stop updating the inlay hints.
115 def LspInlayHintsUpdateStop(bnr: number)
116   var timerid = bnr->getbufvar('LspInlayHintsTimer', -1)
117   if timerid != -1
118     timerid->timer_stop()
119     setbufvar(bnr, 'LspInlayHintsTimer', -1)
120   endif
121 enddef
122
123 # Do buffer-local initialization for displaying inlay hints
124 export def BufferInit(lspserver: dict<any>, bnr: number)
125   if !lspserver.isInlayHintProvider && !lspserver.isClangdInlayHintsProvider
126     # no support for inlay hints
127     return
128   endif
129
130   # Inlays hints are disabled
131   if !opt.lspOptions.showInlayHints
132       || !lspserver.featureEnabled('inlayHint')
133     return
134   endif
135
136   var acmds: list<dict<any>> = []
137
138   # Update the inlay hints (if needed) when the cursor is not moved for some
139   # time.
140   acmds->add({bufnr: bnr,
141                 event: ['CursorHold'],
142                 group: 'LspInlayHints',
143                 cmd: $'LspInlayHintsUpdate({bnr})'})
144   # After the text in the current buffer is modified, the inlay hints need to
145   # be updated.
146   acmds->add({bufnr: bnr,
147                 event: ['TextChanged'],
148                 group: 'LspInlayHints',
149                 cmd: $'LspInlayHintsChanged({bnr})'})
150   # Editing a file should trigger an inlay hint update.
151   acmds->add({bufnr: bnr,
152                 event: ['BufReadPost'],
153                 group: 'LspInlayHints',
154                 cmd: $'LspInlayHintsUpdateNow({bnr})'})
155   # Inlay hints need not be updated if a buffer is no longer active.
156   acmds->add({bufnr: bnr,
157                 event: ['BufLeave'],
158                 group: 'LspInlayHints',
159                 cmd: $'LspInlayHintsUpdateStop({bnr})'})
160
161   autocmd_add(acmds)
162 enddef
163
164 # Track the current inlay hints enabled/disabled state.  Used when the
165 # "showInlayHints" option value is changed.
166 var save_showInlayHints = opt.lspOptions.showInlayHints
167
168 # Enable inlay hints.  For all the buffers with an attached language server
169 # that supports inlay hints, refresh the inlay hints.
170 export def InlayHintsEnable()
171   opt.lspOptions.showInlayHints = true
172   for binfo in getbufinfo()
173     var lspservers: list<dict<any>> = buf.BufLspServersGet(binfo.bufnr)
174     if lspservers->empty()
175       continue
176     endif
177     for lspserver in lspservers
178       if !lspserver.featureEnabled('inlayHint')
179           || (!lspserver.isInlayHintProvider &&
180               !lspserver.isClangdInlayHintsProvider)
181         continue
182       endif
183       BufferInit(lspserver, binfo.bufnr)
184       LspInlayHintsUpdateNow(binfo.bufnr)
185     endfor
186   endfor
187   save_showInlayHints = true
188 enddef
189
190 # Disable inlay hints for the current Vim session.  Clear the inlay hints in
191 # all the buffers.
192 export def InlayHintsDisable()
193   opt.lspOptions.showInlayHints = false
194   for binfo in getbufinfo()
195     var lspserver: dict<any> = buf.BufLspServerGet(binfo.bufnr, 'inlayHint')
196     if lspserver->empty()
197       continue
198     endif
199     LspInlayHintsUpdateStop(binfo.bufnr)
200     :silent! autocmd_delete([{bufnr: binfo.bufnr, group: 'LspInlayHints'}])
201     InlayHintsClear(binfo.bufnr)
202   endfor
203   save_showInlayHints = false
204 enddef
205
206 # Some options are changed.  If 'showInlayHints' option is changed, then
207 # either enable or disable inlay hints.
208 export def LspInlayHintsOptionsChanged()
209   if save_showInlayHints && !opt.lspOptions.showInlayHints
210     InlayHintsDisable()
211   elseif !save_showInlayHints && opt.lspOptions.showInlayHints
212     InlayHintsEnable()
213   endif
214 enddef
215
216 # vim: tabstop=8 shiftwidth=2 softtabstop=2