1use crate::analysis::completion::CompletionEngine;
46use crate::analysis::diagnostic::DiagnosticEngine;
47use crate::analysis::rust::macro_analyzer::MacroAnalyzer;
48use crate::analysis::toml::toml_analyzer::TomlAnalyzer;
49use crate::core::config::ServerConfig;
50use crate::core::document::DocumentManager;
51use crate::core::index::IndexManager;
52use crate::core::schema::SchemaProvider;
53use crate::scanner::route::RouteNavigator;
54use crate::utils::error::{ErrorHandler, RecoveryAction};
55use crate::utils::status::ServerStatus;
56use crate::{Error, Result};
57use lsp_server::{Connection, Message, Notification, Request, RequestId, Response};
58use lsp_types::{
59 notification::{
60 DidChangeTextDocument, DidCloseTextDocument, DidOpenTextDocument, Exit, Notification as _,
61 },
62 request::{Completion, DocumentSymbolRequest, GotoDefinition, HoverRequest, Request as _},
63 CompletionParams, CompletionResponse, DidChangeTextDocumentParams, DidCloseTextDocumentParams,
64 DidOpenTextDocumentParams, DocumentSymbolParams, DocumentSymbolResponse, GotoDefinitionParams,
65 GotoDefinitionResponse, HoverParams, InitializeParams, InitializeResult, ServerCapabilities,
66 ServerInfo,
67};
68use std::sync::Arc;
69
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
72pub enum ServerState {
73 Uninitialized,
75 Initialized,
77 ShuttingDown,
79}
80
81pub struct LspServer {
83 connection: Connection,
85 pub state: ServerState,
87 pub workspace_path: Option<std::path::PathBuf>,
89 pub document_manager: Arc<DocumentManager>,
91 error_handler: ErrorHandler,
93 pub config: ServerConfig,
95 pub status: ServerStatus,
97 pub schema_provider: Arc<SchemaProvider>,
99 pub toml_analyzer: Arc<TomlAnalyzer>,
101 pub macro_analyzer: Arc<MacroAnalyzer>,
103 pub route_navigator: Arc<RouteNavigator>,
105 pub completion_engine: Arc<CompletionEngine>,
107 pub diagnostic_engine: Arc<DiagnosticEngine>,
109 pub index_manager: Arc<IndexManager>,
111}
112
113impl LspServer {
114 pub fn start() -> Result<Self> {
118 tracing::info!("Starting summer-lsp server");
119
120 let (connection, _io_threads) = Connection::stdio();
122
123 Self::new_with_connection(connection)
124 }
125
126 pub fn new_for_test() -> Result<Self> {
128 let (connection, _io_threads) = Connection::memory();
131
132 Self::new_with_connection(connection)
133 }
134
135 fn new_with_connection(connection: Connection) -> Result<Self> {
137 let config = ServerConfig::load(None);
139
140 if let Err(e) = config.validate() {
142 tracing::error!("Invalid configuration: {}", e);
143 return Err(Error::Config(e));
144 }
145
146 let verbose = config.logging.verbose;
148
149 tracing::info!("Initializing components...");
151
152 tracing::info!("Loading configuration schema...");
154 let schema_provider = Arc::new({
155 let runtime = tokio::runtime::Runtime::new()
157 .map_err(|e| Error::SchemaLoad(format!("Failed to create tokio runtime: {}", e)))?;
158
159 match runtime.block_on(SchemaProvider::load()) {
160 Ok(provider) => {
161 tracing::info!("Schema loaded successfully from URL");
162 provider
163 }
164 Err(e) => {
165 tracing::warn!("Failed to load schema from URL: {}, using fallback", e);
166 SchemaProvider::default()
167 }
168 }
169 });
170
171 let toml_analyzer = Arc::new(TomlAnalyzer::new((*schema_provider).clone()));
173
174 let macro_analyzer = Arc::new(MacroAnalyzer::new());
176
177 let route_navigator = Arc::new(RouteNavigator::new());
179
180 let completion_engine = Arc::new(CompletionEngine::new((*schema_provider).clone()));
182
183 let diagnostic_engine = Arc::new(DiagnosticEngine::new());
185
186 let index_manager = Arc::new(IndexManager::new());
188
189 tracing::info!("All components initialized successfully");
190
191 Ok(Self {
192 connection,
193 state: ServerState::Uninitialized,
194 workspace_path: None,
195 document_manager: Arc::new(DocumentManager::new()),
196 error_handler: ErrorHandler::new(verbose),
197 config,
198 status: ServerStatus::new(),
199 schema_provider,
200 toml_analyzer,
201 macro_analyzer,
202 route_navigator,
203 completion_engine,
204 diagnostic_engine,
205 index_manager,
206 })
207 }
208
209 pub fn run(&mut self) -> Result<()> {
213 self.initialize()?;
215
216 self.event_loop()?;
218
219 self.shutdown()?;
221
222 Ok(())
223 }
224
225 fn initialize(&mut self) -> Result<()> {
227 tracing::info!("Waiting for initialize request");
228
229 let (id, params) = self.connection.initialize_start()?;
230 let init_params: InitializeParams = serde_json::from_value(params)?;
231
232 tracing::info!(
233 "Received initialize request from client: {:?}",
234 init_params.client_info
235 );
236
237 let init_result = self.handle_initialize(init_params)?;
238 let init_result_json = serde_json::to_value(init_result)?;
239
240 self.connection.initialize_finish(id, init_result_json)?;
241
242 self.state = ServerState::Initialized;
243 tracing::info!("LSP server initialized successfully");
244
245 Ok(())
246 }
247
248 fn event_loop(&mut self) -> Result<()> {
252 tracing::info!("Entering main event loop");
253
254 loop {
255 if self.state == ServerState::ShuttingDown {
257 tracing::info!("Server is shutting down, stopping event loop");
258 break;
259 }
260
261 let msg = match self.connection.receiver.recv() {
263 Ok(msg) => msg,
264 Err(e) => {
265 let error = Error::MessageReceive(e.to_string());
266 let result = self.error_handler.handle(&error);
267
268 match result.action {
269 RecoveryAction::RetryConnection => {
270 tracing::info!("Attempting to recover connection...");
271 std::thread::sleep(std::time::Duration::from_millis(100));
273 continue;
274 }
275 RecoveryAction::Abort => {
276 tracing::error!("Fatal error receiving message, shutting down");
277 break;
278 }
279 _ => {
280 tracing::warn!("Unexpected recovery action for message receive error");
281 break;
282 }
283 }
284 }
285 };
286
287 if let Err(e) = self.handle_message(msg) {
289 self.status.record_error();
291
292 let result = self.error_handler.handle(&e);
293
294 match result.action {
296 RecoveryAction::Abort => {
297 tracing::error!("Fatal error, shutting down server");
298 self.state = ServerState::ShuttingDown;
299 break;
300 }
301 _ => {
302 if result.notify_client {
304 if let Err(notify_err) = self.notify_client_error(&e) {
306 tracing::error!(
307 "Failed to notify client about error: {}",
308 notify_err
309 );
310 }
311 }
312 }
313 }
314 }
315 }
316
317 Ok(())
318 }
319
320 fn handle_message(&mut self, msg: Message) -> Result<()> {
322 match msg {
323 Message::Request(req) => self.handle_request(req),
324 Message::Response(resp) => {
325 tracing::debug!("Received response: {:?}", resp.id);
326 Ok(())
328 }
329 Message::Notification(not) => self.handle_notification(not),
330 }
331 }
332
333 fn handle_request(&mut self, req: Request) -> Result<()> {
335 tracing::debug!("Received request: {} (id: {:?})", req.method, req.id);
336
337 self.status.record_request();
339
340 if self.connection.handle_shutdown(&req)? {
342 tracing::info!("Received shutdown request");
343 self.state = ServerState::ShuttingDown;
344 return Ok(());
345 }
346
347 match req.method.as_str() {
349 Completion::METHOD => self.handle_completion(req),
351 HoverRequest::METHOD => self.handle_hover(req),
353 GotoDefinition::METHOD => self.handle_goto_definition(req),
355 DocumentSymbolRequest::METHOD => self.handle_document_symbol(req),
357 "workspace/symbol" => self.handle_workspace_symbol(req),
359 "summer-lsp/status" => self.handle_status_query(req),
361 "summer/components" => self.handle_components_request(req),
363 "summer/routes" => self.handle_routes_request(req),
365 "summer/jobs" => self.handle_jobs_request(req),
367 "summer/plugins" => self.handle_plugins_request(req),
369 "summer/configurations" => self.handle_configurations_request(req),
371 _ => {
372 tracing::warn!("Unhandled request method: {}", req.method);
373 self.send_error_response(
375 req.id,
376 lsp_server::ErrorCode::MethodNotFound as i32,
377 format!("Method not found: {}", req.method),
378 )
379 }
380 }?;
381
382 Ok(())
383 }
384
385 fn handle_notification(&mut self, not: Notification) -> Result<()> {
387 tracing::debug!("Received notification: {}", not.method);
388
389 match not.method.as_str() {
390 DidOpenTextDocument::METHOD => {
391 let params: DidOpenTextDocumentParams = serde_json::from_value(not.params)?;
392 self.handle_did_open(params)?;
393 }
394 DidChangeTextDocument::METHOD => {
395 let params: DidChangeTextDocumentParams = serde_json::from_value(not.params)?;
396 self.handle_did_change(params)?;
397 }
398 DidCloseTextDocument::METHOD => {
399 let params: DidCloseTextDocumentParams = serde_json::from_value(not.params)?;
400 self.handle_did_close(params)?;
401 }
402 Exit::METHOD => {
403 tracing::info!("Received exit notification");
404 self.state = ServerState::ShuttingDown;
405 }
406 _ => {
407 tracing::debug!("Unhandled notification method: {}", not.method);
408 }
409 }
410
411 Ok(())
412 }
413
414 pub fn handle_did_open(&mut self, params: DidOpenTextDocumentParams) -> Result<()> {
416 let doc = params.text_document;
417 tracing::debug!("Document opened: {}", doc.uri);
418
419 self.document_manager.open(
420 doc.uri.clone(),
421 doc.version,
422 doc.text,
423 doc.language_id.clone(),
424 );
425
426 self.status.increment_document_count();
428
429 self.analyze_document(&doc.uri, &doc.language_id)?;
431
432 Ok(())
433 }
434
435 pub fn handle_did_change(&mut self, params: DidChangeTextDocumentParams) -> Result<()> {
437 let uri = params.text_document.uri;
438 let version = params.text_document.version;
439 tracing::debug!("Document changed: {} (version: {})", uri, version);
440
441 self.document_manager
442 .change(&uri, version, params.content_changes);
443
444 if let Some(doc) = self.document_manager.get(&uri) {
446 self.analyze_document(&uri, &doc.language_id)?;
447 }
448
449 Ok(())
450 }
451
452 pub fn handle_did_close(&mut self, params: DidCloseTextDocumentParams) -> Result<()> {
454 let uri = params.text_document.uri;
455 tracing::info!("Document closed: {}", uri);
456
457 self.document_manager.close(&uri);
458
459 self.status.decrement_document_count();
461
462 self.diagnostic_engine.clear(&uri);
464 let _ = self.diagnostic_engine.publish(&self.connection, &uri);
465
466 Ok(())
467 }
468
469 fn handle_completion(&mut self, req: Request) -> Result<()> {
471 tracing::debug!("Handling completion request");
472
473 let params: CompletionParams = serde_json::from_value(req.params)?;
474 self.status.record_completion();
475
476 let response = self.document_manager.with_document(
477 ¶ms.text_document_position.text_document.uri,
478 |doc| {
479 match doc.language_id.as_str() {
481 "toml" => {
482 if let Ok(toml_doc) = self.toml_analyzer.parse(&doc.content) {
483 self.completion_engine.complete_toml_document(
484 &toml_doc,
485 params.text_document_position.position,
486 )
487 } else {
488 vec![]
489 }
490 }
491 "rust" => {
492 vec![]
494 }
495 _ => vec![],
496 }
497 },
498 );
499
500 let result = match response {
501 Some(completions) => serde_json::to_value(CompletionResponse::Array(completions))?,
502 None => serde_json::Value::Null,
503 };
504
505 let response = Response {
506 id: req.id,
507 result: Some(result),
508 error: None,
509 };
510
511 self.connection
512 .sender
513 .send(Message::Response(response))
514 .map_err(|e| Error::MessageSend(e.to_string()))?;
515
516 Ok(())
517 }
518
519 fn handle_hover(&mut self, req: Request) -> Result<()> {
521 tracing::debug!("Handling hover request");
522
523 let params: HoverParams = serde_json::from_value(req.params)?;
524 self.status.record_hover();
525
526 let response = self.document_manager.with_document(
527 ¶ms.text_document_position_params.text_document.uri,
528 |doc| {
529 match doc.language_id.as_str() {
531 "toml" => {
532 if let Ok(toml_doc) = self.toml_analyzer.parse(&doc.content) {
533 self.toml_analyzer
534 .hover(&toml_doc, params.text_document_position_params.position)
535 } else {
536 None
537 }
538 }
539 "rust" => {
540 None
542 }
543 _ => None,
544 }
545 },
546 );
547
548 let result = match response {
549 Some(Some(hover)) => serde_json::to_value(hover)?,
550 _ => serde_json::Value::Null,
551 };
552
553 let response = Response {
554 id: req.id,
555 result: Some(result),
556 error: None,
557 };
558
559 self.connection
560 .sender
561 .send(Message::Response(response))
562 .map_err(|e| Error::MessageSend(e.to_string()))?;
563
564 Ok(())
565 }
566
567 fn handle_goto_definition(&mut self, req: Request) -> Result<()> {
569 tracing::debug!("Handling goto definition request");
570
571 let _params: GotoDefinitionParams = serde_json::from_value(req.params)?;
572
573 let result = GotoDefinitionResponse::Array(vec![]);
575
576 let response = Response {
577 id: req.id,
578 result: Some(serde_json::to_value(result)?),
579 error: None,
580 };
581
582 self.connection
583 .sender
584 .send(Message::Response(response))
585 .map_err(|e| Error::MessageSend(e.to_string()))?;
586
587 Ok(())
588 }
589
590 pub fn analyze_document(&mut self, uri: &lsp_types::Url, language_id: &str) -> Result<()> {
592 tracing::debug!("Analyzing document: {} ({})", uri, language_id);
593
594 self.diagnostic_engine.clear(uri);
596
597 let diagnostics = self
598 .document_manager
599 .with_document(uri, |doc| {
600 match language_id {
601 "toml" => {
602 match self.toml_analyzer.parse(&doc.content) {
604 Ok(toml_doc) => {
605 let mut diagnostics = Vec::new();
606
607 let validation_diagnostics = self.toml_analyzer.validate(&toml_doc);
609 diagnostics.extend(validation_diagnostics);
610
611 diagnostics
612 }
613 Err(e) => {
614 tracing::error!("TOML parse error: {}", e);
616 vec![lsp_types::Diagnostic {
617 range: lsp_types::Range {
618 start: lsp_types::Position {
619 line: 0,
620 character: 0,
621 },
622 end: lsp_types::Position {
623 line: 0,
624 character: 0,
625 },
626 },
627 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
628 code: Some(lsp_types::NumberOrString::String(
629 "parse_error".to_string(),
630 )),
631 code_description: None,
632 source: Some("summer-lsp".to_string()),
633 message: format!("TOML parse error: {}", e),
634 related_information: None,
635 tags: None,
636 data: None,
637 }]
638 }
639 }
640 }
641 "rust" => {
642 vec![]
645 }
646 _ => {
647 tracing::debug!("Unsupported language: {}", language_id);
648 vec![]
649 }
650 }
651 })
652 .unwrap_or_default();
653
654 let filtered_diagnostics: Vec<_> = diagnostics
656 .into_iter()
657 .filter(|diag| {
658 if let Some(lsp_types::NumberOrString::String(code)) = &diag.code {
659 !self.config.diagnostics.is_disabled(code)
660 } else {
661 true
662 }
663 })
664 .collect();
665
666 for diagnostic in filtered_diagnostics {
668 self.diagnostic_engine.add(uri.clone(), diagnostic);
669 }
670
671 let _ = self.diagnostic_engine.publish(&self.connection, uri);
673 self.status.record_diagnostic();
674
675 Ok(())
676 }
677
678 fn handle_status_query(&self, req: Request) -> Result<()> {
682 tracing::debug!("Handling status query request");
683
684 let metrics = self.status.get_metrics();
685 let result = serde_json::to_value(metrics)?;
686
687 let response = Response {
688 id: req.id,
689 result: Some(result),
690 error: None,
691 };
692
693 self.connection
694 .sender
695 .send(Message::Response(response))
696 .map_err(|e| Error::MessageSend(e.to_string()))?;
697
698 Ok(())
699 }
700
701 fn handle_routes_request(&self, req: Request) -> Result<()> {
705 tracing::info!("Handling summer/routes request");
706
707 use crate::scanner::route::{RouteScanner, RoutesRequest, RoutesResponse};
708
709 let params: RoutesRequest = serde_json::from_value(req.params)?;
711 let project_path = std::path::Path::new(¶ms.app_path);
712
713 tracing::info!("Scanning routes in: {:?}", project_path);
714
715 let scanner = RouteScanner::new();
717
718 let routes = match scanner.scan_routes(project_path) {
720 Ok(routes) => {
721 tracing::info!("Successfully scanned {} routes", routes.len());
722 routes
723 }
724 Err(e) => {
725 tracing::error!("Failed to scan routes: {}", e);
726 Vec::new()
728 }
729 };
730
731 let response_data = RoutesResponse { routes };
733
734 tracing::info!(
735 "Sending response with {} routes",
736 response_data.routes.len()
737 );
738
739 let result = serde_json::to_value(response_data)?;
740
741 let response = Response {
742 id: req.id,
743 result: Some(result),
744 error: None,
745 };
746
747 self.connection
748 .sender
749 .send(Message::Response(response))
750 .map_err(|e| Error::MessageSend(e.to_string()))?;
751
752 Ok(())
753 }
754
755 fn handle_components_request(&self, req: Request) -> Result<()> {
759 tracing::info!("Handling summer/components request");
760
761 use crate::scanner::component::{ComponentScanner, ComponentsRequest, ComponentsResponse};
762
763 let params: ComponentsRequest = serde_json::from_value(req.params)?;
765 let project_path = std::path::Path::new(¶ms.app_path);
766
767 tracing::info!("Scanning components in: {:?}", project_path);
768 tracing::info!("Project path exists: {}", project_path.exists());
769 tracing::info!("Project path is dir: {}", project_path.is_dir());
770
771 let scanner = ComponentScanner::new();
773
774 let components = match scanner.scan_components(project_path) {
776 Ok(components) => {
777 tracing::info!("Successfully scanned {} components", components.len());
778 components
779 }
780 Err(e) => {
781 tracing::error!("Failed to scan components: {}", e);
782 tracing::error!("Error details: {:?}", e);
783 Vec::new()
785 }
786 };
787
788 let response_data = ComponentsResponse { components };
790
791 tracing::info!(
792 "Sending response with {} components",
793 response_data.components.len()
794 );
795
796 let result = serde_json::to_value(response_data)?;
797
798 let response = Response {
799 id: req.id,
800 result: Some(result),
801 error: None,
802 };
803
804 self.connection
805 .sender
806 .send(Message::Response(response))
807 .map_err(|e| Error::MessageSend(e.to_string()))?;
808
809 Ok(())
810 }
811
812 fn handle_jobs_request(&self, req: Request) -> Result<()> {
816 tracing::info!("Handling summer/jobs request");
817
818 use crate::scanner::job::{JobScanner, JobsRequest, JobsResponse};
819
820 let params: JobsRequest = serde_json::from_value(req.params)?;
822 let project_path = std::path::Path::new(¶ms.app_path);
823
824 tracing::info!("Scanning jobs in: {:?}", project_path);
825
826 let scanner = JobScanner::new();
828
829 let jobs = match scanner.scan_jobs(project_path) {
831 Ok(jobs) => {
832 tracing::info!("Successfully scanned {} jobs", jobs.len());
833 jobs
834 }
835 Err(e) => {
836 tracing::error!("Failed to scan jobs: {}", e);
837 Vec::new()
839 }
840 };
841
842 let response_data = JobsResponse { jobs };
844
845 tracing::info!("Sending response with {} jobs", response_data.jobs.len());
846
847 let result = serde_json::to_value(response_data)?;
848
849 let response = Response {
850 id: req.id,
851 result: Some(result),
852 error: None,
853 };
854
855 self.connection
856 .sender
857 .send(Message::Response(response))
858 .map_err(|e| Error::MessageSend(e.to_string()))?;
859
860 Ok(())
861 }
862
863 fn handle_plugins_request(&self, req: Request) -> Result<()> {
867 tracing::info!("Handling summer/plugins request");
868
869 use crate::scanner::plugin::{PluginScanner, PluginsRequest, PluginsResponse};
870
871 let params: PluginsRequest = serde_json::from_value(req.params)?;
873 let project_path = std::path::Path::new(¶ms.app_path);
874
875 tracing::info!("Scanning plugins in: {:?}", project_path);
876
877 let scanner = PluginScanner::new();
879
880 let plugins = match scanner.scan_plugins(project_path) {
882 Ok(plugins) => {
883 tracing::info!("Successfully scanned {} plugins", plugins.len());
884 plugins
885 }
886 Err(e) => {
887 tracing::error!("Failed to scan plugins: {}", e);
888 Vec::new()
890 }
891 };
892
893 let response_data = PluginsResponse { plugins };
895
896 tracing::info!(
897 "Sending response with {} plugins",
898 response_data.plugins.len()
899 );
900
901 let result = serde_json::to_value(response_data)?;
902
903 let response = Response {
904 id: req.id,
905 result: Some(result),
906 error: None,
907 };
908
909 self.connection
910 .sender
911 .send(Message::Response(response))
912 .map_err(|e| Error::MessageSend(e.to_string()))?;
913
914 Ok(())
915 }
916
917 fn handle_configurations_request(&self, req: Request) -> Result<()> {
921 tracing::debug!("Handling summer/configurations request");
922
923 use crate::scanner::config::{
924 ConfigScanner, ConfigurationsRequest, ConfigurationsResponse,
925 };
926
927 let params: ConfigurationsRequest = serde_json::from_value(req.params)?;
929 let project_path = std::path::Path::new(¶ms.app_path);
930
931 let scanner = ConfigScanner::new();
933
934 let configurations = match scanner.scan_configurations(project_path) {
936 Ok(configurations) => configurations,
937 Err(e) => {
938 tracing::error!("Failed to scan configurations: {}", e);
939 Vec::new()
941 }
942 };
943
944 let response_data = ConfigurationsResponse { configurations };
946 let result = serde_json::to_value(response_data)?;
947
948 let response = Response {
949 id: req.id,
950 result: Some(result),
951 error: None,
952 };
953
954 self.connection
955 .sender
956 .send(Message::Response(response))
957 .map_err(|e| Error::MessageSend(e.to_string()))?;
958
959 Ok(())
960 }
961
962 fn handle_document_symbol(&self, req: Request) -> Result<()> {
966 tracing::debug!("Handling textDocument/documentSymbol request");
967
968 let params: DocumentSymbolParams = serde_json::from_value(req.params)?;
969 let uri = ¶ms.text_document.uri;
970
971 let symbols = self.document_manager.with_document(uri, |doc| {
972 match doc.language_id.as_str() {
973 "toml" => {
974 self.extract_toml_symbols(&doc.content)
976 }
977 "rust" => {
978 self.extract_rust_symbols(&doc.content)
980 }
981 _ => {
982 tracing::debug!(
983 "Unsupported language for document symbols: {}",
984 doc.language_id
985 );
986 vec![]
987 }
988 }
989 });
990
991 let result = match symbols {
992 Some(symbols) => serde_json::to_value(DocumentSymbolResponse::Nested(symbols))?,
993 None => serde_json::Value::Null,
994 };
995
996 let response = Response {
997 id: req.id,
998 result: Some(result),
999 error: None,
1000 };
1001
1002 self.connection
1003 .sender
1004 .send(Message::Response(response))
1005 .map_err(|e| Error::MessageSend(e.to_string()))?;
1006
1007 Ok(())
1008 }
1009
1010 fn handle_workspace_symbol(&self, req: Request) -> Result<()> {
1014 tracing::debug!("Handling workspace/symbol request");
1015
1016 let params: serde_json::Value = req.params;
1018 let query = params
1019 .get("query")
1020 .and_then(|v| v.as_str())
1021 .unwrap_or("")
1022 .to_lowercase();
1023
1024 tracing::debug!("Workspace symbol query: '{}'", query);
1025
1026 let mut symbols: Vec<lsp_types::SymbolInformation> = vec![];
1027
1028 if let Some(workspace_path) = &self.workspace_path {
1030 if let Ok(component_symbols) = self.search_component_symbols(workspace_path, &query) {
1032 symbols.extend(component_symbols);
1033 }
1034
1035 if let Ok(route_symbols) = self.search_route_symbols(workspace_path, &query) {
1037 symbols.extend(route_symbols);
1038 }
1039
1040 if let Ok(config_symbols) = self.search_config_symbols(workspace_path, &query) {
1042 symbols.extend(config_symbols);
1043 }
1044 }
1045
1046 tracing::debug!(
1047 "Found {} workspace symbols matching '{}'",
1048 symbols.len(),
1049 query
1050 );
1051
1052 let result = serde_json::to_value(symbols)?;
1053
1054 let response = Response {
1055 id: req.id,
1056 result: Some(result),
1057 error: None,
1058 };
1059
1060 self.connection
1061 .sender
1062 .send(Message::Response(response))
1063 .map_err(|e| Error::MessageSend(e.to_string()))?;
1064
1065 Ok(())
1066 }
1067
1068 fn search_component_symbols(
1070 &self,
1071 workspace_path: &std::path::Path,
1072 query: &str,
1073 ) -> Result<Vec<lsp_types::SymbolInformation>> {
1074 use crate::scanner::component::{ComponentScanner, ComponentSource};
1075 use lsp_types::{Location, Position, Range, SymbolInformation, SymbolKind, Url};
1076
1077 let scanner = ComponentScanner::new();
1078 let components = scanner
1079 .scan_components(workspace_path)
1080 .map_err(|e| Error::Other(anyhow::anyhow!("Failed to scan components: {}", e)))?;
1081
1082 let mut symbols = Vec::new();
1083
1084 for component in components {
1085 if !query.is_empty() && !component.name.to_lowercase().contains(query) {
1087 continue;
1088 }
1089
1090 let uri = Url::parse(&component.location.uri)
1092 .map_err(|e| Error::Other(anyhow::anyhow!("Invalid URI: {}", e)))?;
1093
1094 let range = Range {
1095 start: Position {
1096 line: component.location.range.start.line,
1097 character: component.location.range.start.character,
1098 },
1099 end: Position {
1100 line: component.location.range.end.line,
1101 character: component.location.range.end.character,
1102 },
1103 };
1104
1105 let kind = match component.source {
1109 ComponentSource::Component => SymbolKind::METHOD,
1110 ComponentSource::Service => SymbolKind::CLASS,
1111 };
1112
1113 #[allow(deprecated)]
1114 symbols.push(SymbolInformation {
1115 name: component.name.clone(),
1116 kind,
1117 tags: None,
1118 deprecated: None,
1119 location: Location { uri, range },
1120 container_name: Some(format!("Component ({})", component.type_name)),
1121 });
1122 }
1123
1124 Ok(symbols)
1125 }
1126
1127 fn search_route_symbols(
1129 &self,
1130 workspace_path: &std::path::Path,
1131 query: &str,
1132 ) -> Result<Vec<lsp_types::SymbolInformation>> {
1133 use crate::scanner::route::RouteScanner;
1134 use lsp_types::{Location, Position, Range, SymbolInformation, SymbolKind, Url};
1135
1136 let scanner = RouteScanner::new();
1137 let routes = scanner
1138 .scan_routes(workspace_path)
1139 .map_err(|e| Error::Other(anyhow::anyhow!("Failed to scan routes: {}", e)))?;
1140
1141 let mut symbols = Vec::new();
1142
1143 for route in routes {
1144 let search_text = format!("{} {}", route.method, route.path).to_lowercase();
1146
1147 if !query.is_empty() && !search_text.contains(query) {
1149 continue;
1150 }
1151
1152 let uri = Url::parse(&route.location.uri)
1154 .map_err(|e| Error::Other(anyhow::anyhow!("Invalid URI: {}", e)))?;
1155
1156 let range = Range {
1157 start: Position {
1158 line: route.location.range.start.line,
1159 character: route.location.range.start.character,
1160 },
1161 end: Position {
1162 line: route.location.range.end.line,
1163 character: route.location.range.end.character,
1164 },
1165 };
1166
1167 #[allow(deprecated)]
1168 symbols.push(SymbolInformation {
1169 name: format!("{} {}", route.method, route.path),
1170 kind: SymbolKind::FUNCTION,
1171 tags: None,
1172 deprecated: None,
1173 location: Location { uri, range },
1174 container_name: Some(format!("Route ({})", route.handler)),
1175 });
1176 }
1177
1178 Ok(symbols)
1179 }
1180
1181 fn search_config_symbols(
1183 &self,
1184 workspace_path: &std::path::Path,
1185 query: &str,
1186 ) -> Result<Vec<lsp_types::SymbolInformation>> {
1187 use crate::scanner::config::ConfigScanner;
1188 use lsp_types::{SymbolInformation, SymbolKind};
1189
1190 let scanner = ConfigScanner::new();
1191 let configs = scanner
1192 .scan_configurations(workspace_path)
1193 .map_err(|e| Error::Other(anyhow::anyhow!("Failed to scan configurations: {}", e)))?;
1194
1195 let mut symbols = Vec::new();
1196
1197 for config in configs {
1198 if !query.is_empty() && !config.name.to_lowercase().contains(query) {
1200 continue;
1201 }
1202
1203 if let Some(location) = config.location {
1205 #[allow(deprecated)]
1206 symbols.push(SymbolInformation {
1207 name: config.name.clone(),
1208 kind: SymbolKind::STRUCT,
1209 tags: None,
1210 deprecated: None,
1211 location,
1212 container_name: Some(format!("Config [{}]", config.prefix)),
1213 });
1214 }
1215 }
1216
1217 Ok(symbols)
1218 }
1219
1220 fn extract_toml_symbols(&self, content: &str) -> Vec<lsp_types::DocumentSymbol> {
1222 use lsp_types::{DocumentSymbol, Position, Range, SymbolKind};
1223
1224 let mut symbols = Vec::new();
1225
1226 let parse_result = taplo::parser::parse(content);
1228 let root = parse_result.into_dom();
1229
1230 if let taplo::dom::Node::Table(table) = root {
1232 let entries = table.entries();
1233 let entries_arc = entries.get();
1234
1235 for (key, value) in entries_arc.iter() {
1237 let key_str = key.value().to_string();
1238
1239 let key_range = Range {
1241 start: Position {
1242 line: 0,
1243 character: 0,
1244 },
1245 end: Position {
1246 line: 0,
1247 character: key_str.len() as u32,
1248 },
1249 };
1250
1251 match value {
1252 taplo::dom::Node::Table(inner_table) => {
1253 let mut children = Vec::new();
1255
1256 let inner_entries = inner_table.entries();
1258 let inner_entries_arc = inner_entries.get();
1259 for (prop_key, _prop_value) in inner_entries_arc.iter() {
1260 let prop_key_str = prop_key.value().to_string();
1261
1262 let prop_symbol = DocumentSymbol {
1263 name: prop_key_str.clone(),
1264 detail: Some("Property".to_string()),
1265 kind: SymbolKind::PROPERTY,
1266 tags: None,
1267 #[allow(deprecated)]
1268 deprecated: None,
1269 range: key_range,
1270 selection_range: key_range,
1271 children: None,
1272 };
1273 children.push(prop_symbol);
1274 }
1275
1276 let symbol = DocumentSymbol {
1277 name: key_str.clone(),
1278 detail: Some("Configuration section".to_string()),
1279 kind: SymbolKind::MODULE,
1280 tags: None,
1281 #[allow(deprecated)]
1282 deprecated: None,
1283 range: key_range,
1284 selection_range: key_range,
1285 children: if children.is_empty() {
1286 None
1287 } else {
1288 Some(children)
1289 },
1290 };
1291 symbols.push(symbol);
1292 }
1293 taplo::dom::Node::Array(_) => {
1294 let symbol = DocumentSymbol {
1296 name: key_str.clone(),
1297 detail: Some("Array".to_string()),
1298 kind: SymbolKind::ARRAY,
1299 tags: None,
1300 #[allow(deprecated)]
1301 deprecated: None,
1302 range: key_range,
1303 selection_range: key_range,
1304 children: None,
1305 };
1306 symbols.push(symbol);
1307 }
1308 _ => {
1309 let symbol = DocumentSymbol {
1311 name: key_str.clone(),
1312 detail: Some("Property".to_string()),
1313 kind: SymbolKind::PROPERTY,
1314 tags: None,
1315 #[allow(deprecated)]
1316 deprecated: None,
1317 range: key_range,
1318 selection_range: key_range,
1319 children: None,
1320 };
1321 symbols.push(symbol);
1322 }
1323 }
1324 }
1325 }
1326
1327 symbols
1328 }
1329
1330 fn extract_rust_symbols(&self, _content: &str) -> Vec<lsp_types::DocumentSymbol> {
1332 use lsp_types::{DocumentSymbol, Range, SymbolKind};
1333
1334 let mut symbols = Vec::new();
1335
1336 let syntax = match syn::parse_file(_content) {
1338 Ok(syntax) => syntax,
1339 Err(e) => {
1340 tracing::warn!("Failed to parse Rust file: {}", e);
1341 return symbols;
1342 }
1343 };
1344
1345 let default_range = Range::default();
1347
1348 for item in syntax.items {
1350 match item {
1351 syn::Item::Fn(item_fn) => {
1352 let name = item_fn.sig.ident.to_string();
1354
1355 let symbol = DocumentSymbol {
1356 name: name.clone(),
1357 detail: Some(format!("fn {}", name)),
1358 kind: SymbolKind::FUNCTION,
1359 tags: None,
1360 #[allow(deprecated)]
1361 deprecated: None,
1362 range: default_range,
1363 selection_range: default_range,
1364 children: None,
1365 };
1366 symbols.push(symbol);
1367 }
1368 syn::Item::Struct(item_struct) => {
1369 let name = item_struct.ident.to_string();
1371
1372 let symbol = DocumentSymbol {
1373 name: name.clone(),
1374 detail: Some(format!("struct {}", name)),
1375 kind: SymbolKind::STRUCT,
1376 tags: None,
1377 #[allow(deprecated)]
1378 deprecated: None,
1379 range: default_range,
1380 selection_range: default_range,
1381 children: None,
1382 };
1383 symbols.push(symbol);
1384 }
1385 syn::Item::Enum(item_enum) => {
1386 let name = item_enum.ident.to_string();
1388
1389 let symbol = DocumentSymbol {
1390 name: name.clone(),
1391 detail: Some(format!("enum {}", name)),
1392 kind: SymbolKind::ENUM,
1393 tags: None,
1394 #[allow(deprecated)]
1395 deprecated: None,
1396 range: default_range,
1397 selection_range: default_range,
1398 children: None,
1399 };
1400 symbols.push(symbol);
1401 }
1402 syn::Item::Trait(item_trait) => {
1403 let name = item_trait.ident.to_string();
1405
1406 let symbol = DocumentSymbol {
1407 name: name.clone(),
1408 detail: Some(format!("trait {}", name)),
1409 kind: SymbolKind::INTERFACE,
1410 tags: None,
1411 #[allow(deprecated)]
1412 deprecated: None,
1413 range: default_range,
1414 selection_range: default_range,
1415 children: None,
1416 };
1417 symbols.push(symbol);
1418 }
1419 syn::Item::Impl(item_impl) => {
1420 if let Some((_, path, _)) = &item_impl.trait_ {
1422 let name = quote::quote!(#path).to_string();
1423 let symbol = DocumentSymbol {
1424 name: format!("impl {}", name),
1425 detail: Some("Implementation".to_string()),
1426 kind: SymbolKind::CLASS,
1427 tags: None,
1428 #[allow(deprecated)]
1429 deprecated: None,
1430 range: default_range,
1431 selection_range: default_range,
1432 children: None,
1433 };
1434 symbols.push(symbol);
1435 } else if let syn::Type::Path(type_path) = &*item_impl.self_ty {
1436 let name = quote::quote!(#type_path).to_string();
1437 let symbol = DocumentSymbol {
1438 name: format!("impl {}", name),
1439 detail: Some("Implementation".to_string()),
1440 kind: SymbolKind::CLASS,
1441 tags: None,
1442 #[allow(deprecated)]
1443 deprecated: None,
1444 range: default_range,
1445 selection_range: default_range,
1446 children: None,
1447 };
1448 symbols.push(symbol);
1449 }
1450 }
1451 _ => {
1452 }
1454 }
1455 }
1456
1457 symbols
1458 }
1459
1460 pub fn handle_initialize(&mut self, params: InitializeParams) -> Result<InitializeResult> {
1470 use lsp_types::{
1471 CompletionOptions, HoverProviderCapability, OneOf, TextDocumentSyncCapability,
1472 TextDocumentSyncKind, TextDocumentSyncOptions, WorkDoneProgressOptions,
1473 };
1474
1475 #[allow(deprecated)]
1477 if let Some(root_uri) = params.root_uri {
1478 if let Ok(workspace_path) = root_uri.to_file_path() {
1479 tracing::info!(
1480 "Loading configuration from workspace: {}",
1481 workspace_path.display()
1482 );
1483
1484 self.workspace_path = Some(workspace_path.clone());
1486
1487 self.config = ServerConfig::load(Some(&workspace_path));
1488
1489 if let Err(e) = self.config.validate() {
1491 tracing::error!("Invalid configuration: {}", e);
1492 return Err(Error::Config(e));
1493 }
1494
1495 tracing::info!("Configuration loaded successfully");
1496 tracing::debug!(
1497 "Trigger characters: {:?}",
1498 self.config.completion.trigger_characters
1499 );
1500 tracing::debug!("Schema URL: {}", self.config.schema.url);
1501 tracing::debug!(
1502 "Disabled diagnostics: {:?}",
1503 self.config.diagnostics.disabled
1504 );
1505 }
1506 }
1507
1508 Ok(InitializeResult {
1509 capabilities: ServerCapabilities {
1510 text_document_sync: Some(TextDocumentSyncCapability::Options(
1512 TextDocumentSyncOptions {
1513 open_close: Some(true),
1514 change: Some(TextDocumentSyncKind::INCREMENTAL),
1515 will_save: None,
1516 will_save_wait_until: None,
1517 save: None,
1518 },
1519 )),
1520
1521 completion_provider: Some(CompletionOptions {
1525 resolve_provider: Some(true),
1526 trigger_characters: Some(self.config.completion.trigger_characters.clone()),
1527 all_commit_characters: None,
1528 work_done_progress_options: WorkDoneProgressOptions {
1529 work_done_progress: None,
1530 },
1531 completion_item: None,
1532 }),
1533
1534 hover_provider: Some(HoverProviderCapability::Simple(true)),
1537
1538 definition_provider: Some(OneOf::Left(true)),
1541
1542 document_symbol_provider: Some(OneOf::Left(true)),
1545
1546 workspace_symbol_provider: Some(OneOf::Left(true)),
1549
1550 ..Default::default()
1562 },
1563 server_info: Some(ServerInfo {
1564 name: "summer-lsp".to_string(),
1565 version: Some(env!("CARGO_PKG_VERSION").to_string()),
1566 }),
1567 })
1568 }
1569
1570 fn send_error_response(&self, id: RequestId, code: i32, message: String) -> Result<()> {
1572 let response = Response {
1573 id,
1574 result: None,
1575 error: Some(lsp_server::ResponseError {
1576 code,
1577 message,
1578 data: None,
1579 }),
1580 };
1581
1582 self.connection
1583 .sender
1584 .send(Message::Response(response))
1585 .map_err(|e| Error::MessageSend(e.to_string()))?;
1586
1587 Ok(())
1588 }
1589
1590 fn notify_client_error(&self, error: &Error) -> Result<()> {
1594 use lsp_types::{MessageType, ShowMessageParams};
1595
1596 let message_type = match error.severity() {
1597 crate::error::ErrorSeverity::Error => MessageType::ERROR,
1598 crate::error::ErrorSeverity::Warning => MessageType::WARNING,
1599 crate::error::ErrorSeverity::Info => MessageType::INFO,
1600 };
1601
1602 let params = ShowMessageParams {
1603 typ: message_type,
1604 message: error.to_string(),
1605 };
1606
1607 let notification = Notification {
1608 method: "window/showMessage".to_string(),
1609 params: serde_json::to_value(params)?,
1610 };
1611
1612 self.connection
1613 .sender
1614 .send(Message::Notification(notification))
1615 .map_err(|e| Error::MessageSend(e.to_string()))?;
1616
1617 Ok(())
1618 }
1619
1620 pub fn shutdown(&mut self) -> Result<()> {
1622 tracing::info!("Shutting down summer-lsp server");
1623
1624 tracing::debug!("Clearing all diagnostics...");
1626 tracing::debug!("Clearing document cache...");
1630 tracing::debug!("Clearing indexes...");
1633 tracing::info!("Server shutdown complete");
1636 Ok(())
1637 }
1638}
1639
1640impl Default for LspServer {
1641 fn default() -> Self {
1642 Self::start().expect("Failed to start LSP server")
1643 }
1644}
1645
1646#[cfg(test)]
1647mod tests {
1648 use super::*;
1649 use lsp_types::{
1650 ClientCapabilities, ClientInfo, InitializeParams, TextDocumentItem, Url,
1651 VersionedTextDocumentIdentifier, WorkDoneProgressParams,
1652 };
1653
1654 #[test]
1656 fn test_server_state_transitions() {
1657 let server = LspServer::new_for_test().unwrap();
1659 assert_eq!(server.state, ServerState::Uninitialized);
1660 }
1661
1662 #[test]
1664 fn test_document_open() {
1665 let mut server = LspServer::new_for_test().unwrap();
1666 server.state = ServerState::Initialized;
1667
1668 let uri = Url::parse("file:///test.toml").unwrap();
1669 let params = DidOpenTextDocumentParams {
1670 text_document: TextDocumentItem {
1671 uri: uri.clone(),
1672 language_id: "toml".to_string(),
1673 version: 1,
1674 text: "host = \"localhost\"".to_string(),
1675 },
1676 };
1677
1678 server.handle_did_open(params).unwrap();
1679
1680 let doc = server.document_manager.get(&uri);
1682 assert!(doc.is_some());
1683 let doc = doc.unwrap();
1684 assert_eq!(doc.version, 1);
1685 assert_eq!(doc.content, "host = \"localhost\"");
1686 assert_eq!(doc.language_id, "toml");
1687 }
1688
1689 #[test]
1691 fn test_document_change() {
1692 let mut server = LspServer::new_for_test().unwrap();
1693 server.state = ServerState::Initialized;
1694
1695 let uri = Url::parse("file:///test.toml").unwrap();
1696
1697 let open_params = DidOpenTextDocumentParams {
1699 text_document: TextDocumentItem {
1700 uri: uri.clone(),
1701 language_id: "toml".to_string(),
1702 version: 1,
1703 text: "host = \"localhost\"".to_string(),
1704 },
1705 };
1706 server.handle_did_open(open_params).unwrap();
1707
1708 let change_params = DidChangeTextDocumentParams {
1710 text_document: VersionedTextDocumentIdentifier {
1711 uri: uri.clone(),
1712 version: 2,
1713 },
1714 content_changes: vec![lsp_types::TextDocumentContentChangeEvent {
1715 range: None,
1716 range_length: None,
1717 text: "host = \"127.0.0.1\"".to_string(),
1718 }],
1719 };
1720 server.handle_did_change(change_params).unwrap();
1721
1722 let doc = server.document_manager.get(&uri).unwrap();
1724 assert_eq!(doc.version, 2);
1725 assert_eq!(doc.content, "host = \"127.0.0.1\"");
1726 }
1727
1728 #[test]
1730 fn test_document_close() {
1731 let mut server = LspServer::new_for_test().unwrap();
1732 server.state = ServerState::Initialized;
1733
1734 let uri = Url::parse("file:///test.toml").unwrap();
1735
1736 let open_params = DidOpenTextDocumentParams {
1738 text_document: TextDocumentItem {
1739 uri: uri.clone(),
1740 language_id: "toml".to_string(),
1741 version: 1,
1742 text: "host = \"localhost\"".to_string(),
1743 },
1744 };
1745 server.handle_did_open(open_params).unwrap();
1746
1747 assert!(server.document_manager.get(&uri).is_some());
1749
1750 let close_params = DidCloseTextDocumentParams {
1752 text_document: lsp_types::TextDocumentIdentifier { uri: uri.clone() },
1753 };
1754 server.handle_did_close(close_params).unwrap();
1755
1756 assert!(server.document_manager.get(&uri).is_none());
1758 }
1759
1760 #[test]
1762 fn test_initialize_response() {
1763 let mut server = LspServer::new_for_test().unwrap();
1764
1765 #[allow(deprecated)]
1766 let params = InitializeParams {
1767 process_id: Some(1234),
1768 root_uri: None,
1769 capabilities: ClientCapabilities::default(),
1770 client_info: Some(ClientInfo {
1771 name: "test-client".to_string(),
1772 version: Some("1.0.0".to_string()),
1773 }),
1774 locale: None,
1775 root_path: None,
1776 initialization_options: None,
1777 trace: None,
1778 workspace_folders: Some(vec![lsp_types::WorkspaceFolder {
1779 uri: Url::parse("file:///workspace").unwrap(),
1780 name: "workspace".to_string(),
1781 }]),
1782 work_done_progress_params: WorkDoneProgressParams::default(),
1783 };
1784
1785 let result = server.handle_initialize(params).unwrap();
1786
1787 assert!(result.server_info.is_some());
1789 let server_info = result.server_info.unwrap();
1790 assert_eq!(server_info.name, "summer-lsp");
1791 assert!(server_info.version.is_some());
1792
1793 let capabilities = result.capabilities;
1795
1796 assert!(capabilities.text_document_sync.is_some());
1798 if let Some(lsp_types::TextDocumentSyncCapability::Options(sync_options)) =
1799 capabilities.text_document_sync
1800 {
1801 assert_eq!(sync_options.open_close, Some(true));
1802 assert_eq!(
1803 sync_options.change,
1804 Some(lsp_types::TextDocumentSyncKind::INCREMENTAL)
1805 );
1806 } else {
1807 panic!("Expected TextDocumentSyncOptions");
1808 }
1809
1810 assert!(capabilities.completion_provider.is_some());
1812 let completion = capabilities.completion_provider.unwrap();
1813 assert_eq!(completion.resolve_provider, Some(true));
1814 assert!(completion.trigger_characters.is_some());
1815 let triggers = completion.trigger_characters.unwrap();
1816 assert!(triggers.contains(&"[".to_string()));
1817 assert!(triggers.contains(&"$".to_string()));
1818 assert!(triggers.contains(&"{".to_string()));
1819
1820 assert!(capabilities.hover_provider.is_some());
1822
1823 assert!(capabilities.definition_provider.is_some());
1825
1826 assert!(capabilities.document_symbol_provider.is_some());
1828
1829 assert!(capabilities.workspace_symbol_provider.is_some());
1831 }
1832
1833 #[test]
1835 fn test_error_recovery() {
1836 let mut server = LspServer::new_for_test().unwrap();
1837 server.state = ServerState::Initialized;
1838
1839 let uri = Url::parse("file:///nonexistent.toml").unwrap();
1841 let change_params = DidChangeTextDocumentParams {
1842 text_document: VersionedTextDocumentIdentifier {
1843 uri: uri.clone(),
1844 version: 1,
1845 },
1846 content_changes: vec![lsp_types::TextDocumentContentChangeEvent {
1847 range: None,
1848 range_length: None,
1849 text: "test".to_string(),
1850 }],
1851 };
1852
1853 let result = server.handle_did_change(change_params);
1855 assert!(result.is_ok());
1856
1857 assert!(server.document_manager.get(&uri).is_none());
1859 }
1860}