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