@@ -9,18 +9,41 @@ import { sqlmeshLspExec } from '../utilities/sqlmesh/sqlmesh'
99import { err , isErr , ok , Result } from '@bus/result'
1010import { getWorkspaceFolders } from '../utilities/common/vscodeapi'
1111import { 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'
1318import { CustomLSPMethods } from './custom'
1419
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+
1529let outputChannel : OutputChannel | undefined
1630
1731export class LSPClient implements Disposable {
1832 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' }
1938
2039 constructor ( ) {
2140 this . client = undefined
2241 }
2342
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.
2447 public hasCompletionCapability ( ) : boolean {
2548 if ( ! this . client ) {
2649 traceError ( 'LSP client is not initialized' )
@@ -98,21 +121,123 @@ export class LSPClient implements Disposable {
98121 if ( this . client ) {
99122 await this . client . stop ( )
100123 this . client = undefined
124+ // Reset supported methods state when the client stops
125+ this . supportedMethodsState = { type : 'not-fetched' }
101126 }
102127 }
103128
104129 public async dispose ( ) {
105130 await this . stop ( )
106131 }
107132
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+
108161 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 <
109233 Method extends CustomLSPMethods [ 'method' ] ,
110234 Request extends Extract < CustomLSPMethods , { method : Method } > [ 'request' ] ,
111235 Response extends Extract < CustomLSPMethods , { method : Method } > [ 'response' ] ,
112236 > ( method : Method , request : Request ) : Promise < Result < Response , string > > {
113237 if ( ! this . client ) {
114238 return err ( 'lsp client not ready' )
115239 }
240+
116241 try {
117242 const result = await this . client . sendRequest < Response > ( method , request )
118243 return ok ( result )
0 commit comments