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