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