5656from sqlmesh .lsp .uri import URI
5757from web .server .api .endpoints .lineage import column_lineage , model_lineage
5858from web .server .api .endpoints .models import get_models
59+ from typing import Union
60+ from dataclasses import dataclass
61+
62+
63+ @dataclass
64+ class NoContext :
65+ """State when no context has been attempted to load."""
66+
67+ pass
68+
69+
70+ @dataclass
71+ class ContextLoaded :
72+ """State when context has been successfully loaded."""
73+
74+ lsp_context : LSPContext
75+
76+
77+ @dataclass
78+ class ContextFailed :
79+ """State when context failed to load with an error message."""
80+
81+ error_message : str
82+ context : t .Optional [Context ] = None
83+
84+
85+ ContextState = Union [NoContext , ContextLoaded , ContextFailed ]
5986
6087
6188class SQLMeshLanguageServer :
@@ -72,7 +99,7 @@ def __init__(
7299 """
73100 self .server = LanguageServer (server_name , version )
74101 self .context_class = context_class
75- self .lsp_context : t . Optional [ LSPContext ] = None
102+ self .context_state : ContextState = NoContext ()
76103 self .workspace_folders : t .List [Path ] = []
77104
78105 self .has_raised_loading_error : bool = False
@@ -257,16 +284,53 @@ def did_open(ls: LanguageServer, params: types.DidOpenTextDocumentParams) -> Non
257284 @self .server .feature (types .TEXT_DOCUMENT_DID_SAVE )
258285 def did_save (ls : LanguageServer , params : types .DidSaveTextDocumentParams ) -> None :
259286 uri = URI (params .text_document .uri )
260- if self .lsp_context is None :
287+ if isinstance ( self .context_state , NoContext ) :
261288 return
262289
263- context = self .lsp_context .context
264- context .load ()
265- self .lsp_context = LSPContext (context )
290+ if isinstance (self .context_state , ContextFailed ):
291+ if self .context_state .context :
292+ try :
293+ self .context_state .context .load ()
294+ self .context_state = ContextLoaded (
295+ lsp_context = LSPContext (self .context_state .context )
296+ )
297+ except Exception as e :
298+ ls .log_trace (f"Error loading context: { e } " )
299+ if not isinstance (self .context_state , ContextFailed ):
300+ raise Exception ("Context state should be failed" )
301+ self .context_state = ContextFailed (
302+ error_message = str (e ), context = self .context_state .context
303+ )
304+ return
305+ else :
306+ # If there's no context, try to create one from scratch
307+ try :
308+ self ._ensure_context_for_document (uri )
309+ # If successful, context_state will be ContextLoaded
310+ if isinstance (self .context_state , ContextLoaded ):
311+ ls .show_message (
312+ "Successfully loaded SQLMesh context" ,
313+ types .MessageType .Info ,
314+ )
315+ except Exception as e :
316+ ls .log_trace (f"Still cannot load context: { e } " )
317+ return
318+
319+ # Reload the context if was successfully
320+ try :
321+ context = self .context_state .lsp_context .context
322+ context .load ()
323+ self .context_state = ContextLoaded (lsp_context = LSPContext (context ))
324+ except Exception as e :
325+ ls .log_trace (f"Error loading context: { e } " )
326+ self .context_state = ContextFailed (
327+ error_message = str (e ), context = self .context_state .lsp_context .context
328+ )
329+ return
266330
267331 # Only publish diagnostics if client doesn't support pull diagnostics
268332 if not self .client_supports_pull_diagnostics :
269- diagnostics = self .lsp_context .lint_model (uri )
333+ diagnostics = self .context_state . lsp_context .lint_model (uri )
270334 ls .publish_diagnostics (
271335 params .text_document .uri ,
272336 SQLMeshLanguageServer ._diagnostics_to_lsp_diagnostics (diagnostics ),
@@ -443,11 +507,8 @@ def prepare_rename_handler(
443507 """Prepare for rename operation by checking if the symbol can be renamed."""
444508 try :
445509 uri = URI (params .text_document .uri )
446- self ._ensure_context_for_document (uri )
447- if self .lsp_context is None :
448- raise RuntimeError (f"No context found for document: { uri } " )
449-
450- result = prepare_rename (self .lsp_context , uri , params .position )
510+ context = self ._context_get_or_load (uri )
511+ result = prepare_rename (context , uri , params .position )
451512 return result
452513 except Exception as e :
453514 ls .log_trace (f"Error preparing rename: { e } " )
@@ -460,13 +521,8 @@ def rename_handler(
460521 """Perform rename operation on the symbol at the given position."""
461522 try :
462523 uri = URI (params .text_document .uri )
463- self ._ensure_context_for_document (uri )
464- if self .lsp_context is None :
465- raise RuntimeError (f"No context found for document: { uri } " )
466-
467- workspace_edit = rename_symbol (
468- self .lsp_context , uri , params .position , params .new_name
469- )
524+ context = self ._context_get_or_load (uri )
525+ workspace_edit = rename_symbol (context , uri , params .position , params .new_name )
470526 return workspace_edit
471527 except Exception as e :
472528 ls .show_message (f"Error performing rename: { e } " , types .MessageType .Error )
@@ -479,11 +535,8 @@ def document_highlight_handler(
479535 """Highlight all occurrences of the symbol at the given position."""
480536 try :
481537 uri = URI (params .text_document .uri )
482- self ._ensure_context_for_document (uri )
483- if self .lsp_context is None :
484- raise RuntimeError (f"No context found for document: { uri } " )
485-
486- highlights = get_document_highlights (self .lsp_context , uri , params .position )
538+ context = self ._context_get_or_load (uri )
539+ highlights = get_document_highlights (context , uri , params .position )
487540 return highlights
488541 except Exception as e :
489542 ls .log_trace (f"Error getting document highlights: { e } " )
@@ -670,11 +723,13 @@ def _get_diagnostics_for_uri(self, uri: URI) -> t.Tuple[t.List[types.Diagnostic]
670723 return [], 0
671724
672725 def _context_get_or_load (self , document_uri : t .Optional [URI ] = None ) -> LSPContext :
673- if self .lsp_context is None :
726+ if isinstance (self .context_state , ContextFailed ):
727+ raise RuntimeError (self .context_state .error_message )
728+ if isinstance (self .context_state , NoContext ):
674729 self ._ensure_context_for_document (document_uri )
675- if self .lsp_context is None :
676- raise RuntimeError ("No context found able to get or load " )
677- return self .lsp_context
730+ if not isinstance ( self .context_state , ContextLoaded ) :
731+ raise RuntimeError ("Context is not loaded " )
732+ return self .context_state . lsp_context
678733
679734 def _ensure_context_for_document (
680735 self ,
@@ -692,10 +747,10 @@ def _ensure_context_for_document(
692747 self ._ensure_context_in_folder (document_folder )
693748 return
694749
695- return self ._ensure_context_in_folder ()
750+ self ._ensure_context_in_folder ()
696751
697752 def _ensure_context_in_folder (self , folder_path : t .Optional [Path ] = None ) -> None :
698- if self .lsp_context is not None :
753+ if not isinstance ( self .context_state , NoContext ) :
699754 return
700755
701756 # If not found in the provided folder, search through all workspace folders
@@ -729,7 +784,7 @@ def _ensure_context_in_folder(self, folder_path: t.Optional[Path] = None) -> Non
729784 def _create_lsp_context (self , paths : t .List [Path ]) -> t .Optional [LSPContext ]:
730785 """Create a new LSPContext instance using the configured context class.
731786
732- On success, sets self.lsp_context and returns the created context.
787+ On success, sets self.context_state to ContextLoaded and returns the created context.
733788
734789 Args:
735790 paths: List of paths to pass to the context constructor
@@ -738,14 +793,22 @@ def _create_lsp_context(self, paths: t.List[Path]) -> t.Optional[LSPContext]:
738793 A new LSPContext instance wrapping the created context, or None if creation fails
739794 """
740795 try :
741- if self .lsp_context is None :
796+ if isinstance ( self .context_state , NoContext ) :
742797 context = self .context_class (paths = paths )
743798 loaded_sqlmesh_message (self .server , paths [0 ])
799+ elif isinstance (self .context_state , ContextFailed ):
800+ if self .context_state .context :
801+ context = self .context_state .context
802+ context .load ()
803+ else :
804+ # If there's no context (initial creation failed), try creating again
805+ context = self .context_class (paths = paths )
806+ loaded_sqlmesh_message (self .server , paths [0 ])
744807 else :
745- self .lsp_context .context . load ()
746- context = self . lsp_context . context
747- self .lsp_context = LSPContext (context )
748- return self .lsp_context
808+ context = self .context_state . lsp_context .context
809+ context . load ()
810+ self .context_state = ContextLoaded ( lsp_context = LSPContext (context ) )
811+ return self .context_state . lsp_context
749812 except Exception as e :
750813 # Only show the error message once
751814 if not self .has_raised_loading_error :
@@ -756,6 +819,14 @@ def _create_lsp_context(self, paths: t.List[Path]) -> t.Optional[LSPContext]:
756819 self .has_raised_loading_error = True
757820
758821 self .server .log_trace (f"Error creating context: { e } " )
822+ # Store the error in context state so subsequent requests show the actual error
823+ # Try to preserve any partially loaded context if it exists
824+ context = None
825+ if isinstance (self .context_state , ContextLoaded ):
826+ context = self .context_state .lsp_context .context
827+ elif isinstance (self .context_state , ContextFailed ) and self .context_state .context :
828+ context = self .context_state .context
829+ self .context_state = ContextFailed (error_message = str (e ), context = context )
759830 return None
760831
761832 @staticmethod
0 commit comments