]> Sergey Matveev's repositories - vim-lsp.git/blob - autoload/lsp/handlers.vim
fix: ignore `eslint/status` and `taplo/didChangeSchemaAssociation`
[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.WarnMsg($'Unsupported notification message received from the LSP server ({lspserver.name}), message = {reply->string()}')
74 enddef
75
76 # Dict to process telemetry notification messages only once per filetype
77 var telemetryProcessed: dict<bool> = {}
78 # process unsupported notification messages only once
79 def ProcessUnsupportedNotifOnce(lspserver: dict<any>, reply: dict<any>)
80   if !telemetryProcessed->get(&ft, false)
81     ProcessUnsupportedNotif(lspserver, reply)
82     telemetryProcessed->extend({[&ft]: 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       # pyright language server notifications
117       'pyright/beginProgress',
118       'pyright/reportProgress',
119       'pyright/endProgress',
120       'eslint/status',
121       'taplo/didChangeSchemaAssociation'
122     ]
123
124   if lsp_notif_handlers->has_key(reply.method)
125     lsp_notif_handlers[reply.method](lspserver, reply)
126   elseif lspserver.customNotificationHandlers->has_key(reply.method)
127     lspserver.customNotificationHandlers[reply.method](lspserver, reply)
128   elseif lsp_ignored_notif_handlers->index(reply.method) == -1
129     ProcessUnsupportedNotif(lspserver, reply)
130   endif
131 enddef
132
133 # process the workspace/applyEdit LSP server request
134 # Request: "workspace/applyEdit"
135 # Param: ApplyWorkspaceEditParams
136 def ProcessApplyEditReq(lspserver: dict<any>, request: dict<any>)
137   # interface ApplyWorkspaceEditParams
138   if !request->has_key('params')
139     return
140   endif
141   var workspaceEditParams: dict<any> = request.params
142   if workspaceEditParams->has_key('label')
143     util.InfoMsg($'Workspace edit {workspaceEditParams.label}')
144   endif
145   textedit.ApplyWorkspaceEdit(workspaceEditParams.edit)
146   # TODO: Need to return the proper result of the edit operation
147   lspserver.sendResponse(request, {applied: true}, {})
148 enddef
149
150 # process the workspace/workspaceFolders LSP server request
151 # Request: "workspace/workspaceFolders"
152 # Param: none
153 def ProcessWorkspaceFoldersReq(lspserver: dict<any>, request: dict<any>)
154   if !lspserver->has_key('workspaceFolders')
155     lspserver.sendResponse(request, null, {})
156     return
157   endif
158   if lspserver.workspaceFolders->empty()
159     lspserver.sendResponse(request, [], {})
160   else
161     lspserver.sendResponse(request,
162           \ lspserver.workspaceFolders->copy()->map('{name: v:val->fnamemodify(":t"), uri: util.LspFileToUri(v:val)}'),
163           \ {})
164   endif
165 enddef
166
167 # process the workspace/configuration LSP server request
168 # Request: "workspace/configuration"
169 # Param: ConfigurationParams
170 def ProcessWorkspaceConfiguration(lspserver: dict<any>, request: dict<any>)
171   var items = request.params.items
172   var response = items->map((_, item) => lspserver.workspaceConfigGet(item))
173   lspserver.sendResponse(request, response, {})
174 enddef
175
176 # process the window/workDoneProgress/create LSP server request
177 # Request: "window/workDoneProgress/create"
178 # Param: none
179 def ProcessWorkDoneProgressCreate(lspserver: dict<any>, request: dict<any>)
180   lspserver.sendResponse(request, {}, {})
181 enddef
182
183 # process the client/registerCapability LSP server request
184 # Request: "client/registerCapability"
185 # Param: RegistrationParams
186 def ProcessClientRegisterCap(lspserver: dict<any>, request: dict<any>)
187   lspserver.sendResponse(request, {}, {})
188 enddef
189
190 # process the client/unregisterCapability LSP server request
191 # Request: "client/unregisterCapability"
192 # Param: UnregistrationParams
193 def ProcessClientUnregisterCap(lspserver: dict<any>, request: dict<any>)
194   lspserver.sendResponse(request, {}, {})
195 enddef
196
197 # process a request message from the server
198 export def ProcessRequest(lspserver: dict<any>, request: dict<any>)
199   var lspRequestHandlers: dict<func> =
200     {
201       'client/registerCapability': ProcessClientRegisterCap,
202       'client/unregisterCapability': ProcessClientUnregisterCap,
203       'window/workDoneProgress/create': ProcessWorkDoneProgressCreate,
204       'workspace/applyEdit': ProcessApplyEditReq,
205       'workspace/configuration': ProcessWorkspaceConfiguration,
206       'workspace/workspaceFolders': ProcessWorkspaceFoldersReq
207       # TODO: Handle the following requests from the server:
208       #     workspace/codeLens/refresh
209       #     workspace/diagnostic/refresh
210       #     workspace/inlayHint/refresh
211       #     workspace/inlineValue/refresh
212       #     workspace/semanticTokens/refresh
213     }
214
215   # Explicitly ignored requests
216   var lspIgnoredRequestHandlers: list<string> =
217     [
218       # Eclipse java language server sends the
219       # 'workspace/executeClientCommand' request (to reload bundles) which is
220       # not in the LSP specification.
221       'workspace/executeClientCommand',
222     ]
223
224   if lspRequestHandlers->has_key(request.method)
225     lspRequestHandlers[request.method](lspserver, request)
226   elseif lspserver.customRequestHandlers->has_key(request.method)
227     lspserver.customRequestHandlers[request.method](lspserver, request)
228   elseif lspIgnoredRequestHandlers->index(request.method) == -1
229     util.ErrMsg($'Unsupported request message received from the LSP server ({lspserver.name}), message = {request->string()}')
230   endif
231 enddef
232
233 # process one or more LSP server messages
234 export def ProcessMessages(lspserver: dict<any>): void
235   var idx: number
236   var len: number
237   var content: string
238   var msg: dict<any>
239   var req: dict<any>
240
241   msg = lspserver.data
242   if msg->has_key('result') || msg->has_key('error')
243     # response message from the server
244     req = lspserver.requests->get(msg.id->string(), {})
245     if !req->empty()
246       # Remove the corresponding stored request message
247       lspserver.requests->remove(msg.id->string())
248
249       if msg->has_key('result')
250         lspserver.processReply(req, msg)
251       else
252         # request failed
253         var emsg: string = msg.error.message
254         emsg ..= $', code = {msg.error.code}'
255         if msg.error->has_key('data')
256           emsg ..= $', data = {msg.error.data->string()}'
257         endif
258         util.ErrMsg($'request {req.method} failed ({emsg})')
259       endif
260     endif
261   elseif msg->has_key('id') && msg->has_key('method')
262     # request message from the server
263     lspserver.processRequest(msg)
264   elseif msg->has_key('method')
265     # notification message from the server
266     lspserver.processNotif(msg)
267   else
268     util.ErrMsg($'Unsupported message ({msg->string()})')
269   endif
270 enddef
271
272 # vim: tabstop=8 shiftwidth=2 softtabstop=2