@@ -9,18 +9,41 @@ import { sqlmeshLspExec } from '../utilities/sqlmesh/sqlmesh'
9
9
import { err , isErr , ok , Result } from '@bus/result'
10
10
import { getWorkspaceFolders } from '../utilities/common/vscodeapi'
11
11
import { traceError , traceInfo } from '../utilities/common/log'
12
- import { ErrorType } from '../utilities/errors'
12
+ import {
13
+ ErrorType ,
14
+ ErrorTypeGeneric ,
15
+ ErrorTypeInvalidState ,
16
+ ErrorTypeSQLMeshOutdated ,
17
+ } from '../utilities/errors'
13
18
import { CustomLSPMethods } from './custom'
14
19
20
+ type SupportedMethodsState =
21
+ | { type : 'not-fetched' }
22
+ | { type : 'fetched' ; methods : Set < string > }
23
+ // TODO: This state is used when the `sqlmesh/supported_methods` endpoint is
24
+ // not supported by the LSP server. This is in order to be backward compatible
25
+ // with older versions of SQLMesh that do not support this endpoint. At some point
26
+ // we should remove this state and always fetch the supported methods.
27
+ | { type : 'endpoint-not-supported' }
28
+
15
29
let outputChannel : OutputChannel | undefined
16
30
17
31
export class LSPClient implements Disposable {
18
32
private client : LanguageClient | undefined
33
+ /**
34
+ * State to track whether the supported methods have been fetched. These are used to determine if a method is supported
35
+ * by the LSP server and return an error if not.
36
+ */
37
+ private supportedMethodsState : SupportedMethodsState = { type : 'not-fetched' }
19
38
20
39
constructor ( ) {
21
40
this . client = undefined
22
41
}
23
42
43
+ // TODO: This method is used to check if the LSP client has completion capability
44
+ // in order to be backward compatible with older versions of SQLMesh that do not
45
+ // support completion. At some point we should remove this method and always assume
46
+ // that the LSP client has completion capability.
24
47
public hasCompletionCapability ( ) : boolean {
25
48
if ( ! this . client ) {
26
49
traceError ( 'LSP client is not initialized' )
@@ -98,21 +121,123 @@ export class LSPClient implements Disposable {
98
121
if ( this . client ) {
99
122
await this . client . stop ( )
100
123
this . client = undefined
124
+ // Reset supported methods state when the client stops
125
+ this . supportedMethodsState = { type : 'not-fetched' }
101
126
}
102
127
}
103
128
104
129
public async dispose ( ) {
105
130
await this . stop ( )
106
131
}
107
132
133
+ private async fetchSupportedMethods ( ) : Promise < void > {
134
+ if ( ! this . client || this . supportedMethodsState . type !== 'not-fetched' ) {
135
+ return
136
+ }
137
+ try {
138
+ const result = await this . internal_call_custom_method (
139
+ 'sqlmesh/supported_methods' ,
140
+ { } ,
141
+ )
142
+ if ( isErr ( result ) ) {
143
+ traceError ( `Failed to fetch supported methods: ${ result . error } ` )
144
+ this . supportedMethodsState = { type : 'endpoint-not-supported' }
145
+ return
146
+ }
147
+ const methodNames = new Set ( result . value . methods . map ( m => m . name ) )
148
+ this . supportedMethodsState = { type : 'fetched' , methods : methodNames }
149
+ traceInfo (
150
+ `Fetched supported methods: ${ Array . from ( methodNames ) . join ( ', ' ) } ` ,
151
+ )
152
+ } catch {
153
+ // If the supported_methods endpoint doesn't exist, mark it as not supported
154
+ this . supportedMethodsState = { type : 'endpoint-not-supported' }
155
+ traceInfo (
156
+ 'Supported methods endpoint not available, proceeding without validation' ,
157
+ )
158
+ }
159
+ }
160
+
108
161
public async call_custom_method <
162
+ Method extends Exclude <
163
+ CustomLSPMethods [ 'method' ] ,
164
+ 'sqlmesh/supported_methods'
165
+ > ,
166
+ Request extends Extract < CustomLSPMethods , { method : Method } > [ 'request' ] ,
167
+ Response extends Extract < CustomLSPMethods , { method : Method } > [ 'response' ] ,
168
+ > (
169
+ method : Method ,
170
+ request : Request ,
171
+ ) : Promise <
172
+ Result <
173
+ Response ,
174
+ ErrorTypeGeneric | ErrorTypeInvalidState | ErrorTypeSQLMeshOutdated
175
+ >
176
+ > {
177
+ if ( ! this . client ) {
178
+ return err ( {
179
+ type : 'generic' ,
180
+ message : 'LSP client not ready.' ,
181
+ } )
182
+ }
183
+ await this . fetchSupportedMethods ( )
184
+
185
+ const supportedState = this . supportedMethodsState
186
+ switch ( supportedState . type ) {
187
+ case 'not-fetched' :
188
+ return err ( {
189
+ type : 'invalid_state' ,
190
+ message : 'Supported methods not fetched yet whereas they should.' ,
191
+ } )
192
+ case 'fetched' : {
193
+ // If we have fetched the supported methods, we can check if the method is supported
194
+ if ( ! supportedState . methods . has ( method ) ) {
195
+ return err ( {
196
+ type : 'sqlmesh_outdated' ,
197
+ message : `Method '${ method } ' is not supported by this LSP server.` ,
198
+ } )
199
+ }
200
+ const response = await this . internal_call_custom_method (
201
+ method ,
202
+ request as any ,
203
+ )
204
+ if ( isErr ( response ) ) {
205
+ return err ( {
206
+ type : 'generic' ,
207
+ message : response . error ,
208
+ } )
209
+ }
210
+ return ok ( response . value as Response )
211
+ }
212
+ case 'endpoint-not-supported' : {
213
+ const response = await this . internal_call_custom_method (
214
+ method ,
215
+ request as any ,
216
+ )
217
+ if ( isErr ( response ) ) {
218
+ return err ( {
219
+ type : 'generic' ,
220
+ message : response . error ,
221
+ } )
222
+ }
223
+ return ok ( response . value as Response )
224
+ }
225
+ }
226
+ }
227
+
228
+ /**
229
+ * Internal method to call a custom LSP method without checking if the method is supported. It is used for
230
+ * the class whereas the `call_custom_method` checks if the method is supported.
231
+ */
232
+ public async internal_call_custom_method <
109
233
Method extends CustomLSPMethods [ 'method' ] ,
110
234
Request extends Extract < CustomLSPMethods , { method : Method } > [ 'request' ] ,
111
235
Response extends Extract < CustomLSPMethods , { method : Method } > [ 'response' ] ,
112
236
> ( method : Method , request : Request ) : Promise < Result < Response , string > > {
113
237
if ( ! this . client ) {
114
238
return err ( 'lsp client not ready' )
115
239
}
240
+
116
241
try {
117
242
const result = await this . client . sendRequest < Response > ( method , request )
118
243
return ok ( result )
0 commit comments