Skip to main content

rez_lsp_server/server/
lsp_server.rs

1//! Main LSP server implementation.
2
3use 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
15/// The main Rez Language Server.
16pub struct RezLanguageServer {
17    /// LSP client for communication
18    client: Client,
19    /// Document content cache
20    document_map: tokio::sync::RwLock<HashMap<Url, String>>,
21    /// Configuration provider
22    config_provider: Arc<tokio::sync::RwLock<RezConfigProvider>>,
23    /// Package discovery service
24    package_discovery: Arc<tokio::sync::RwLock<Option<PackageDiscoveryImpl>>>,
25    /// Diagnostics manager
26    diagnostics_manager: Arc<DiagnosticsManager>,
27    /// Navigation handler
28    navigation_handler: Arc<NavigationHandler>,
29}
30
31impl RezLanguageServer {
32    /// Create a new Rez Language Server instance.
33    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    /// Initialize the server components.
51    async fn initialize_components(&self) -> Result<()> {
52        info!("Initializing Rez LSP server components");
53
54        // Load configuration
55        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        // Validate configuration
67        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        // Initialize package discovery
78        let config = config_provider.config().clone();
79        drop(config_provider); // Release the lock
80
81        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    /// Handle document changes.
109    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); // Release the lock early
113
114        // Run diagnostics for package.py files
115        if params.uri.path().ends_with("package.py") {
116            if let Ok(diagnostics) = self
117                .diagnostics_manager
118                .validate_file(&params.uri, &params.text)
119                .await
120            {
121                // Publish diagnostics to the client
122                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        // Initialize components in the background
182        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        // Use debug level for frequent document changes
211        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); // Release the lock early
224
225            // Run diagnostics for package.py files
226            if params.text_document.uri.path().ends_with("package.py") {
227                if let Ok(diagnostics) = self
228                    .diagnostics_manager
229                    .validate_file(&params.text_document.uri, &change.text)
230                    .await
231                {
232                    // Publish diagnostics to the client
233                    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(&params.text_document.uri);
264    }
265
266    async fn completion(&self, params: CompletionParams) -> Result<Option<CompletionResponse>> {
267        super::completion::handle_completion(&params, &self.package_discovery).await
268    }
269
270    async fn hover(&self, params: HoverParams) -> Result<Option<Hover>> {
271        super::hover::handle_hover(&params).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(&params)
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(&params)
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(&params)
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(&params)
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        // Basic smoke test - server should be created without panicking
366        drop(client);
367    }
368}