1use crate::completion::CompletionEngine;
46use crate::config::ServerConfig;
47use crate::diagnostic::DiagnosticEngine;
48use crate::document::DocumentManager;
49use crate::error::{ErrorHandler, RecoveryAction};
50use crate::index::IndexManager;
51use crate::macro_analyzer::MacroAnalyzer;
52use crate::route::RouteNavigator;
53use crate::schema::SchemaProvider;
54use crate::status::ServerStatus;
55use crate::toml_analyzer::TomlAnalyzer;
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, GotoDefinition, HoverRequest, Request as _},
63 CompletionParams, CompletionResponse, DidChangeTextDocumentParams, DidCloseTextDocumentParams,
64 DidOpenTextDocumentParams, GotoDefinitionParams, GotoDefinitionResponse, HoverParams,
65 InitializeParams, InitializeResult, ServerCapabilities, ServerInfo,
66};
67use std::sync::Arc;
68
69#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub enum ServerState {
72 Uninitialized,
74 Initialized,
76 ShuttingDown,
78}
79
80pub struct LspServer {
82 connection: Connection,
84 pub state: ServerState,
86 pub document_manager: Arc<DocumentManager>,
88 error_handler: ErrorHandler,
90 pub config: ServerConfig,
92 pub status: ServerStatus,
94 pub schema_provider: Arc<SchemaProvider>,
96 pub toml_analyzer: Arc<TomlAnalyzer>,
98 pub macro_analyzer: Arc<MacroAnalyzer>,
100 pub route_navigator: Arc<RouteNavigator>,
102 pub completion_engine: Arc<CompletionEngine>,
104 pub diagnostic_engine: Arc<DiagnosticEngine>,
106 pub index_manager: Arc<IndexManager>,
108}
109
110impl LspServer {
111 pub fn start() -> Result<Self> {
115 tracing::info!("Starting spring-lsp server");
116
117 let (connection, _io_threads) = Connection::stdio();
119
120 Self::new_with_connection(connection)
121 }
122
123 pub fn new_for_test() -> Result<Self> {
125 let (connection, _io_threads) = Connection::memory();
128
129 Self::new_with_connection(connection)
130 }
131
132 fn new_with_connection(connection: Connection) -> Result<Self> {
134 let config = ServerConfig::load(None);
136
137 if let Err(e) = config.validate() {
139 tracing::error!("Invalid configuration: {}", e);
140 return Err(Error::Config(e));
141 }
142
143 let verbose = config.logging.verbose;
145
146 tracing::info!("Initializing components...");
148
149 let schema_provider = Arc::new(SchemaProvider::default());
151
152 let toml_analyzer = Arc::new(TomlAnalyzer::new((*schema_provider).clone()));
154
155 let macro_analyzer = Arc::new(MacroAnalyzer::new());
157
158 let route_navigator = Arc::new(RouteNavigator::new());
160
161 let completion_engine = Arc::new(CompletionEngine::new((*schema_provider).clone()));
163
164 let diagnostic_engine = Arc::new(DiagnosticEngine::new());
166
167 let index_manager = Arc::new(IndexManager::new());
169
170 tracing::info!("All components initialized successfully");
171
172 Ok(Self {
173 connection,
174 state: ServerState::Uninitialized,
175 document_manager: Arc::new(DocumentManager::new()),
176 error_handler: ErrorHandler::new(verbose),
177 config,
178 status: ServerStatus::new(),
179 schema_provider,
180 toml_analyzer,
181 macro_analyzer,
182 route_navigator,
183 completion_engine,
184 diagnostic_engine,
185 index_manager,
186 })
187 }
188
189 pub fn run(&mut self) -> Result<()> {
193 self.initialize()?;
195
196 self.event_loop()?;
198
199 self.shutdown()?;
201
202 Ok(())
203 }
204
205 fn initialize(&mut self) -> Result<()> {
207 tracing::info!("Waiting for initialize request");
208
209 let (id, params) = self.connection.initialize_start()?;
210 let init_params: InitializeParams = serde_json::from_value(params)?;
211
212 tracing::info!(
213 "Received initialize request from client: {:?}",
214 init_params.client_info
215 );
216
217 let init_result = self.handle_initialize(init_params)?;
218 let init_result_json = serde_json::to_value(init_result)?;
219
220 self.connection.initialize_finish(id, init_result_json)?;
221
222 self.state = ServerState::Initialized;
223 tracing::info!("LSP server initialized successfully");
224
225 Ok(())
226 }
227
228 fn event_loop(&mut self) -> Result<()> {
232 tracing::info!("Entering main event loop");
233
234 loop {
235 if self.state == ServerState::ShuttingDown {
237 tracing::info!("Server is shutting down, stopping event loop");
238 break;
239 }
240
241 let msg = match self.connection.receiver.recv() {
243 Ok(msg) => msg,
244 Err(e) => {
245 let error = Error::MessageReceive(e.to_string());
246 let result = self.error_handler.handle(&error);
247
248 match result.action {
249 RecoveryAction::RetryConnection => {
250 tracing::info!("Attempting to recover connection...");
251 std::thread::sleep(std::time::Duration::from_millis(100));
253 continue;
254 }
255 RecoveryAction::Abort => {
256 tracing::error!("Fatal error receiving message, shutting down");
257 break;
258 }
259 _ => {
260 tracing::warn!("Unexpected recovery action for message receive error");
261 break;
262 }
263 }
264 }
265 };
266
267 if let Err(e) = self.handle_message(msg) {
269 self.status.record_error();
271
272 let result = self.error_handler.handle(&e);
273
274 match result.action {
276 RecoveryAction::Abort => {
277 tracing::error!("Fatal error, shutting down server");
278 self.state = ServerState::ShuttingDown;
279 break;
280 }
281 _ => {
282 if result.notify_client {
284 if let Err(notify_err) = self.notify_client_error(&e) {
286 tracing::error!(
287 "Failed to notify client about error: {}",
288 notify_err
289 );
290 }
291 }
292 }
293 }
294 }
295 }
296
297 Ok(())
298 }
299
300 fn handle_message(&mut self, msg: Message) -> Result<()> {
302 match msg {
303 Message::Request(req) => self.handle_request(req),
304 Message::Response(resp) => {
305 tracing::debug!("Received response: {:?}", resp.id);
306 Ok(())
308 }
309 Message::Notification(not) => self.handle_notification(not),
310 }
311 }
312
313 fn handle_request(&mut self, req: Request) -> Result<()> {
315 tracing::debug!("Received request: {} (id: {:?})", req.method, req.id);
316
317 self.status.record_request();
319
320 if self.connection.handle_shutdown(&req)? {
322 tracing::info!("Received shutdown request");
323 self.state = ServerState::ShuttingDown;
324 return Ok(());
325 }
326
327 match req.method.as_str() {
329 Completion::METHOD => self.handle_completion(req),
331 HoverRequest::METHOD => self.handle_hover(req),
333 GotoDefinition::METHOD => self.handle_goto_definition(req),
335 "spring-lsp/status" => self.handle_status_query(req),
337 _ => {
338 tracing::warn!("Unhandled request method: {}", req.method);
339 self.send_error_response(
341 req.id,
342 lsp_server::ErrorCode::MethodNotFound as i32,
343 format!("Method not found: {}", req.method),
344 )
345 }
346 }?;
347
348 Ok(())
349 }
350
351 fn handle_notification(&mut self, not: Notification) -> Result<()> {
353 tracing::debug!("Received notification: {}", not.method);
354
355 match not.method.as_str() {
356 DidOpenTextDocument::METHOD => {
357 let params: DidOpenTextDocumentParams = serde_json::from_value(not.params)?;
358 self.handle_did_open(params)?;
359 }
360 DidChangeTextDocument::METHOD => {
361 let params: DidChangeTextDocumentParams = serde_json::from_value(not.params)?;
362 self.handle_did_change(params)?;
363 }
364 DidCloseTextDocument::METHOD => {
365 let params: DidCloseTextDocumentParams = serde_json::from_value(not.params)?;
366 self.handle_did_close(params)?;
367 }
368 Exit::METHOD => {
369 tracing::info!("Received exit notification");
370 self.state = ServerState::ShuttingDown;
371 }
372 _ => {
373 tracing::debug!("Unhandled notification method: {}", not.method);
374 }
375 }
376
377 Ok(())
378 }
379
380 pub fn handle_did_open(&mut self, params: DidOpenTextDocumentParams) -> Result<()> {
382 let doc = params.text_document;
383 tracing::info!("Document opened: {}", doc.uri);
384
385 self.document_manager.open(
386 doc.uri.clone(),
387 doc.version,
388 doc.text,
389 doc.language_id.clone(),
390 );
391
392 self.status.increment_document_count();
394
395 self.analyze_document(&doc.uri, &doc.language_id)?;
397
398 Ok(())
399 }
400
401 pub fn handle_did_change(&mut self, params: DidChangeTextDocumentParams) -> Result<()> {
403 let uri = params.text_document.uri;
404 let version = params.text_document.version;
405 tracing::debug!("Document changed: {} (version: {})", uri, version);
406
407 self.document_manager
408 .change(&uri, version, params.content_changes);
409
410 if let Some(doc) = self.document_manager.get(&uri) {
412 self.analyze_document(&uri, &doc.language_id)?;
413 }
414
415 Ok(())
416 }
417
418 pub fn handle_did_close(&mut self, params: DidCloseTextDocumentParams) -> Result<()> {
420 let uri = params.text_document.uri;
421 tracing::info!("Document closed: {}", uri);
422
423 self.document_manager.close(&uri);
424
425 self.status.decrement_document_count();
427
428 self.diagnostic_engine.clear(&uri);
430 let _ = self.diagnostic_engine.publish(&self.connection, &uri);
431
432 Ok(())
433 }
434
435 fn handle_completion(&mut self, req: Request) -> Result<()> {
437 tracing::debug!("Handling completion request");
438
439 let params: CompletionParams = serde_json::from_value(req.params)?;
440 self.status.record_completion();
441
442 let response = self.document_manager.with_document(
443 ¶ms.text_document_position.text_document.uri,
444 |doc| {
445 match doc.language_id.as_str() {
447 "toml" => {
448 if let Ok(toml_doc) = self.toml_analyzer.parse(&doc.content) {
449 self.completion_engine.complete_toml_document(
450 &toml_doc,
451 params.text_document_position.position,
452 )
453 } else {
454 vec![]
455 }
456 }
457 "rust" => {
458 vec![]
460 }
461 _ => vec![],
462 }
463 },
464 );
465
466 let result = match response {
467 Some(completions) => serde_json::to_value(CompletionResponse::Array(completions))?,
468 None => serde_json::Value::Null,
469 };
470
471 let response = Response {
472 id: req.id,
473 result: Some(result),
474 error: None,
475 };
476
477 self.connection
478 .sender
479 .send(Message::Response(response))
480 .map_err(|e| Error::MessageSend(e.to_string()))?;
481
482 Ok(())
483 }
484
485 fn handle_hover(&mut self, req: Request) -> Result<()> {
487 tracing::debug!("Handling hover request");
488
489 let params: HoverParams = serde_json::from_value(req.params)?;
490 self.status.record_hover();
491
492 let response = self.document_manager.with_document(
493 ¶ms.text_document_position_params.text_document.uri,
494 |doc| {
495 match doc.language_id.as_str() {
497 "toml" => {
498 if let Ok(toml_doc) = self.toml_analyzer.parse(&doc.content) {
499 self.toml_analyzer
500 .hover(&toml_doc, params.text_document_position_params.position)
501 } else {
502 None
503 }
504 }
505 "rust" => {
506 None
508 }
509 _ => None,
510 }
511 },
512 );
513
514 let result = match response {
515 Some(Some(hover)) => serde_json::to_value(hover)?,
516 _ => serde_json::Value::Null,
517 };
518
519 let response = Response {
520 id: req.id,
521 result: Some(result),
522 error: None,
523 };
524
525 self.connection
526 .sender
527 .send(Message::Response(response))
528 .map_err(|e| Error::MessageSend(e.to_string()))?;
529
530 Ok(())
531 }
532
533 fn handle_goto_definition(&mut self, req: Request) -> Result<()> {
535 tracing::debug!("Handling goto definition request");
536
537 let _params: GotoDefinitionParams = serde_json::from_value(req.params)?;
538
539 let result = GotoDefinitionResponse::Array(vec![]);
541
542 let response = Response {
543 id: req.id,
544 result: Some(serde_json::to_value(result)?),
545 error: None,
546 };
547
548 self.connection
549 .sender
550 .send(Message::Response(response))
551 .map_err(|e| Error::MessageSend(e.to_string()))?;
552
553 Ok(())
554 }
555
556 pub fn analyze_document(&mut self, uri: &lsp_types::Url, language_id: &str) -> Result<()> {
558 tracing::debug!("Analyzing document: {} ({})", uri, language_id);
559
560 self.diagnostic_engine.clear(uri);
562
563 let diagnostics = self
564 .document_manager
565 .with_document(uri, |doc| {
566 match language_id {
567 "toml" => {
568 match self.toml_analyzer.parse(&doc.content) {
570 Ok(toml_doc) => {
571 let mut diagnostics = Vec::new();
572
573 let validation_diagnostics = self.toml_analyzer.validate(&toml_doc);
575 diagnostics.extend(validation_diagnostics);
576
577 diagnostics
578 }
579 Err(_e) => {
580 vec![lsp_types::Diagnostic {
582 range: lsp_types::Range {
583 start: lsp_types::Position {
584 line: 0,
585 character: 0,
586 },
587 end: lsp_types::Position {
588 line: 0,
589 character: 0,
590 },
591 },
592 severity: Some(lsp_types::DiagnosticSeverity::ERROR),
593 code: Some(lsp_types::NumberOrString::String(
594 "parse_error".to_string(),
595 )),
596 code_description: None,
597 source: Some("spring-lsp".to_string()),
598 message: "TOML parse error".to_string(),
599 related_information: None,
600 tags: None,
601 data: None,
602 }]
603 }
604 }
605 }
606 "rust" => {
607 vec![]
610 }
611 _ => {
612 tracing::debug!("Unsupported language: {}", language_id);
613 vec![]
614 }
615 }
616 })
617 .unwrap_or_default();
618
619 let filtered_diagnostics: Vec<_> = diagnostics
621 .into_iter()
622 .filter(|diag| {
623 if let Some(lsp_types::NumberOrString::String(code)) = &diag.code {
624 !self.config.diagnostics.is_disabled(code)
625 } else {
626 true
627 }
628 })
629 .collect();
630
631 for diagnostic in filtered_diagnostics {
633 self.diagnostic_engine.add(uri.clone(), diagnostic);
634 }
635
636 let _ = self.diagnostic_engine.publish(&self.connection, uri);
638 self.status.record_diagnostic();
639
640 Ok(())
641 }
642
643 fn handle_status_query(&self, req: Request) -> Result<()> {
647 tracing::debug!("Handling status query request");
648
649 let metrics = self.status.get_metrics();
650 let result = serde_json::to_value(metrics)?;
651
652 let response = Response {
653 id: req.id,
654 result: Some(result),
655 error: None,
656 };
657
658 self.connection
659 .sender
660 .send(Message::Response(response))
661 .map_err(|e| Error::MessageSend(e.to_string()))?;
662
663 Ok(())
664 }
665
666 pub fn handle_initialize(&mut self, params: InitializeParams) -> Result<InitializeResult> {
676 use lsp_types::{
677 CompletionOptions, HoverProviderCapability, OneOf, TextDocumentSyncCapability,
678 TextDocumentSyncKind, TextDocumentSyncOptions, WorkDoneProgressOptions,
679 };
680
681 #[allow(deprecated)]
683 if let Some(root_uri) = params.root_uri {
684 if let Ok(workspace_path) = root_uri.to_file_path() {
685 tracing::info!(
686 "Loading configuration from workspace: {}",
687 workspace_path.display()
688 );
689 self.config = ServerConfig::load(Some(&workspace_path));
690
691 if let Err(e) = self.config.validate() {
693 tracing::error!("Invalid configuration: {}", e);
694 return Err(Error::Config(e));
695 }
696
697 tracing::info!("Configuration loaded successfully");
698 tracing::debug!(
699 "Trigger characters: {:?}",
700 self.config.completion.trigger_characters
701 );
702 tracing::debug!("Schema URL: {}", self.config.schema.url);
703 tracing::debug!(
704 "Disabled diagnostics: {:?}",
705 self.config.diagnostics.disabled
706 );
707 }
708 }
709
710 Ok(InitializeResult {
711 capabilities: ServerCapabilities {
712 text_document_sync: Some(TextDocumentSyncCapability::Options(
714 TextDocumentSyncOptions {
715 open_close: Some(true),
716 change: Some(TextDocumentSyncKind::INCREMENTAL),
717 will_save: None,
718 will_save_wait_until: None,
719 save: None,
720 },
721 )),
722
723 completion_provider: Some(CompletionOptions {
727 resolve_provider: Some(true),
728 trigger_characters: Some(self.config.completion.trigger_characters.clone()),
729 all_commit_characters: None,
730 work_done_progress_options: WorkDoneProgressOptions {
731 work_done_progress: None,
732 },
733 completion_item: None,
734 }),
735
736 hover_provider: Some(HoverProviderCapability::Simple(true)),
739
740 definition_provider: Some(OneOf::Left(true)),
743
744 document_symbol_provider: Some(OneOf::Left(true)),
747
748 workspace_symbol_provider: Some(OneOf::Left(true)),
751
752 ..Default::default()
764 },
765 server_info: Some(ServerInfo {
766 name: "spring-lsp".to_string(),
767 version: Some(env!("CARGO_PKG_VERSION").to_string()),
768 }),
769 })
770 }
771
772 fn send_error_response(&self, id: RequestId, code: i32, message: String) -> Result<()> {
774 let response = Response {
775 id,
776 result: None,
777 error: Some(lsp_server::ResponseError {
778 code,
779 message,
780 data: None,
781 }),
782 };
783
784 self.connection
785 .sender
786 .send(Message::Response(response))
787 .map_err(|e| Error::MessageSend(e.to_string()))?;
788
789 Ok(())
790 }
791
792 fn notify_client_error(&self, error: &Error) -> Result<()> {
796 use lsp_types::{MessageType, ShowMessageParams};
797
798 let message_type = match error.severity() {
799 crate::error::ErrorSeverity::Error => MessageType::ERROR,
800 crate::error::ErrorSeverity::Warning => MessageType::WARNING,
801 crate::error::ErrorSeverity::Info => MessageType::INFO,
802 };
803
804 let params = ShowMessageParams {
805 typ: message_type,
806 message: error.to_string(),
807 };
808
809 let notification = Notification {
810 method: "window/showMessage".to_string(),
811 params: serde_json::to_value(params)?,
812 };
813
814 self.connection
815 .sender
816 .send(Message::Notification(notification))
817 .map_err(|e| Error::MessageSend(e.to_string()))?;
818
819 Ok(())
820 }
821
822 pub fn shutdown(&mut self) -> Result<()> {
824 tracing::info!("Shutting down spring-lsp server");
825
826 tracing::debug!("Clearing all diagnostics...");
828 tracing::debug!("Clearing document cache...");
832 tracing::debug!("Clearing indexes...");
835 tracing::info!("Server shutdown complete");
838 Ok(())
839 }
840}
841
842impl Default for LspServer {
843 fn default() -> Self {
844 Self::start().expect("Failed to start LSP server")
845 }
846}
847
848#[cfg(test)]
849mod tests {
850 use super::*;
851 use lsp_types::{
852 ClientCapabilities, ClientInfo, InitializeParams, TextDocumentItem, Url,
853 VersionedTextDocumentIdentifier, WorkDoneProgressParams,
854 };
855
856 #[test]
858 fn test_server_state_transitions() {
859 let server = LspServer::new_for_test().unwrap();
861 assert_eq!(server.state, ServerState::Uninitialized);
862 }
863
864 #[test]
866 fn test_document_open() {
867 let mut server = LspServer::new_for_test().unwrap();
868 server.state = ServerState::Initialized;
869
870 let uri = Url::parse("file:///test.toml").unwrap();
871 let params = DidOpenTextDocumentParams {
872 text_document: TextDocumentItem {
873 uri: uri.clone(),
874 language_id: "toml".to_string(),
875 version: 1,
876 text: "host = \"localhost\"".to_string(),
877 },
878 };
879
880 server.handle_did_open(params).unwrap();
881
882 let doc = server.document_manager.get(&uri);
884 assert!(doc.is_some());
885 let doc = doc.unwrap();
886 assert_eq!(doc.version, 1);
887 assert_eq!(doc.content, "host = \"localhost\"");
888 assert_eq!(doc.language_id, "toml");
889 }
890
891 #[test]
893 fn test_document_change() {
894 let mut server = LspServer::new_for_test().unwrap();
895 server.state = ServerState::Initialized;
896
897 let uri = Url::parse("file:///test.toml").unwrap();
898
899 let open_params = DidOpenTextDocumentParams {
901 text_document: TextDocumentItem {
902 uri: uri.clone(),
903 language_id: "toml".to_string(),
904 version: 1,
905 text: "host = \"localhost\"".to_string(),
906 },
907 };
908 server.handle_did_open(open_params).unwrap();
909
910 let change_params = DidChangeTextDocumentParams {
912 text_document: VersionedTextDocumentIdentifier {
913 uri: uri.clone(),
914 version: 2,
915 },
916 content_changes: vec![lsp_types::TextDocumentContentChangeEvent {
917 range: None,
918 range_length: None,
919 text: "host = \"127.0.0.1\"".to_string(),
920 }],
921 };
922 server.handle_did_change(change_params).unwrap();
923
924 let doc = server.document_manager.get(&uri).unwrap();
926 assert_eq!(doc.version, 2);
927 assert_eq!(doc.content, "host = \"127.0.0.1\"");
928 }
929
930 #[test]
932 fn test_document_close() {
933 let mut server = LspServer::new_for_test().unwrap();
934 server.state = ServerState::Initialized;
935
936 let uri = Url::parse("file:///test.toml").unwrap();
937
938 let open_params = DidOpenTextDocumentParams {
940 text_document: TextDocumentItem {
941 uri: uri.clone(),
942 language_id: "toml".to_string(),
943 version: 1,
944 text: "host = \"localhost\"".to_string(),
945 },
946 };
947 server.handle_did_open(open_params).unwrap();
948
949 assert!(server.document_manager.get(&uri).is_some());
951
952 let close_params = DidCloseTextDocumentParams {
954 text_document: lsp_types::TextDocumentIdentifier { uri: uri.clone() },
955 };
956 server.handle_did_close(close_params).unwrap();
957
958 assert!(server.document_manager.get(&uri).is_none());
960 }
961
962 #[test]
964 fn test_initialize_response() {
965 let mut server = LspServer::new_for_test().unwrap();
966
967 #[allow(deprecated)]
968 let params = InitializeParams {
969 process_id: Some(1234),
970 root_uri: None,
971 capabilities: ClientCapabilities::default(),
972 client_info: Some(ClientInfo {
973 name: "test-client".to_string(),
974 version: Some("1.0.0".to_string()),
975 }),
976 locale: None,
977 root_path: None,
978 initialization_options: None,
979 trace: None,
980 workspace_folders: Some(vec![lsp_types::WorkspaceFolder {
981 uri: Url::parse("file:///workspace").unwrap(),
982 name: "workspace".to_string(),
983 }]),
984 work_done_progress_params: WorkDoneProgressParams::default(),
985 };
986
987 let result = server.handle_initialize(params).unwrap();
988
989 assert!(result.server_info.is_some());
991 let server_info = result.server_info.unwrap();
992 assert_eq!(server_info.name, "spring-lsp");
993 assert!(server_info.version.is_some());
994
995 let capabilities = result.capabilities;
997
998 assert!(capabilities.text_document_sync.is_some());
1000 if let Some(lsp_types::TextDocumentSyncCapability::Options(sync_options)) =
1001 capabilities.text_document_sync
1002 {
1003 assert_eq!(sync_options.open_close, Some(true));
1004 assert_eq!(
1005 sync_options.change,
1006 Some(lsp_types::TextDocumentSyncKind::INCREMENTAL)
1007 );
1008 } else {
1009 panic!("Expected TextDocumentSyncOptions");
1010 }
1011
1012 assert!(capabilities.completion_provider.is_some());
1014 let completion = capabilities.completion_provider.unwrap();
1015 assert_eq!(completion.resolve_provider, Some(true));
1016 assert!(completion.trigger_characters.is_some());
1017 let triggers = completion.trigger_characters.unwrap();
1018 assert!(triggers.contains(&"[".to_string()));
1019 assert!(triggers.contains(&"$".to_string()));
1020 assert!(triggers.contains(&"{".to_string()));
1021
1022 assert!(capabilities.hover_provider.is_some());
1024
1025 assert!(capabilities.definition_provider.is_some());
1027
1028 assert!(capabilities.document_symbol_provider.is_some());
1030
1031 assert!(capabilities.workspace_symbol_provider.is_some());
1033 }
1034
1035 #[test]
1037 fn test_error_recovery() {
1038 let mut server = LspServer::new_for_test().unwrap();
1039 server.state = ServerState::Initialized;
1040
1041 let uri = Url::parse("file:///nonexistent.toml").unwrap();
1043 let change_params = DidChangeTextDocumentParams {
1044 text_document: VersionedTextDocumentIdentifier {
1045 uri: uri.clone(),
1046 version: 1,
1047 },
1048 content_changes: vec![lsp_types::TextDocumentContentChangeEvent {
1049 range: None,
1050 range_length: None,
1051 text: "test".to_string(),
1052 }],
1053 };
1054
1055 let result = server.handle_did_change(change_params);
1057 assert!(result.is_ok());
1058
1059 assert!(server.document_manager.get(&uri).is_none());
1061 }
1062}