1use std::collections::HashMap;
4use std::sync::Arc;
5use tower_lsp::jsonrpc::Result;
6use tower_lsp::lsp_types::*;
7use tower_lsp::{Client, LanguageServer};
8use tracing::info;
9
10use crate::config::RezConfigProvider;
11use crate::core::{ConfigProvider, PackageDiscovery as PackageDiscoveryTrait};
12use crate::discovery::PackageDiscoveryImpl;
13use crate::server::{navigation::NavigationHandler, DiagnosticsManager};
14
15pub struct RezLanguageServer {
17 client: Client,
19 document_map: tokio::sync::RwLock<HashMap<Url, String>>,
21 config_provider: Arc<tokio::sync::RwLock<RezConfigProvider>>,
23 package_discovery: Arc<tokio::sync::RwLock<Option<PackageDiscoveryImpl>>>,
25 diagnostics_manager: Arc<DiagnosticsManager>,
27 navigation_handler: Arc<NavigationHandler>,
29}
30
31impl RezLanguageServer {
32 pub fn new(client: Client) -> Self {
34 let diagnostics_manager =
35 Arc::new(DiagnosticsManager::new().expect("Failed to create diagnostics manager"));
36
37 let package_discovery = Arc::new(tokio::sync::RwLock::new(None));
38 let navigation_handler = Arc::new(NavigationHandler::new(package_discovery.clone()));
39
40 Self {
41 client,
42 document_map: tokio::sync::RwLock::new(HashMap::new()),
43 config_provider: Arc::new(tokio::sync::RwLock::new(RezConfigProvider::new())),
44 package_discovery,
45 diagnostics_manager,
46 navigation_handler,
47 }
48 }
49
50 async fn initialize_components(&self) -> Result<()> {
52 info!("Initializing Rez LSP server components");
53
54 let mut config_provider = self.config_provider.write().await;
56 if let Err(e) = config_provider.load_from_environment().await {
57 self.client
58 .log_message(
59 MessageType::WARNING,
60 format!("Failed to load configuration: {}", e),
61 )
62 .await;
63 return Ok(());
64 }
65
66 if let Err(e) = config_provider.validate().await {
68 self.client
69 .log_message(
70 MessageType::WARNING,
71 format!("Configuration validation failed: {}", e),
72 )
73 .await;
74 return Ok(());
75 }
76
77 let config = config_provider.config().clone();
79 drop(config_provider); let mut discovery = PackageDiscoveryImpl::new(config);
82 if let Err(e) = discovery.scan_packages().await {
83 self.client
84 .log_message(
85 MessageType::WARNING,
86 format!("Failed to scan packages: {}", e),
87 )
88 .await;
89 } else {
90 let (families, total) = discovery.get_stats().await.unwrap_or((0, 0));
91 self.client
92 .log_message(
93 MessageType::INFO,
94 format!(
95 "Discovered {} package families ({} total packages)",
96 families, total
97 ),
98 )
99 .await;
100 }
101
102 let mut package_discovery = self.package_discovery.write().await;
103 *package_discovery = Some(discovery);
104
105 Ok(())
106 }
107
108 async fn on_change(&self, params: TextDocumentItem) {
110 let mut document_map = self.document_map.write().await;
111 document_map.insert(params.uri.clone(), params.text.clone());
112 drop(document_map); if params.uri.path().ends_with("package.py") {
116 if let Ok(diagnostics) = self
117 .diagnostics_manager
118 .validate_file(¶ms.uri, ¶ms.text)
119 .await
120 {
121 self.client
123 .publish_diagnostics(params.uri, diagnostics, None)
124 .await;
125 }
126 }
127 }
128}
129
130#[tower_lsp::async_trait]
131impl LanguageServer for RezLanguageServer {
132 async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> {
133 info!("Rez LSP Server initializing...");
134
135 Ok(InitializeResult {
136 server_info: Some(ServerInfo {
137 name: "rez-lsp-server".to_string(),
138 version: Some(env!("CARGO_PKG_VERSION").to_string()),
139 }),
140 capabilities: ServerCapabilities {
141 text_document_sync: Some(TextDocumentSyncCapability::Kind(
142 TextDocumentSyncKind::FULL,
143 )),
144 completion_provider: Some(CompletionOptions {
145 resolve_provider: Some(false),
146 trigger_characters: Some(vec![
147 "\"".to_string(),
148 "'".to_string(),
149 "-".to_string(),
150 ".".to_string(),
151 ]),
152 work_done_progress_options: Default::default(),
153 all_commit_characters: None,
154 completion_item: None,
155 }),
156 hover_provider: Some(HoverProviderCapability::Simple(true)),
157 diagnostic_provider: Some(DiagnosticServerCapabilities::Options(
158 DiagnosticOptions {
159 identifier: Some("rez-lsp".to_string()),
160 inter_file_dependencies: true,
161 workspace_diagnostics: false,
162 work_done_progress_options: Default::default(),
163 },
164 )),
165 definition_provider: Some(OneOf::Left(true)),
166 references_provider: Some(OneOf::Left(true)),
167 document_symbol_provider: Some(OneOf::Left(true)),
168 workspace_symbol_provider: Some(OneOf::Left(true)),
169 ..ServerCapabilities::default()
170 },
171 })
172 }
173
174 async fn initialized(&self, _: InitializedParams) {
175 info!("Rez LSP Server initialized!");
176
177 self.client
178 .log_message(MessageType::INFO, "Rez LSP Server initialized")
179 .await;
180
181 if let Err(e) = self.initialize_components().await {
183 self.client
184 .log_message(
185 MessageType::ERROR,
186 format!("Failed to initialize components: {}", e),
187 )
188 .await;
189 }
190 }
191
192 async fn shutdown(&self) -> Result<()> {
193 info!("Rez LSP Server shutting down...");
194 Ok(())
195 }
196
197 async fn did_open(&self, params: DidOpenTextDocumentParams) {
198 let filename = params
199 .text_document
200 .uri
201 .path()
202 .split('/')
203 .next_back()
204 .unwrap_or("unknown");
205 info!("Opened: {}", filename);
206 self.on_change(params.text_document).await;
207 }
208
209 async fn did_change(&self, mut params: DidChangeTextDocumentParams) {
210 let filename = params
212 .text_document
213 .uri
214 .path()
215 .split('/')
216 .next_back()
217 .unwrap_or("unknown");
218 tracing::debug!("Document changed: {}", filename);
219
220 if let Some(change) = params.content_changes.pop() {
221 let mut document_map = self.document_map.write().await;
222 document_map.insert(params.text_document.uri.clone(), change.text.clone());
223 drop(document_map); if params.text_document.uri.path().ends_with("package.py") {
227 if let Ok(diagnostics) = self
228 .diagnostics_manager
229 .validate_file(¶ms.text_document.uri, &change.text)
230 .await
231 {
232 self.client
234 .publish_diagnostics(params.text_document.uri, diagnostics, None)
235 .await;
236 }
237 }
238 }
239 }
240
241 async fn did_save(&self, params: DidSaveTextDocumentParams) {
242 let filename = params
243 .text_document
244 .uri
245 .path()
246 .split('/')
247 .next_back()
248 .unwrap_or("unknown");
249 info!("Saved: {}", filename);
250 }
251
252 async fn did_close(&self, params: DidCloseTextDocumentParams) {
253 let filename = params
254 .text_document
255 .uri
256 .path()
257 .split('/')
258 .next_back()
259 .unwrap_or("unknown");
260 tracing::debug!("Closed: {}", filename);
261
262 let mut document_map = self.document_map.write().await;
263 document_map.remove(¶ms.text_document.uri);
264 }
265
266 async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
267 super::completion::handle_completion(¶ms, &self.package_discovery).await
268 }
269
270 async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
271 super::hover::handle_hover(¶ms).await
272 }
273
274 async fn goto_definition(
275 &self,
276 params: GotoDefinitionParams,
277 ) -> Result<Option<GotoDefinitionResponse>> {
278 match self
279 .navigation_handler
280 .handle_goto_definition(¶ms)
281 .await
282 {
283 Ok(response) => Ok(response),
284 Err(e) => {
285 self.client
286 .log_message(
287 MessageType::ERROR,
288 format!("Go to definition failed: {}", e),
289 )
290 .await;
291 Ok(None)
292 }
293 }
294 }
295
296 async fn references(&self, params: ReferenceParams) -> Result<Option<Vec<Location>>> {
297 match self
298 .navigation_handler
299 .handle_find_references(¶ms)
300 .await
301 {
302 Ok(response) => Ok(response),
303 Err(e) => {
304 self.client
305 .log_message(MessageType::ERROR, format!("Find references failed: {}", e))
306 .await;
307 Ok(None)
308 }
309 }
310 }
311
312 async fn document_symbol(
313 &self,
314 params: DocumentSymbolParams,
315 ) -> Result<Option<DocumentSymbolResponse>> {
316 match self
317 .navigation_handler
318 .handle_document_symbols(¶ms)
319 .await
320 {
321 Ok(response) => Ok(response),
322 Err(e) => {
323 self.client
324 .log_message(
325 MessageType::ERROR,
326 format!("Document symbols failed: {}", e),
327 )
328 .await;
329 Ok(None)
330 }
331 }
332 }
333
334 async fn symbol(
335 &self,
336 params: WorkspaceSymbolParams,
337 ) -> Result<Option<Vec<SymbolInformation>>> {
338 match self
339 .navigation_handler
340 .handle_workspace_symbols(¶ms)
341 .await
342 {
343 Ok(response) => Ok(response),
344 Err(e) => {
345 self.client
346 .log_message(
347 MessageType::ERROR,
348 format!("Workspace symbols failed: {}", e),
349 )
350 .await;
351 Ok(None)
352 }
353 }
354 }
355}
356
357#[cfg(test)]
358mod tests {
359 use super::*;
360 use tower_lsp::LspService;
361
362 #[tokio::test]
363 async fn test_server_creation() {
364 let (client, _) = LspService::new(RezLanguageServer::new);
365 drop(client);
367 }
368}