1414from sqlmesh ._version import __version__
1515from sqlmesh .core .audit .definition import ModelAudit
1616from sqlmesh .core .context import Context
17+ from sqlmesh .core .linter .rule import RuleViolationWithModelAndType
1718from sqlmesh .core .model import Model
1819
1920
@@ -32,13 +33,78 @@ def __init__(
3233 self .server = LanguageServer (server_name , version )
3334 self .context_class = context_class
3435 self .context : t .Optional [Context ] = None
36+ self .lint_cache : t .Dict [str , t .List [RuleViolationWithModelAndType ]] = {}
3537
3638 # Register LSP features (e.g., formatting, hover, etc.)
3739 self ._register_features ()
3840
3941 def _register_features (self ) -> None :
4042 """Register LSP features on the internal LanguageServer instance."""
4143
44+ @self .server .feature (types .TEXT_DOCUMENT_DID_OPEN )
45+ def did_open (ls : LanguageServer , params : types .DidOpenTextDocumentParams ) -> None :
46+ if self .context is None :
47+ self .ensure_context_for_document (
48+ ls .workspace .get_document (params .text_document .uri )
49+ )
50+ if self .context is None :
51+ raise RuntimeError ("No context found" )
52+ self .lint_cache [params .text_document .uri ] = self .context .lint_models (
53+ [params .text_document .uri ]
54+ )
55+ ls .publish_diagnostics (
56+ params .text_document .uri ,
57+ [
58+ self ._diagnostic_to_lsp_diagnostic (diagnostic )
59+ for diagnostic in self .lint_cache [params .text_document .uri ]
60+ if diagnostic is not None
61+ ],
62+ )
63+
64+ @self .server .feature (types .TEXT_DOCUMENT_DID_CHANGE )
65+ def did_change (ls : LanguageServer , params : types .DidChangeTextDocumentParams ) -> None :
66+ if self .context is None :
67+ self .ensure_context_for_document (
68+ ls .workspace .get_document (params .text_document .uri )
69+ )
70+ if self .context is None :
71+ raise RuntimeError ("No context found" )
72+ self .lint_cache [params .text_document .uri ] = self .context .lint_models (
73+ [params .text_document .uri ]
74+ )
75+ ls .publish_diagnostics (
76+ params .text_document .uri ,
77+ [
78+ self ._diagnostic_to_lsp_diagnostic (diagnostic )
79+ for diagnostic in self .lint_cache [params .text_document .uri ]
80+ if diagnostic is not None
81+ ],
82+ )
83+
84+ @self .server .feature (types .TEXT_DOCUMENT_DID_CLOSE )
85+ def did_close (ls : LanguageServer , params : types .DidCloseTextDocumentParams ) -> None :
86+ self .lint_cache .pop (params .text_document .uri , None )
87+
88+ @self .server .feature (types .TEXT_DOCUMENT_DID_SAVE )
89+ def did_save (ls : LanguageServer , params : types .DidSaveTextDocumentParams ) -> None :
90+ if self .context is None :
91+ self .ensure_context_for_document (
92+ ls .workspace .get_document (params .text_document .uri )
93+ )
94+ if self .context is None :
95+ raise RuntimeError ("No context found" )
96+ self .lint_cache [params .text_document .uri ] = self .context .lint_models (
97+ [params .text_document .uri ]
98+ )
99+ ls .publish_diagnostics (
100+ params .text_document .uri ,
101+ [
102+ self ._diagnostic_to_lsp_diagnostic (diagnostic )
103+ for diagnostic in self .lint_cache [params .text_document .uri ]
104+ if diagnostic is not None
105+ ],
106+ )
107+
42108 @self .server .feature (types .TEXT_DOCUMENT_FORMATTING )
43109 def formatting (
44110 ls : LanguageServer , params : types .DocumentFormattingParams
@@ -110,6 +176,25 @@ def ensure_context_for_document(self, document: TextDocument) -> TextDocument:
110176
111177 return document
112178
179+ @staticmethod
180+ def _diagnostic_to_lsp_diagnostic (
181+ diagnostic : RuleViolationWithModelAndType ,
182+ ) -> t .Optional [types .Diagnostic ]:
183+ if diagnostic .model ._path is None :
184+ return None
185+ with open (diagnostic .model ._path , "r" , encoding = "utf-8" ) as file :
186+ lines = file .readlines ()
187+ return types .Diagnostic (
188+ range = types .Range (
189+ start = types .Position (line = 0 , character = 0 ),
190+ end = types .Position (line = len (lines ), character = len (lines [- 1 ])),
191+ ),
192+ message = diagnostic .violation_msg ,
193+ severity = types .DiagnosticSeverity .Error
194+ if diagnostic .violation_type == "error"
195+ else types .DiagnosticSeverity .Warning ,
196+ )
197+
113198 def start (self ) -> None :
114199 """Start the server with I/O transport."""
115200 logging .basicConfig (level = logging .DEBUG )
0 commit comments