1use crate::completion;
2use crate::goto;
3use crate::hover;
4use crate::links;
5use crate::references;
6use crate::rename;
7use crate::runner::{ForgeRunner, Runner};
8use crate::symbols;
9use crate::utils;
10use std::collections::HashMap;
11use std::sync::Arc;
12use tokio::sync::RwLock;
13use tower_lsp::{Client, LanguageServer, lsp_types::*};
14
15pub struct ForgeLsp {
16 client: Client,
17 compiler: Arc<dyn Runner>,
18 ast_cache: Arc<RwLock<HashMap<String, Arc<goto::CachedBuild>>>>,
19 text_cache: Arc<RwLock<HashMap<String, (i32, String)>>>,
23 completion_cache: Arc<RwLock<HashMap<String, Arc<completion::CompletionCache>>>>,
24}
25
26impl ForgeLsp {
27 pub fn new(client: Client, use_solar: bool) -> Self {
28 let compiler: Arc<dyn Runner> = if use_solar {
29 Arc::new(crate::solar_runner::SolarRunner)
30 } else {
31 Arc::new(ForgeRunner)
32 };
33 let ast_cache = Arc::new(RwLock::new(HashMap::new()));
34 let text_cache = Arc::new(RwLock::new(HashMap::new()));
35 let completion_cache = Arc::new(RwLock::new(HashMap::new()));
36 Self {
37 client,
38 compiler,
39 ast_cache,
40 text_cache,
41 completion_cache,
42 }
43 }
44
45 async fn on_change(&self, params: TextDocumentItem) {
46 let uri = params.uri.clone();
47 let version = params.version;
48
49 let file_path = match uri.to_file_path() {
50 Ok(path) => path,
51 Err(_) => {
52 self.client
53 .log_message(MessageType::ERROR, "Invalid file URI")
54 .await;
55 return;
56 }
57 };
58
59 let path_str = match file_path.to_str() {
60 Some(s) => s,
61 None => {
62 self.client
63 .log_message(MessageType::ERROR, "Invalid file path")
64 .await;
65 return;
66 }
67 };
68
69 let (lint_result, build_result, ast_result) = tokio::join!(
70 self.compiler.get_lint_diagnostics(&uri),
71 self.compiler.get_build_diagnostics(&uri),
72 self.compiler.ast(path_str)
73 );
74
75 let build_succeeded = matches!(&build_result, Ok(diagnostics) if diagnostics.iter().all(|d| d.severity != Some(DiagnosticSeverity::ERROR)));
77
78 if build_succeeded {
79 if let Ok(ast_data) = ast_result {
80 let cached_build = Arc::new(goto::CachedBuild::new(ast_data));
81 let mut cache = self.ast_cache.write().await;
82 cache.insert(uri.to_string(), cached_build.clone());
83 drop(cache);
84
85 let completion_cache = self.completion_cache.clone();
87 let uri_string = uri.to_string();
88 tokio::spawn(async move {
89 if let Some(sources) = cached_build.ast.get("sources") {
90 let contracts = cached_build.ast.get("contracts");
91 let cc = completion::build_completion_cache(sources, contracts);
92 completion_cache
93 .write()
94 .await
95 .insert(uri_string, Arc::new(cc));
96 }
97 });
98 self.client
99 .log_message(MessageType::INFO, "Build successful, AST cache updated")
100 .await;
101 } else if let Err(e) = ast_result {
102 self.client
103 .log_message(
104 MessageType::INFO,
105 format!("Build succeeded but failed to get AST: {e}"),
106 )
107 .await;
108 }
109 } else {
110 self.client
112 .log_message(
113 MessageType::INFO,
114 "Build errors detected, keeping existing AST cache",
115 )
116 .await;
117 }
118
119 {
121 let mut text_cache = self.text_cache.write().await;
122 text_cache.insert(uri.to_string(), (version, params.text));
123 }
124
125 let mut all_diagnostics = vec![];
126
127 match lint_result {
128 Ok(mut lints) => {
129 self.client
130 .log_message(
131 MessageType::INFO,
132 format!("found {} lint diagnostics", lints.len()),
133 )
134 .await;
135 all_diagnostics.append(&mut lints);
136 }
137 Err(e) => {
138 self.client
139 .log_message(
140 MessageType::ERROR,
141 format!("Forge lint diagnostics failed: {e}"),
142 )
143 .await;
144 }
145 }
146
147 match build_result {
148 Ok(mut builds) => {
149 self.client
150 .log_message(
151 MessageType::INFO,
152 format!("found {} build diagnostics", builds.len()),
153 )
154 .await;
155 all_diagnostics.append(&mut builds);
156 }
157 Err(e) => {
158 self.client
159 .log_message(
160 MessageType::WARNING,
161 format!("Forge build diagnostics failed: {e}"),
162 )
163 .await;
164 }
165 }
166
167 self.client
169 .publish_diagnostics(uri, all_diagnostics, None)
170 .await;
171 }
172
173 async fn get_or_fetch_build(
182 &self,
183 uri: &Url,
184 file_path: &std::path::Path,
185 insert_on_miss: bool,
186 ) -> Option<Arc<goto::CachedBuild>> {
187 let uri_str = uri.to_string();
188
189 {
192 let cache = self.ast_cache.read().await;
193 if let Some(cached) = cache.get(&uri_str) {
194 return Some(cached.clone());
195 }
196 }
197
198 let path_str = file_path.to_str()?;
200 match self.compiler.ast(path_str).await {
201 Ok(data) => {
202 let build = Arc::new(goto::CachedBuild::new(data));
203 if insert_on_miss {
204 let mut cache = self.ast_cache.write().await;
205 cache.insert(uri_str.clone(), build.clone());
206 }
207 Some(build)
208 }
209 Err(e) => {
210 self.client
211 .log_message(MessageType::ERROR, format!("failed to get AST: {e}"))
212 .await;
213 None
214 }
215 }
216 }
217
218 async fn get_source_bytes(&self, uri: &Url, file_path: &std::path::Path) -> Option<Vec<u8>> {
221 {
222 let text_cache = self.text_cache.read().await;
223 if let Some((_, content)) = text_cache.get(&uri.to_string()) {
224 return Some(content.as_bytes().to_vec());
225 }
226 }
227 match std::fs::read(file_path) {
228 Ok(bytes) => Some(bytes),
229 Err(e) => {
230 self.client
231 .log_message(MessageType::ERROR, format!("failed to read file: {e}"))
232 .await;
233 None
234 }
235 }
236 }
237}
238
239#[tower_lsp::async_trait]
240impl LanguageServer for ForgeLsp {
241 async fn initialize(
242 &self,
243 params: InitializeParams,
244 ) -> tower_lsp::jsonrpc::Result<InitializeResult> {
245 let client_encodings = params
247 .capabilities
248 .general
249 .as_ref()
250 .and_then(|g| g.position_encodings.as_deref());
251 let encoding = utils::PositionEncoding::negotiate(client_encodings);
252 utils::set_encoding(encoding);
253
254 Ok(InitializeResult {
255 server_info: Some(ServerInfo {
256 name: "Solidity Language Server".to_string(),
257 version: Some(env!("LONG_VERSION").to_string()),
258 }),
259 capabilities: ServerCapabilities {
260 position_encoding: Some(encoding.to_encoding_kind()),
261 completion_provider: Some(CompletionOptions {
262 trigger_characters: Some(vec![".".to_string()]),
263 resolve_provider: Some(false),
264 ..Default::default()
265 }),
266 definition_provider: Some(OneOf::Left(true)),
267 declaration_provider: Some(DeclarationCapability::Simple(true)),
268 references_provider: Some(OneOf::Left(true)),
269 rename_provider: Some(OneOf::Right(RenameOptions {
270 prepare_provider: Some(true),
271 work_done_progress_options: WorkDoneProgressOptions {
272 work_done_progress: Some(true),
273 },
274 })),
275 workspace_symbol_provider: Some(OneOf::Left(true)),
276 document_symbol_provider: Some(OneOf::Left(true)),
277 hover_provider: Some(HoverProviderCapability::Simple(true)),
278 document_link_provider: Some(DocumentLinkOptions {
279 resolve_provider: Some(false),
280 work_done_progress_options: WorkDoneProgressOptions {
281 work_done_progress: None,
282 },
283 }),
284 document_formatting_provider: Some(OneOf::Left(true)),
285 text_document_sync: Some(TextDocumentSyncCapability::Options(
286 TextDocumentSyncOptions {
287 will_save: Some(true),
288 will_save_wait_until: None,
289 open_close: Some(true),
290 save: Some(TextDocumentSyncSaveOptions::SaveOptions(SaveOptions {
291 include_text: Some(true),
292 })),
293 change: Some(TextDocumentSyncKind::FULL),
294 },
295 )),
296 ..ServerCapabilities::default()
297 },
298 })
299 }
300
301 async fn initialized(&self, _: InitializedParams) {
302 self.client
303 .log_message(MessageType::INFO, "lsp server initialized.")
304 .await;
305 }
306
307 async fn shutdown(&self) -> tower_lsp::jsonrpc::Result<()> {
308 self.client
309 .log_message(MessageType::INFO, "lsp server shutting down.")
310 .await;
311 Ok(())
312 }
313
314 async fn did_open(&self, params: DidOpenTextDocumentParams) {
315 self.client
316 .log_message(MessageType::INFO, "file opened")
317 .await;
318
319 self.on_change(params.text_document).await
320 }
321
322 async fn did_change(&self, params: DidChangeTextDocumentParams) {
323 self.client
324 .log_message(MessageType::INFO, "file changed")
325 .await;
326
327 if let Some(change) = params.content_changes.into_iter().next() {
329 let mut text_cache = self.text_cache.write().await;
330 text_cache.insert(
331 params.text_document.uri.to_string(),
332 (params.text_document.version, change.text),
333 );
334 }
335 }
336
337 async fn did_save(&self, params: DidSaveTextDocumentParams) {
338 self.client
339 .log_message(MessageType::INFO, "file saved")
340 .await;
341
342 let text_content = if let Some(text) = params.text {
343 text
344 } else {
345 let cached = {
347 let text_cache = self.text_cache.read().await;
348 text_cache
349 .get(params.text_document.uri.as_str())
350 .map(|(_, content)| content.clone())
351 };
352 if let Some(content) = cached {
353 content
354 } else {
355 match std::fs::read_to_string(params.text_document.uri.path()) {
356 Ok(content) => content,
357 Err(e) => {
358 self.client
359 .log_message(
360 MessageType::ERROR,
361 format!("Failed to read file on save: {e}"),
362 )
363 .await;
364 return;
365 }
366 }
367 }
368 };
369
370 let version = self
371 .text_cache
372 .read()
373 .await
374 .get(params.text_document.uri.as_str())
375 .map(|(version, _)| *version)
376 .unwrap_or_default();
377
378 self.on_change(TextDocumentItem {
379 uri: params.text_document.uri,
380 text: text_content,
381 version,
382 language_id: "".to_string(),
383 })
384 .await;
385 }
386
387 async fn will_save(&self, params: WillSaveTextDocumentParams) {
388 self.client
389 .log_message(
390 MessageType::INFO,
391 format!(
392 "file will save reason:{:?} {}",
393 params.reason, params.text_document.uri
394 ),
395 )
396 .await;
397 }
398
399 async fn formatting(
400 &self,
401 params: DocumentFormattingParams,
402 ) -> tower_lsp::jsonrpc::Result<Option<Vec<TextEdit>>> {
403 self.client
404 .log_message(MessageType::INFO, "formatting request")
405 .await;
406
407 let uri = params.text_document.uri;
408 let file_path = match uri.to_file_path() {
409 Ok(path) => path,
410 Err(_) => {
411 self.client
412 .log_message(MessageType::ERROR, "Invalid file URI for formatting")
413 .await;
414 return Ok(None);
415 }
416 };
417 let path_str = match file_path.to_str() {
418 Some(s) => s,
419 None => {
420 self.client
421 .log_message(MessageType::ERROR, "Invalid file path for formatting")
422 .await;
423 return Ok(None);
424 }
425 };
426
427 let original_content = {
429 let text_cache = self.text_cache.read().await;
430 if let Some((_, content)) = text_cache.get(&uri.to_string()) {
431 content.clone()
432 } else {
433 match std::fs::read_to_string(&file_path) {
435 Ok(content) => content,
436 Err(_) => {
437 self.client
438 .log_message(MessageType::ERROR, "Failed to read file for formatting")
439 .await;
440 return Ok(None);
441 }
442 }
443 }
444 };
445
446 let formatted_content = match self.compiler.format(path_str).await {
448 Ok(content) => content,
449 Err(e) => {
450 self.client
451 .log_message(MessageType::WARNING, format!("Formatting failed: {e}"))
452 .await;
453 return Ok(None);
454 }
455 };
456
457 if original_content != formatted_content {
459 let (end_line, end_character) =
460 utils::byte_offset_to_position(&original_content, original_content.len());
461 let edit = TextEdit {
462 range: Range {
463 start: Position {
464 line: 0,
465 character: 0,
466 },
467 end: Position {
468 line: end_line,
469 character: end_character,
470 },
471 },
472 new_text: formatted_content,
473 };
474 Ok(Some(vec![edit]))
475 } else {
476 Ok(None)
477 }
478 }
479
480 async fn did_close(&self, params: DidCloseTextDocumentParams) {
481 let uri = params.text_document.uri.to_string();
482 self.ast_cache.write().await.remove(&uri);
483 self.text_cache.write().await.remove(&uri);
484 self.completion_cache.write().await.remove(&uri);
485 self.client
486 .log_message(MessageType::INFO, "file closed, caches cleared.")
487 .await;
488 }
489
490 async fn did_change_configuration(&self, _: DidChangeConfigurationParams) {
491 self.client
492 .log_message(MessageType::INFO, "configuration changed.")
493 .await;
494 }
495 async fn did_change_workspace_folders(&self, _: DidChangeWorkspaceFoldersParams) {
496 self.client
497 .log_message(MessageType::INFO, "workdspace folders changed.")
498 .await;
499 }
500
501 async fn did_change_watched_files(&self, _: DidChangeWatchedFilesParams) {
502 self.client
503 .log_message(MessageType::INFO, "watched files have changed.")
504 .await;
505 }
506
507 async fn completion(
508 &self,
509 params: CompletionParams,
510 ) -> tower_lsp::jsonrpc::Result<Option<CompletionResponse>> {
511 let uri = params.text_document_position.text_document.uri;
512 let position = params.text_document_position.position;
513
514 let trigger_char = params
515 .context
516 .as_ref()
517 .and_then(|ctx| ctx.trigger_character.as_deref());
518
519 let source_text = {
521 let text_cache = self.text_cache.read().await;
522 if let Some((_, text)) = text_cache.get(&uri.to_string()) {
523 text.clone()
524 } else {
525 match uri.to_file_path() {
526 Ok(path) => std::fs::read_to_string(&path).unwrap_or_default(),
527 Err(_) => return Ok(None),
528 }
529 }
530 };
531
532 let cached: Option<Arc<completion::CompletionCache>> = {
534 let comp_cache = self.completion_cache.read().await;
535 comp_cache.get(&uri.to_string()).cloned()
536 };
537
538 if cached.is_none() {
539 let ast_cache = self.ast_cache.clone();
541 let completion_cache = self.completion_cache.clone();
542 let uri_string = uri.to_string();
543 tokio::spawn(async move {
544 let cached_build = {
545 let cache = ast_cache.read().await;
546 match cache.get(&uri_string) {
547 Some(v) => v.clone(),
548 None => return,
549 }
550 };
551 if let Some(sources) = cached_build.ast.get("sources") {
552 let contracts = cached_build.ast.get("contracts");
553 let cc = completion::build_completion_cache(sources, contracts);
554 completion_cache
555 .write()
556 .await
557 .insert(uri_string, Arc::new(cc));
558 }
559 });
560 }
561
562 let cache_ref = cached.as_deref();
563
564 let file_id = {
566 let uri_path = uri.to_file_path().ok();
567 cache_ref.and_then(|c| {
568 uri_path.as_ref().and_then(|p| {
569 let path_str = p.to_str()?;
570 c.path_to_file_id.get(path_str).copied()
571 })
572 })
573 };
574
575 let result =
576 completion::handle_completion(cache_ref, &source_text, position, trigger_char, file_id);
577 Ok(result)
578 }
579
580 async fn goto_definition(
581 &self,
582 params: GotoDefinitionParams,
583 ) -> tower_lsp::jsonrpc::Result<Option<GotoDefinitionResponse>> {
584 self.client
585 .log_message(MessageType::INFO, "got textDocument/definition request")
586 .await;
587
588 let uri = params.text_document_position_params.text_document.uri;
589 let position = params.text_document_position_params.position;
590
591 let file_path = match uri.to_file_path() {
592 Ok(path) => path,
593 Err(_) => {
594 self.client
595 .log_message(MessageType::ERROR, "Invalid file uri")
596 .await;
597 return Ok(None);
598 }
599 };
600
601 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
602 Some(bytes) => bytes,
603 None => return Ok(None),
604 };
605
606 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
607 let cached_build = match cached_build {
608 Some(cb) => cb,
609 None => return Ok(None),
610 };
611
612 if let Some(location) =
613 goto::goto_declaration(&cached_build.ast, &uri, position, &source_bytes)
614 {
615 self.client
616 .log_message(
617 MessageType::INFO,
618 format!(
619 "found definition at {}:{}",
620 location.uri, location.range.start.line
621 ),
622 )
623 .await;
624 Ok(Some(GotoDefinitionResponse::from(location)))
625 } else {
626 self.client
627 .log_message(MessageType::INFO, "no definition found")
628 .await;
629 Ok(None)
630 }
631 }
632
633 async fn goto_declaration(
634 &self,
635 params: request::GotoDeclarationParams,
636 ) -> tower_lsp::jsonrpc::Result<Option<request::GotoDeclarationResponse>> {
637 self.client
638 .log_message(MessageType::INFO, "got textDocument/declaration request")
639 .await;
640
641 let uri = params.text_document_position_params.text_document.uri;
642 let position = params.text_document_position_params.position;
643
644 let file_path = match uri.to_file_path() {
645 Ok(path) => path,
646 Err(_) => {
647 self.client
648 .log_message(MessageType::ERROR, "invalid file uri")
649 .await;
650 return Ok(None);
651 }
652 };
653
654 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
655 Some(bytes) => bytes,
656 None => return Ok(None),
657 };
658
659 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
660 let cached_build = match cached_build {
661 Some(cb) => cb,
662 None => return Ok(None),
663 };
664
665 if let Some(location) =
666 goto::goto_declaration(&cached_build.ast, &uri, position, &source_bytes)
667 {
668 self.client
669 .log_message(
670 MessageType::INFO,
671 format!(
672 "found declaration at {}:{}",
673 location.uri, location.range.start.line
674 ),
675 )
676 .await;
677 Ok(Some(request::GotoDeclarationResponse::from(location)))
678 } else {
679 self.client
680 .log_message(MessageType::INFO, "no declaration found")
681 .await;
682 Ok(None)
683 }
684 }
685
686 async fn references(
687 &self,
688 params: ReferenceParams,
689 ) -> tower_lsp::jsonrpc::Result<Option<Vec<Location>>> {
690 self.client
691 .log_message(MessageType::INFO, "Got a textDocument/references request")
692 .await;
693
694 let uri = params.text_document_position.text_document.uri;
695 let position = params.text_document_position.position;
696 let file_path = match uri.to_file_path() {
697 Ok(path) => path,
698 Err(_) => {
699 self.client
700 .log_message(MessageType::ERROR, "Invalid file URI")
701 .await;
702 return Ok(None);
703 }
704 };
705 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
706 Some(bytes) => bytes,
707 None => return Ok(None),
708 };
709 let cached_build = self.get_or_fetch_build(&uri, &file_path, true).await;
710 let cached_build = match cached_build {
711 Some(cb) => cb,
712 None => return Ok(None),
713 };
714
715 let mut locations = references::goto_references(
717 &cached_build.ast,
718 &uri,
719 position,
720 &source_bytes,
721 params.context.include_declaration,
722 );
723
724 if let Some((def_abs_path, def_byte_offset)) =
726 references::resolve_target_location(&cached_build, &uri, position, &source_bytes)
727 {
728 let cache = self.ast_cache.read().await;
729 for (cached_uri, other_build) in cache.iter() {
730 if *cached_uri == uri.to_string() {
731 continue;
732 }
733 let other_locations = references::goto_references_for_target(
734 other_build,
735 &def_abs_path,
736 def_byte_offset,
737 None,
738 params.context.include_declaration,
739 );
740 locations.extend(other_locations);
741 }
742 }
743
744 let mut seen = std::collections::HashSet::new();
746 locations.retain(|loc| {
747 seen.insert((
748 loc.uri.clone(),
749 loc.range.start.line,
750 loc.range.start.character,
751 loc.range.end.line,
752 loc.range.end.character,
753 ))
754 });
755
756 if locations.is_empty() {
757 self.client
758 .log_message(MessageType::INFO, "No references found")
759 .await;
760 Ok(None)
761 } else {
762 self.client
763 .log_message(
764 MessageType::INFO,
765 format!("Found {} references", locations.len()),
766 )
767 .await;
768 Ok(Some(locations))
769 }
770 }
771
772 async fn prepare_rename(
773 &self,
774 params: TextDocumentPositionParams,
775 ) -> tower_lsp::jsonrpc::Result<Option<PrepareRenameResponse>> {
776 self.client
777 .log_message(MessageType::INFO, "got textDocument/prepareRename request")
778 .await;
779
780 let uri = params.text_document.uri;
781 let position = params.position;
782
783 let file_path = match uri.to_file_path() {
784 Ok(path) => path,
785 Err(_) => {
786 self.client
787 .log_message(MessageType::ERROR, "invalid file uri")
788 .await;
789 return Ok(None);
790 }
791 };
792
793 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
794 Some(bytes) => bytes,
795 None => return Ok(None),
796 };
797
798 if let Some(range) = rename::get_identifier_range(&source_bytes, position) {
799 self.client
800 .log_message(
801 MessageType::INFO,
802 format!(
803 "prepare rename range: {}:{}",
804 range.start.line, range.start.character
805 ),
806 )
807 .await;
808 Ok(Some(PrepareRenameResponse::Range(range)))
809 } else {
810 self.client
811 .log_message(MessageType::INFO, "no identifier found for prepare rename")
812 .await;
813 Ok(None)
814 }
815 }
816
817 async fn rename(
818 &self,
819 params: RenameParams,
820 ) -> tower_lsp::jsonrpc::Result<Option<WorkspaceEdit>> {
821 self.client
822 .log_message(MessageType::INFO, "got textDocument/rename request")
823 .await;
824
825 let uri = params.text_document_position.text_document.uri;
826 let position = params.text_document_position.position;
827 let new_name = params.new_name;
828 let file_path = match uri.to_file_path() {
829 Ok(p) => p,
830 Err(_) => {
831 self.client
832 .log_message(MessageType::ERROR, "invalid file uri")
833 .await;
834 return Ok(None);
835 }
836 };
837 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
838 Some(bytes) => bytes,
839 None => return Ok(None),
840 };
841
842 let current_identifier = match rename::get_identifier_at_position(&source_bytes, position) {
843 Some(id) => id,
844 None => {
845 self.client
846 .log_message(MessageType::ERROR, "No identifier found at position")
847 .await;
848 return Ok(None);
849 }
850 };
851
852 if !utils::is_valid_solidity_identifier(&new_name) {
853 return Err(tower_lsp::jsonrpc::Error::invalid_params(
854 "new name is not a valid solidity identifier",
855 ));
856 }
857
858 if new_name == current_identifier {
859 self.client
860 .log_message(
861 MessageType::INFO,
862 "new name is the same as current identifier",
863 )
864 .await;
865 return Ok(None);
866 }
867
868 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
869 let cached_build = match cached_build {
870 Some(cb) => cb,
871 None => return Ok(None),
872 };
873 let other_builds: Vec<Arc<goto::CachedBuild>> = {
874 let cache = self.ast_cache.read().await;
875 cache
876 .iter()
877 .filter(|(key, _)| **key != uri.to_string())
878 .map(|(_, v)| v.clone())
879 .collect()
880 };
881 let other_refs: Vec<&goto::CachedBuild> = other_builds.iter().map(|v| v.as_ref()).collect();
882
883 let text_buffers: HashMap<String, Vec<u8>> = {
887 let text_cache = self.text_cache.read().await;
888 text_cache
889 .iter()
890 .map(|(uri, (_, content))| (uri.clone(), content.as_bytes().to_vec()))
891 .collect()
892 };
893
894 match rename::rename_symbol(
895 &cached_build,
896 &uri,
897 position,
898 &source_bytes,
899 new_name,
900 &other_refs,
901 &text_buffers,
902 ) {
903 Some(workspace_edit) => {
904 self.client
905 .log_message(
906 MessageType::INFO,
907 format!(
908 "created rename edit with {} file(s), {} total change(s)",
909 workspace_edit
910 .changes
911 .as_ref()
912 .map(|c| c.len())
913 .unwrap_or(0),
914 workspace_edit
915 .changes
916 .as_ref()
917 .map(|c| c.values().map(|v| v.len()).sum::<usize>())
918 .unwrap_or(0)
919 ),
920 )
921 .await;
922
923 Ok(Some(workspace_edit))
928 }
929
930 None => {
931 self.client
932 .log_message(MessageType::INFO, "No locations found for renaming")
933 .await;
934 Ok(None)
935 }
936 }
937 }
938
939 async fn symbol(
940 &self,
941 params: WorkspaceSymbolParams,
942 ) -> tower_lsp::jsonrpc::Result<Option<Vec<SymbolInformation>>> {
943 self.client
944 .log_message(MessageType::INFO, "got workspace/symbol request")
945 .await;
946
947 let ast_data = {
950 let cache = self.ast_cache.read().await;
951 cache.values().next().map(|cb| cb.ast.clone())
952 };
953 let ast_data = match ast_data {
954 Some(data) => data,
955 None => {
956 let current_dir = std::env::current_dir().ok();
957 if let Some(dir) = current_dir {
958 let path_str = dir.to_str().unwrap_or(".");
959 match self.compiler.ast(path_str).await {
960 Ok(data) => data,
961 Err(e) => {
962 self.client
963 .log_message(
964 MessageType::WARNING,
965 format!("failed to get ast data: {e}"),
966 )
967 .await;
968 return Ok(None);
969 }
970 }
971 } else {
972 self.client
973 .log_message(MessageType::ERROR, "could not get current directory")
974 .await;
975 return Ok(None);
976 }
977 }
978 };
979
980 let mut all_symbols = symbols::extract_symbols(&ast_data);
981 if !params.query.is_empty() {
982 let query = params.query.to_lowercase();
983 all_symbols.retain(|symbol| symbol.name.to_lowercase().contains(&query));
984 }
985 if all_symbols.is_empty() {
986 self.client
987 .log_message(MessageType::INFO, "No symbols found")
988 .await;
989 Ok(None)
990 } else {
991 self.client
992 .log_message(
993 MessageType::INFO,
994 format!("found {} symbol", all_symbols.len()),
995 )
996 .await;
997 Ok(Some(all_symbols))
998 }
999 }
1000
1001 async fn document_symbol(
1002 &self,
1003 params: DocumentSymbolParams,
1004 ) -> tower_lsp::jsonrpc::Result<Option<DocumentSymbolResponse>> {
1005 self.client
1006 .log_message(MessageType::INFO, "got textDocument/documentSymbol request")
1007 .await;
1008 let uri = params.text_document.uri;
1009 let file_path = match uri.to_file_path() {
1010 Ok(path) => path,
1011 Err(_) => {
1012 self.client
1013 .log_message(MessageType::ERROR, "invalid file uri")
1014 .await;
1015 return Ok(None);
1016 }
1017 };
1018
1019 let path_str = match file_path.to_str() {
1020 Some(s) => s,
1021 None => {
1022 self.client
1023 .log_message(MessageType::ERROR, "invalid path")
1024 .await;
1025 return Ok(None);
1026 }
1027 };
1028 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
1030 let cached_build = match cached_build {
1031 Some(cb) => cb,
1032 None => return Ok(None),
1033 };
1034 let symbols = symbols::extract_document_symbols(&cached_build.ast, path_str);
1035 if symbols.is_empty() {
1036 self.client
1037 .log_message(MessageType::INFO, "no document symbols found")
1038 .await;
1039 Ok(None)
1040 } else {
1041 self.client
1042 .log_message(
1043 MessageType::INFO,
1044 format!("found {} document symbols", symbols.len()),
1045 )
1046 .await;
1047 Ok(Some(DocumentSymbolResponse::Nested(symbols)))
1048 }
1049 }
1050
1051 async fn hover(&self, params: HoverParams) -> tower_lsp::jsonrpc::Result<Option<Hover>> {
1052 self.client
1053 .log_message(MessageType::INFO, "got textDocument/hover request")
1054 .await;
1055
1056 let uri = params.text_document_position_params.text_document.uri;
1057 let position = params.text_document_position_params.position;
1058
1059 let file_path = match uri.to_file_path() {
1060 Ok(path) => path,
1061 Err(_) => {
1062 self.client
1063 .log_message(MessageType::ERROR, "invalid file uri")
1064 .await;
1065 return Ok(None);
1066 }
1067 };
1068
1069 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
1070 Some(bytes) => bytes,
1071 None => return Ok(None),
1072 };
1073
1074 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
1075 let cached_build = match cached_build {
1076 Some(cb) => cb,
1077 None => return Ok(None),
1078 };
1079
1080 let result = hover::hover_info(&cached_build.ast, &uri, position, &source_bytes);
1081
1082 if result.is_some() {
1083 self.client
1084 .log_message(MessageType::INFO, "hover info found")
1085 .await;
1086 } else {
1087 self.client
1088 .log_message(MessageType::INFO, "no hover info found")
1089 .await;
1090 }
1091
1092 Ok(result)
1093 }
1094
1095 async fn document_link(
1096 &self,
1097 params: DocumentLinkParams,
1098 ) -> tower_lsp::jsonrpc::Result<Option<Vec<DocumentLink>>> {
1099 self.client
1100 .log_message(MessageType::INFO, "got textDocument/documentLink request")
1101 .await;
1102
1103 let uri = params.text_document.uri;
1104 let file_path = match uri.to_file_path() {
1105 Ok(path) => path,
1106 Err(_) => {
1107 self.client
1108 .log_message(MessageType::ERROR, "invalid file uri")
1109 .await;
1110 return Ok(None);
1111 }
1112 };
1113
1114 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
1115 Some(bytes) => bytes,
1116 None => return Ok(None),
1117 };
1118
1119 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
1120 let cached_build = match cached_build {
1121 Some(cb) => cb,
1122 None => return Ok(None),
1123 };
1124
1125 let result = links::document_links(&cached_build, &uri, &source_bytes);
1126
1127 if result.is_empty() {
1128 self.client
1129 .log_message(MessageType::INFO, "no document links found")
1130 .await;
1131 Ok(None)
1132 } else {
1133 self.client
1134 .log_message(
1135 MessageType::INFO,
1136 format!("found {} document links", result.len()),
1137 )
1138 .await;
1139 Ok(Some(result))
1140 }
1141 }
1142}