]> Sergey Matveev's repositories - vim-lsp.git/blob - autoload/lsp/handlers.vim
Dict key access optimizations
[vim-lsp.git] / autoload / lsp / handlers.vim
1 vim9script
2
3 # Handlers for messages from the LSP server
4 # Refer to https://microsoft.github.io/language-server-protocol/specification
5 # for the Language Server Protocol (LSP) specification.
6
7 import './util.vim'
8 import './diag.vim'
9 import './textedit.vim'
10
11 # Process various reply messages from the LSP server
12 export def ProcessReply(lspserver: dict<any>, req: dict<any>, reply: dict<any>): void
13   util.ErrMsg($'Unsupported reply received from LSP server: {reply->string()} for request: {req->string()}')
14 enddef
15
16 # process a diagnostic notification message from the LSP server
17 # Notification: textDocument/publishDiagnostics
18 # Param: PublishDiagnosticsParams
19 def ProcessDiagNotif(lspserver: dict<any>, reply: dict<any>): void
20   var params = reply.params
21   diag.DiagNotification(lspserver, params.uri, params.diagnostics)
22 enddef
23
24 # Convert LSP message type to a string
25 def LspMsgTypeToString(lspMsgType: number): string
26   var msgStrMap: list<string> = ['', 'Error', 'Warning', 'Info', 'Log']
27   var mtype: string = 'Log'
28   if lspMsgType > 0 && lspMsgType < 5
29     mtype = msgStrMap[lspMsgType]
30   endif
31   return mtype
32 enddef
33
34 # process a show notification message from the LSP server
35 # Notification: window/showMessage
36 # Param: ShowMessageParams
37 def ProcessShowMsgNotif(lspserver: dict<any>, reply: dict<any>)
38   var msgType = reply.params.type
39   if msgType >= 4
40     # ignore log messages from the LSP server (too chatty)
41     # TODO: Add a configuration to control the message level that will be
42     # displayed. Also store these messages and provide a command to display
43     # them.
44     return
45   endif
46   if msgType == 1
47     util.ErrMsg($'Lsp({lspserver.name}) {reply.params.message}')
48   elseif msgType == 2
49     util.WarnMsg($'Lsp({lspserver.name}) {reply.params.message}')
50   elseif msgType == 3
51     util.InfoMsg($'Lsp({lspserver.name}) {reply.params.message}')
52   endif
53 enddef
54
55 # process a log notification message from the LSP server
56 # Notification: window/logMessage
57 # Param: LogMessageParams
58 def ProcessLogMsgNotif(lspserver: dict<any>, reply: dict<any>)
59   var params = reply.params
60   var mtype = LspMsgTypeToString(params.type)
61   lspserver.addMessage(mtype, params.message)
62 enddef
63
64 # process the log trace notification messages
65 # Notification: $/logTrace
66 # Param: LogTraceParams
67 def ProcessLogTraceNotif(lspserver: dict<any>, reply: dict<any>)
68   lspserver.addMessage('trace', reply.params.message)
69 enddef
70
71 # process unsupported notification messages
72 def ProcessUnsupportedNotif(lspserver: dict<any>, reply: dict<any>)
73   util.ErrMsg($'Unsupported notification message received from the LSP server ({lspserver.path}), message = {reply->string()}')
74 enddef
75
76 # per-filetype private map inside to record if ntf once or not
77 var ftypeNtfOnceMap: dict<bool> = {}
78 # process unsupported notification messages but only notify once
79 def ProcessUnsupportedNotifOnce(lspserver: dict<any>, reply: dict<any>)
80   if !ftypeNtfOnceMap->get(&ft, v:false)
81         ProcessUnsupportedNotif(lspserver, reply)
82         ftypeNtfOnceMap->extend({[&ft]: v:true})
83   endif
84 enddef
85
86 # process notification messages from the LSP server
87 export def ProcessNotif(lspserver: dict<any>, reply: dict<any>): void
88   var lsp_notif_handlers: dict<func> =
89     {
90       'window/showMessage': ProcessShowMsgNotif,
91       'window/logMessage': ProcessLogMsgNotif,
92       'textDocument/publishDiagnostics': ProcessDiagNotif,
93       '$/logTrace': ProcessLogTraceNotif,
94       'telemetry/event': ProcessUnsupportedNotifOnce,
95     }
96
97   # Explicitly ignored notification messages
98   var lsp_ignored_notif_handlers: list<string> =
99     [
100       '$/progress',
101       '$/status/report',
102       '$/status/show',
103       # PHP intelephense server sends the "indexingStarted" and
104       # "indexingEnded" notifications which is not in the LSP specification.
105       'indexingStarted',
106       'indexingEnded',
107       # Java language server sends the 'language/status' notification which is
108       # not in the LSP specification.
109       'language/status',
110       # Typescript language server sends the '$/typescriptVersion'
111       # notification which is not in the LSP specification.
112       '$/typescriptVersion',
113       # Dart language server sends the '$/analyzerStatus' notification which
114       # is not in the LSP specification.
115       '$/analyzerStatus'
116     ]
117
118   if lsp_notif_handlers->has_key(reply.method)
119     lsp_notif_handlers[reply.method](lspserver, reply)
120   elseif lspserver.customNotificationHandlers->has_key(reply.method)
121     lspserver.customNotificationHandlers[reply.method](lspserver, reply)
122   elseif lsp_ignored_notif_handlers->index(reply.method) == -1
123     util.ErrMsg($'Unsupported notification received from LSP server {reply->string()}')
124   endif
125 enddef
126
127 # process the workspace/applyEdit LSP server request
128 # Request: "workspace/applyEdit"
129 # Param: ApplyWorkspaceEditParams
130 def ProcessApplyEditReq(lspserver: dict<any>, request: dict<any>)
131   # interface ApplyWorkspaceEditParams
132   if !request->has_key('params')
133     return
134   endif
135   var workspaceEditParams: dict<any> = request.params
136   if workspaceEditParams->has_key('label')
137     util.InfoMsg($'Workspace edit {workspaceEditParams.label}')
138   endif
139   textedit.ApplyWorkspaceEdit(workspaceEditParams.edit)
140   # TODO: Need to return the proper result of the edit operation
141   lspserver.sendResponse(request, {applied: true}, {})
142 enddef
143
144 # process the workspace/workspaceFolders LSP server request
145 # Request: "workspace/workspaceFolders"
146 # Param: none
147 def ProcessWorkspaceFoldersReq(lspserver: dict<any>, request: dict<any>)
148   if !lspserver->has_key('workspaceFolders')
149     lspserver.sendResponse(request, null, {})
150     return
151   endif
152   if lspserver.workspaceFolders->empty()
153     lspserver.sendResponse(request, [], {})
154   else
155     lspserver.sendResponse(request,
156           \ lspserver.workspaceFolders->copy()->map('{name: v:val->fnamemodify(":t"), uri: util.LspFileToUri(v:val)}'),
157           \ {})
158   endif
159 enddef
160
161 # process the workspace/configuration LSP server request
162 # Request: "workspace/configuration"
163 # Param: ConfigurationParams
164 def ProcessWorkspaceConfiguration(lspserver: dict<any>, request: dict<any>)
165   var items = request.params.items
166   var response = items->map((_, item) => lspserver.workspaceConfigGet(item))
167   lspserver.sendResponse(request, response, {})
168 enddef
169
170 # process the window/workDoneProgress/create LSP server request
171 # Request: "window/workDoneProgress/create"
172 # Param: none
173 def ProcessWorkDoneProgressCreate(lspserver: dict<any>, request: dict<any>)
174   lspserver.sendResponse(request, {}, {})
175 enddef
176
177 # process the client/registerCapability LSP server request
178 # Request: "client/registerCapability"
179 # Param: RegistrationParams
180 def ProcessClientRegisterCap(lspserver: dict<any>, request: dict<any>)
181   lspserver.sendResponse(request, {}, {})
182 enddef
183
184 # process the client/unregisterCapability LSP server request
185 # Request: "client/unregisterCapability"
186 # Param: UnregistrationParams
187 def ProcessClientUnregisterCap(lspserver: dict<any>, request: dict<any>)
188   lspserver.sendResponse(request, {}, {})
189 enddef
190
191 # process a request message from the server
192 export def ProcessRequest(lspserver: dict<any>, request: dict<any>)
193   var lspRequestHandlers: dict<func> =
194     {
195       'client/registerCapability': ProcessClientRegisterCap,
196       'client/unregisterCapability': ProcessClientUnregisterCap,
197       'window/workDoneProgress/create': ProcessWorkDoneProgressCreate,
198       'workspace/applyEdit': ProcessApplyEditReq,
199       'workspace/configuration': ProcessWorkspaceConfiguration,
200       'workspace/workspaceFolders': ProcessWorkspaceFoldersReq
201       # TODO: Handle the following requests from the server:
202       #     workspace/codeLens/refresh
203       #     workspace/diagnostic/refresh
204       #     workspace/inlayHint/refresh
205       #     workspace/inlineValue/refresh
206       #     workspace/semanticTokens/refresh
207     }
208
209   # Explicitly ignored requests
210   var lspIgnoredRequestHandlers: list<string> =
211     [
212       # Eclipse java language server sends the
213       # 'workspace/executeClientCommand' request (to reload bundles) which is
214       # not in the LSP specification.
215       'workspace/executeClientCommand',
216     ]
217
218   if lspRequestHandlers->has_key(request.method)
219     lspRequestHandlers[request.method](lspserver, request)
220   elseif lspserver.customRequestHandlers->has_key(request.method)
221     lspserver.customRequestHandlers[request.method](lspserver, request)
222   elseif lspIgnoredRequestHandlers->index(request.method) == -1
223     util.ErrMsg($'Unsupported request message received from the LSP server ({lspserver.path}), message = {request->string()}')
224   endif
225 enddef
226
227 # process one or more LSP server messages
228 export def ProcessMessages(lspserver: dict<any>): void
229   var idx: number
230   var len: number
231   var content: string
232   var msg: dict<any>
233   var req: dict<any>
234
235   msg = lspserver.data
236   if msg->has_key('result') || msg->has_key('error')
237     # response message from the server
238     req = lspserver.requests->get(msg.id->string(), {})
239     if !req->empty()
240       # Remove the corresponding stored request message
241       lspserver.requests->remove(msg.id->string())
242
243       if msg->has_key('result')
244         lspserver.processReply(req, msg)
245       else
246         # request failed
247         var emsg: string = msg.error.message
248         emsg ..= $', code = {msg.error.code}'
249         if msg.error->has_key('data')
250           emsg ..= $', data = {msg.error.data->string()}'
251         endif
252         util.ErrMsg($'request {req.method} failed ({emsg})')
253       endif
254     endif
255   elseif msg->has_key('id') && msg->has_key('method')
256     # request message from the server
257     lspserver.processRequest(msg)
258   elseif msg->has_key('method')
259     # notification message from the server
260     lspserver.processNotif(msg)
261   else
262     util.ErrMsg($'Unsupported message ({msg->string()})')
263   endif
264 enddef
265
266 # vim: tabstop=8 shiftwidth=2 softtabstop=2