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.into()),
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 = utils::byte_offset_to_position(&original_content, original_content.len());
460 let edit = TextEdit {
461 range: Range {
462 start: Position::default(),
463 end,
464 },
465 new_text: formatted_content,
466 };
467 Ok(Some(vec![edit]))
468 } else {
469 Ok(None)
470 }
471 }
472
473 async fn did_close(&self, params: DidCloseTextDocumentParams) {
474 let uri = params.text_document.uri.to_string();
475 self.ast_cache.write().await.remove(&uri);
476 self.text_cache.write().await.remove(&uri);
477 self.completion_cache.write().await.remove(&uri);
478 self.client
479 .log_message(MessageType::INFO, "file closed, caches cleared.")
480 .await;
481 }
482
483 async fn did_change_configuration(&self, _: DidChangeConfigurationParams) {
484 self.client
485 .log_message(MessageType::INFO, "configuration changed.")
486 .await;
487 }
488 async fn did_change_workspace_folders(&self, _: DidChangeWorkspaceFoldersParams) {
489 self.client
490 .log_message(MessageType::INFO, "workdspace folders changed.")
491 .await;
492 }
493
494 async fn did_change_watched_files(&self, _: DidChangeWatchedFilesParams) {
495 self.client
496 .log_message(MessageType::INFO, "watched files have changed.")
497 .await;
498 }
499
500 async fn completion(
501 &self,
502 params: CompletionParams,
503 ) -> tower_lsp::jsonrpc::Result<Option<CompletionResponse>> {
504 let uri = params.text_document_position.text_document.uri;
505 let position = params.text_document_position.position;
506
507 let trigger_char = params
508 .context
509 .as_ref()
510 .and_then(|ctx| ctx.trigger_character.as_deref());
511
512 let source_text = {
514 let text_cache = self.text_cache.read().await;
515 if let Some((_, text)) = text_cache.get(&uri.to_string()) {
516 text.clone()
517 } else {
518 match uri.to_file_path() {
519 Ok(path) => std::fs::read_to_string(&path).unwrap_or_default(),
520 Err(_) => return Ok(None),
521 }
522 }
523 };
524
525 let cached: Option<Arc<completion::CompletionCache>> = {
527 let comp_cache = self.completion_cache.read().await;
528 comp_cache.get(&uri.to_string()).cloned()
529 };
530
531 if cached.is_none() {
532 let ast_cache = self.ast_cache.clone();
534 let completion_cache = self.completion_cache.clone();
535 let uri_string = uri.to_string();
536 tokio::spawn(async move {
537 let cached_build = {
538 let cache = ast_cache.read().await;
539 match cache.get(&uri_string) {
540 Some(v) => v.clone(),
541 None => return,
542 }
543 };
544 if let Some(sources) = cached_build.ast.get("sources") {
545 let contracts = cached_build.ast.get("contracts");
546 let cc = completion::build_completion_cache(sources, contracts);
547 completion_cache
548 .write()
549 .await
550 .insert(uri_string, Arc::new(cc));
551 }
552 });
553 }
554
555 let cache_ref = cached.as_deref();
556
557 let file_id = {
559 let uri_path = uri.to_file_path().ok();
560 cache_ref.and_then(|c| {
561 uri_path.as_ref().and_then(|p| {
562 let path_str = p.to_str()?;
563 c.path_to_file_id.get(path_str).copied()
564 })
565 })
566 };
567
568 let result =
569 completion::handle_completion(cache_ref, &source_text, position, trigger_char, file_id);
570 Ok(result)
571 }
572
573 async fn goto_definition(
574 &self,
575 params: GotoDefinitionParams,
576 ) -> tower_lsp::jsonrpc::Result<Option<GotoDefinitionResponse>> {
577 self.client
578 .log_message(MessageType::INFO, "got textDocument/definition request")
579 .await;
580
581 let uri = params.text_document_position_params.text_document.uri;
582 let position = params.text_document_position_params.position;
583
584 let file_path = match uri.to_file_path() {
585 Ok(path) => path,
586 Err(_) => {
587 self.client
588 .log_message(MessageType::ERROR, "Invalid file uri")
589 .await;
590 return Ok(None);
591 }
592 };
593
594 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
595 Some(bytes) => bytes,
596 None => return Ok(None),
597 };
598
599 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
600 let cached_build = match cached_build {
601 Some(cb) => cb,
602 None => return Ok(None),
603 };
604
605 if let Some(location) =
606 goto::goto_declaration(&cached_build.ast, &uri, position, &source_bytes)
607 {
608 self.client
609 .log_message(
610 MessageType::INFO,
611 format!(
612 "found definition at {}:{}",
613 location.uri, location.range.start.line
614 ),
615 )
616 .await;
617 Ok(Some(GotoDefinitionResponse::from(location)))
618 } else {
619 self.client
620 .log_message(MessageType::INFO, "no definition found")
621 .await;
622 Ok(None)
623 }
624 }
625
626 async fn goto_declaration(
627 &self,
628 params: request::GotoDeclarationParams,
629 ) -> tower_lsp::jsonrpc::Result<Option<request::GotoDeclarationResponse>> {
630 self.client
631 .log_message(MessageType::INFO, "got textDocument/declaration request")
632 .await;
633
634 let uri = params.text_document_position_params.text_document.uri;
635 let position = params.text_document_position_params.position;
636
637 let file_path = match uri.to_file_path() {
638 Ok(path) => path,
639 Err(_) => {
640 self.client
641 .log_message(MessageType::ERROR, "invalid file uri")
642 .await;
643 return Ok(None);
644 }
645 };
646
647 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
648 Some(bytes) => bytes,
649 None => return Ok(None),
650 };
651
652 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
653 let cached_build = match cached_build {
654 Some(cb) => cb,
655 None => return Ok(None),
656 };
657
658 if let Some(location) =
659 goto::goto_declaration(&cached_build.ast, &uri, position, &source_bytes)
660 {
661 self.client
662 .log_message(
663 MessageType::INFO,
664 format!(
665 "found declaration at {}:{}",
666 location.uri, location.range.start.line
667 ),
668 )
669 .await;
670 Ok(Some(request::GotoDeclarationResponse::from(location)))
671 } else {
672 self.client
673 .log_message(MessageType::INFO, "no declaration found")
674 .await;
675 Ok(None)
676 }
677 }
678
679 async fn references(
680 &self,
681 params: ReferenceParams,
682 ) -> tower_lsp::jsonrpc::Result<Option<Vec<Location>>> {
683 self.client
684 .log_message(MessageType::INFO, "Got a textDocument/references request")
685 .await;
686
687 let uri = params.text_document_position.text_document.uri;
688 let position = params.text_document_position.position;
689 let file_path = match uri.to_file_path() {
690 Ok(path) => path,
691 Err(_) => {
692 self.client
693 .log_message(MessageType::ERROR, "Invalid file URI")
694 .await;
695 return Ok(None);
696 }
697 };
698 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
699 Some(bytes) => bytes,
700 None => return Ok(None),
701 };
702 let cached_build = self.get_or_fetch_build(&uri, &file_path, true).await;
703 let cached_build = match cached_build {
704 Some(cb) => cb,
705 None => return Ok(None),
706 };
707
708 let mut locations = references::goto_references(
710 &cached_build.ast,
711 &uri,
712 position,
713 &source_bytes,
714 params.context.include_declaration,
715 );
716
717 if let Some((def_abs_path, def_byte_offset)) =
719 references::resolve_target_location(&cached_build, &uri, position, &source_bytes)
720 {
721 let cache = self.ast_cache.read().await;
722 for (cached_uri, other_build) in cache.iter() {
723 if *cached_uri == uri.to_string() {
724 continue;
725 }
726 let other_locations = references::goto_references_for_target(
727 other_build,
728 &def_abs_path,
729 def_byte_offset,
730 None,
731 params.context.include_declaration,
732 );
733 locations.extend(other_locations);
734 }
735 }
736
737 let mut seen = std::collections::HashSet::new();
739 locations.retain(|loc| {
740 seen.insert((
741 loc.uri.clone(),
742 loc.range.start.line,
743 loc.range.start.character,
744 loc.range.end.line,
745 loc.range.end.character,
746 ))
747 });
748
749 if locations.is_empty() {
750 self.client
751 .log_message(MessageType::INFO, "No references found")
752 .await;
753 Ok(None)
754 } else {
755 self.client
756 .log_message(
757 MessageType::INFO,
758 format!("Found {} references", locations.len()),
759 )
760 .await;
761 Ok(Some(locations))
762 }
763 }
764
765 async fn prepare_rename(
766 &self,
767 params: TextDocumentPositionParams,
768 ) -> tower_lsp::jsonrpc::Result<Option<PrepareRenameResponse>> {
769 self.client
770 .log_message(MessageType::INFO, "got textDocument/prepareRename request")
771 .await;
772
773 let uri = params.text_document.uri;
774 let position = params.position;
775
776 let file_path = match uri.to_file_path() {
777 Ok(path) => path,
778 Err(_) => {
779 self.client
780 .log_message(MessageType::ERROR, "invalid file uri")
781 .await;
782 return Ok(None);
783 }
784 };
785
786 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
787 Some(bytes) => bytes,
788 None => return Ok(None),
789 };
790
791 if let Some(range) = rename::get_identifier_range(&source_bytes, position) {
792 self.client
793 .log_message(
794 MessageType::INFO,
795 format!(
796 "prepare rename range: {}:{}",
797 range.start.line, range.start.character
798 ),
799 )
800 .await;
801 Ok(Some(PrepareRenameResponse::Range(range)))
802 } else {
803 self.client
804 .log_message(MessageType::INFO, "no identifier found for prepare rename")
805 .await;
806 Ok(None)
807 }
808 }
809
810 async fn rename(
811 &self,
812 params: RenameParams,
813 ) -> tower_lsp::jsonrpc::Result<Option<WorkspaceEdit>> {
814 self.client
815 .log_message(MessageType::INFO, "got textDocument/rename request")
816 .await;
817
818 let uri = params.text_document_position.text_document.uri;
819 let position = params.text_document_position.position;
820 let new_name = params.new_name;
821 let file_path = match uri.to_file_path() {
822 Ok(p) => p,
823 Err(_) => {
824 self.client
825 .log_message(MessageType::ERROR, "invalid file uri")
826 .await;
827 return Ok(None);
828 }
829 };
830 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
831 Some(bytes) => bytes,
832 None => return Ok(None),
833 };
834
835 let current_identifier = match rename::get_identifier_at_position(&source_bytes, position) {
836 Some(id) => id,
837 None => {
838 self.client
839 .log_message(MessageType::ERROR, "No identifier found at position")
840 .await;
841 return Ok(None);
842 }
843 };
844
845 if !utils::is_valid_solidity_identifier(&new_name) {
846 return Err(tower_lsp::jsonrpc::Error::invalid_params(
847 "new name is not a valid solidity identifier",
848 ));
849 }
850
851 if new_name == current_identifier {
852 self.client
853 .log_message(
854 MessageType::INFO,
855 "new name is the same as current identifier",
856 )
857 .await;
858 return Ok(None);
859 }
860
861 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
862 let cached_build = match cached_build {
863 Some(cb) => cb,
864 None => return Ok(None),
865 };
866 let other_builds: Vec<Arc<goto::CachedBuild>> = {
867 let cache = self.ast_cache.read().await;
868 cache
869 .iter()
870 .filter(|(key, _)| **key != uri.to_string())
871 .map(|(_, v)| v.clone())
872 .collect()
873 };
874 let other_refs: Vec<&goto::CachedBuild> = other_builds.iter().map(|v| v.as_ref()).collect();
875
876 let text_buffers: HashMap<String, Vec<u8>> = {
880 let text_cache = self.text_cache.read().await;
881 text_cache
882 .iter()
883 .map(|(uri, (_, content))| (uri.clone(), content.as_bytes().to_vec()))
884 .collect()
885 };
886
887 match rename::rename_symbol(
888 &cached_build,
889 &uri,
890 position,
891 &source_bytes,
892 new_name,
893 &other_refs,
894 &text_buffers,
895 ) {
896 Some(workspace_edit) => {
897 self.client
898 .log_message(
899 MessageType::INFO,
900 format!(
901 "created rename edit with {} file(s), {} total change(s)",
902 workspace_edit
903 .changes
904 .as_ref()
905 .map(|c| c.len())
906 .unwrap_or(0),
907 workspace_edit
908 .changes
909 .as_ref()
910 .map(|c| c.values().map(|v| v.len()).sum::<usize>())
911 .unwrap_or(0)
912 ),
913 )
914 .await;
915
916 Ok(Some(workspace_edit))
921 }
922
923 None => {
924 self.client
925 .log_message(MessageType::INFO, "No locations found for renaming")
926 .await;
927 Ok(None)
928 }
929 }
930 }
931
932 async fn symbol(
933 &self,
934 params: WorkspaceSymbolParams,
935 ) -> tower_lsp::jsonrpc::Result<Option<Vec<SymbolInformation>>> {
936 self.client
937 .log_message(MessageType::INFO, "got workspace/symbol request")
938 .await;
939
940 let ast_data = {
943 let cache = self.ast_cache.read().await;
944 cache.values().next().map(|cb| cb.ast.clone())
945 };
946 let ast_data = match ast_data {
947 Some(data) => data,
948 None => {
949 let current_dir = std::env::current_dir().ok();
950 if let Some(dir) = current_dir {
951 let path_str = dir.to_str().unwrap_or(".");
952 match self.compiler.ast(path_str).await {
953 Ok(data) => data,
954 Err(e) => {
955 self.client
956 .log_message(
957 MessageType::WARNING,
958 format!("failed to get ast data: {e}"),
959 )
960 .await;
961 return Ok(None);
962 }
963 }
964 } else {
965 self.client
966 .log_message(MessageType::ERROR, "could not get current directory")
967 .await;
968 return Ok(None);
969 }
970 }
971 };
972
973 let mut all_symbols = symbols::extract_symbols(&ast_data);
974 if !params.query.is_empty() {
975 let query = params.query.to_lowercase();
976 all_symbols.retain(|symbol| symbol.name.to_lowercase().contains(&query));
977 }
978 if all_symbols.is_empty() {
979 self.client
980 .log_message(MessageType::INFO, "No symbols found")
981 .await;
982 Ok(None)
983 } else {
984 self.client
985 .log_message(
986 MessageType::INFO,
987 format!("found {} symbol", all_symbols.len()),
988 )
989 .await;
990 Ok(Some(all_symbols))
991 }
992 }
993
994 async fn document_symbol(
995 &self,
996 params: DocumentSymbolParams,
997 ) -> tower_lsp::jsonrpc::Result<Option<DocumentSymbolResponse>> {
998 self.client
999 .log_message(MessageType::INFO, "got textDocument/documentSymbol request")
1000 .await;
1001 let uri = params.text_document.uri;
1002 let file_path = match uri.to_file_path() {
1003 Ok(path) => path,
1004 Err(_) => {
1005 self.client
1006 .log_message(MessageType::ERROR, "invalid file uri")
1007 .await;
1008 return Ok(None);
1009 }
1010 };
1011
1012 let path_str = match file_path.to_str() {
1013 Some(s) => s,
1014 None => {
1015 self.client
1016 .log_message(MessageType::ERROR, "invalid path")
1017 .await;
1018 return Ok(None);
1019 }
1020 };
1021 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
1023 let cached_build = match cached_build {
1024 Some(cb) => cb,
1025 None => return Ok(None),
1026 };
1027 let symbols = symbols::extract_document_symbols(&cached_build.ast, path_str);
1028 if symbols.is_empty() {
1029 self.client
1030 .log_message(MessageType::INFO, "no document symbols found")
1031 .await;
1032 Ok(None)
1033 } else {
1034 self.client
1035 .log_message(
1036 MessageType::INFO,
1037 format!("found {} document symbols", symbols.len()),
1038 )
1039 .await;
1040 Ok(Some(DocumentSymbolResponse::Nested(symbols)))
1041 }
1042 }
1043
1044 async fn hover(&self, params: HoverParams) -> tower_lsp::jsonrpc::Result<Option<Hover>> {
1045 self.client
1046 .log_message(MessageType::INFO, "got textDocument/hover request")
1047 .await;
1048
1049 let uri = params.text_document_position_params.text_document.uri;
1050 let position = params.text_document_position_params.position;
1051
1052 let file_path = match uri.to_file_path() {
1053 Ok(path) => path,
1054 Err(_) => {
1055 self.client
1056 .log_message(MessageType::ERROR, "invalid file uri")
1057 .await;
1058 return Ok(None);
1059 }
1060 };
1061
1062 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
1063 Some(bytes) => bytes,
1064 None => return Ok(None),
1065 };
1066
1067 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
1068 let cached_build = match cached_build {
1069 Some(cb) => cb,
1070 None => return Ok(None),
1071 };
1072
1073 let result = hover::hover_info(&cached_build.ast, &uri, position, &source_bytes);
1074
1075 if result.is_some() {
1076 self.client
1077 .log_message(MessageType::INFO, "hover info found")
1078 .await;
1079 } else {
1080 self.client
1081 .log_message(MessageType::INFO, "no hover info found")
1082 .await;
1083 }
1084
1085 Ok(result)
1086 }
1087
1088 async fn document_link(
1089 &self,
1090 params: DocumentLinkParams,
1091 ) -> tower_lsp::jsonrpc::Result<Option<Vec<DocumentLink>>> {
1092 self.client
1093 .log_message(MessageType::INFO, "got textDocument/documentLink request")
1094 .await;
1095
1096 let uri = params.text_document.uri;
1097 let file_path = match uri.to_file_path() {
1098 Ok(path) => path,
1099 Err(_) => {
1100 self.client
1101 .log_message(MessageType::ERROR, "invalid file uri")
1102 .await;
1103 return Ok(None);
1104 }
1105 };
1106
1107 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
1108 Some(bytes) => bytes,
1109 None => return Ok(None),
1110 };
1111
1112 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
1113 let cached_build = match cached_build {
1114 Some(cb) => cb,
1115 None => return Ok(None),
1116 };
1117
1118 let result = links::document_links(&cached_build, &uri, &source_bytes);
1119
1120 if result.is_empty() {
1121 self.client
1122 .log_message(MessageType::INFO, "no document links found")
1123 .await;
1124 Ok(None)
1125 } else {
1126 self.client
1127 .log_message(
1128 MessageType::INFO,
1129 format!("found {} document links", result.len()),
1130 )
1131 .await;
1132 Ok(Some(result))
1133 }
1134 }
1135}