1use crate::completion;
2use crate::goto;
3use crate::hover;
4use crate::inlay_hints;
5use crate::links;
6use crate::references;
7use crate::rename;
8use crate::runner::{ForgeRunner, Runner};
9use crate::semantic_tokens;
10use crate::symbols;
11use crate::utils;
12use std::collections::HashMap;
13use std::sync::Arc;
14use tokio::sync::RwLock;
15use tower_lsp::{Client, LanguageServer, lsp_types::*};
16
17pub struct ForgeLsp {
18 client: Client,
19 compiler: Arc<dyn Runner>,
20 ast_cache: Arc<RwLock<HashMap<String, Arc<goto::CachedBuild>>>>,
21 text_cache: Arc<RwLock<HashMap<String, (i32, String)>>>,
25 completion_cache: Arc<RwLock<HashMap<String, Arc<completion::CompletionCache>>>>,
26}
27
28impl ForgeLsp {
29 pub fn new(client: Client, use_solar: bool) -> Self {
30 let compiler: Arc<dyn Runner> = if use_solar {
31 Arc::new(crate::solar_runner::SolarRunner)
32 } else {
33 Arc::new(ForgeRunner)
34 };
35 let ast_cache = Arc::new(RwLock::new(HashMap::new()));
36 let text_cache = Arc::new(RwLock::new(HashMap::new()));
37 let completion_cache = Arc::new(RwLock::new(HashMap::new()));
38 Self {
39 client,
40 compiler,
41 ast_cache,
42 text_cache,
43 completion_cache,
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, version));
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 let uri_str = uri.to_string();
125 let existing_version = text_cache.get(&uri_str).map(|(v, _)| *v).unwrap_or(-1);
126 if version >= existing_version {
127 text_cache.insert(uri_str, (version, params.text));
128 }
129 }
130
131 let mut all_diagnostics = vec![];
132
133 match lint_result {
134 Ok(mut lints) => {
135 self.client
136 .log_message(
137 MessageType::INFO,
138 format!("found {} lint diagnostics", lints.len()),
139 )
140 .await;
141 all_diagnostics.append(&mut lints);
142 }
143 Err(e) => {
144 self.client
145 .log_message(
146 MessageType::ERROR,
147 format!("Forge lint diagnostics failed: {e}"),
148 )
149 .await;
150 }
151 }
152
153 match build_result {
154 Ok(mut builds) => {
155 self.client
156 .log_message(
157 MessageType::INFO,
158 format!("found {} build diagnostics", builds.len()),
159 )
160 .await;
161 all_diagnostics.append(&mut builds);
162 }
163 Err(e) => {
164 self.client
165 .log_message(
166 MessageType::WARNING,
167 format!("Forge build diagnostics failed: {e}"),
168 )
169 .await;
170 }
171 }
172
173 self.client
175 .publish_diagnostics(uri, all_diagnostics, None)
176 .await;
177
178 if build_succeeded {
180 let client = self.client.clone();
181 tokio::spawn(async move {
182 let _ = client.inlay_hint_refresh().await;
183 });
184 }
185 }
186
187 async fn get_or_fetch_build(
196 &self,
197 uri: &Url,
198 file_path: &std::path::Path,
199 insert_on_miss: bool,
200 ) -> Option<Arc<goto::CachedBuild>> {
201 let uri_str = uri.to_string();
202
203 {
206 let cache = self.ast_cache.read().await;
207 if let Some(cached) = cache.get(&uri_str) {
208 return Some(cached.clone());
209 }
210 }
211
212 let path_str = file_path.to_str()?;
214 match self.compiler.ast(path_str).await {
215 Ok(data) => {
216 let build = Arc::new(goto::CachedBuild::new(data, 0));
219 if insert_on_miss {
220 let mut cache = self.ast_cache.write().await;
221 cache.insert(uri_str.clone(), build.clone());
222 }
223 Some(build)
224 }
225 Err(e) => {
226 self.client
227 .log_message(MessageType::ERROR, format!("failed to get AST: {e}"))
228 .await;
229 None
230 }
231 }
232 }
233
234 async fn get_source_bytes(&self, uri: &Url, file_path: &std::path::Path) -> Option<Vec<u8>> {
237 {
238 let text_cache = self.text_cache.read().await;
239 if let Some((_, content)) = text_cache.get(&uri.to_string()) {
240 return Some(content.as_bytes().to_vec());
241 }
242 }
243 match std::fs::read(file_path) {
244 Ok(bytes) => Some(bytes),
245 Err(e) => {
246 self.client
247 .log_message(MessageType::ERROR, format!("failed to read file: {e}"))
248 .await;
249 None
250 }
251 }
252 }
253}
254
255#[tower_lsp::async_trait]
256impl LanguageServer for ForgeLsp {
257 async fn initialize(
258 &self,
259 params: InitializeParams,
260 ) -> tower_lsp::jsonrpc::Result<InitializeResult> {
261 let client_encodings = params
263 .capabilities
264 .general
265 .as_ref()
266 .and_then(|g| g.position_encodings.as_deref());
267 let encoding = utils::PositionEncoding::negotiate(client_encodings);
268 utils::set_encoding(encoding);
269
270 Ok(InitializeResult {
271 server_info: Some(ServerInfo {
272 name: "Solidity Language Server".to_string(),
273 version: Some(env!("LONG_VERSION").to_string()),
274 }),
275 capabilities: ServerCapabilities {
276 position_encoding: Some(encoding.into()),
277 completion_provider: Some(CompletionOptions {
278 trigger_characters: Some(vec![".".to_string()]),
279 resolve_provider: Some(false),
280 ..Default::default()
281 }),
282 definition_provider: Some(OneOf::Left(true)),
283 declaration_provider: Some(DeclarationCapability::Simple(true)),
284 references_provider: Some(OneOf::Left(true)),
285 rename_provider: Some(OneOf::Right(RenameOptions {
286 prepare_provider: Some(true),
287 work_done_progress_options: WorkDoneProgressOptions {
288 work_done_progress: Some(true),
289 },
290 })),
291 workspace_symbol_provider: Some(OneOf::Left(true)),
292 document_symbol_provider: Some(OneOf::Left(true)),
293 hover_provider: Some(HoverProviderCapability::Simple(true)),
294 document_link_provider: Some(DocumentLinkOptions {
295 resolve_provider: Some(false),
296 work_done_progress_options: WorkDoneProgressOptions {
297 work_done_progress: None,
298 },
299 }),
300 document_formatting_provider: Some(OneOf::Left(true)),
301 inlay_hint_provider: Some(OneOf::Right(InlayHintServerCapabilities::Options(
302 InlayHintOptions {
303 resolve_provider: Some(false),
304 work_done_progress_options: WorkDoneProgressOptions {
305 work_done_progress: None,
306 },
307 },
308 ))),
309 semantic_tokens_provider: Some(
310 SemanticTokensServerCapabilities::SemanticTokensOptions(
311 SemanticTokensOptions {
312 legend: semantic_tokens::legend(),
313 full: Some(SemanticTokensFullOptions::Bool(true)),
314 range: None,
315 work_done_progress_options: WorkDoneProgressOptions {
316 work_done_progress: None,
317 },
318 },
319 ),
320 ),
321 text_document_sync: Some(TextDocumentSyncCapability::Options(
322 TextDocumentSyncOptions {
323 will_save: Some(true),
324 will_save_wait_until: None,
325 open_close: Some(true),
326 save: Some(TextDocumentSyncSaveOptions::SaveOptions(SaveOptions {
327 include_text: Some(true),
328 })),
329 change: Some(TextDocumentSyncKind::FULL),
330 },
331 )),
332 ..ServerCapabilities::default()
333 },
334 })
335 }
336
337 async fn initialized(&self, _: InitializedParams) {
338 self.client
339 .log_message(MessageType::INFO, "lsp server initialized.")
340 .await;
341 }
342
343 async fn shutdown(&self) -> tower_lsp::jsonrpc::Result<()> {
344 self.client
345 .log_message(MessageType::INFO, "lsp server shutting down.")
346 .await;
347 Ok(())
348 }
349
350 async fn did_open(&self, params: DidOpenTextDocumentParams) {
351 self.client
352 .log_message(MessageType::INFO, "file opened")
353 .await;
354
355 self.on_change(params.text_document).await
356 }
357
358 async fn did_change(&self, params: DidChangeTextDocumentParams) {
359 self.client
360 .log_message(MessageType::INFO, "file changed")
361 .await;
362
363 if let Some(change) = params.content_changes.into_iter().next() {
365 let mut text_cache = self.text_cache.write().await;
366 text_cache.insert(
367 params.text_document.uri.to_string(),
368 (params.text_document.version, change.text),
369 );
370 }
371 }
372
373 async fn did_save(&self, params: DidSaveTextDocumentParams) {
374 self.client
375 .log_message(MessageType::INFO, "file saved")
376 .await;
377
378 let text_content = if let Some(text) = params.text {
379 text
380 } else {
381 let cached = {
383 let text_cache = self.text_cache.read().await;
384 text_cache
385 .get(params.text_document.uri.as_str())
386 .map(|(_, content)| content.clone())
387 };
388 if let Some(content) = cached {
389 content
390 } else {
391 match std::fs::read_to_string(params.text_document.uri.path()) {
392 Ok(content) => content,
393 Err(e) => {
394 self.client
395 .log_message(
396 MessageType::ERROR,
397 format!("Failed to read file on save: {e}"),
398 )
399 .await;
400 return;
401 }
402 }
403 }
404 };
405
406 let version = self
407 .text_cache
408 .read()
409 .await
410 .get(params.text_document.uri.as_str())
411 .map(|(version, _)| *version)
412 .unwrap_or_default();
413
414 self.on_change(TextDocumentItem {
415 uri: params.text_document.uri,
416 text: text_content,
417 version,
418 language_id: "".to_string(),
419 })
420 .await;
421 }
422
423 async fn will_save(&self, params: WillSaveTextDocumentParams) {
424 self.client
425 .log_message(
426 MessageType::INFO,
427 format!(
428 "file will save reason:{:?} {}",
429 params.reason, params.text_document.uri
430 ),
431 )
432 .await;
433 }
434
435 async fn formatting(
436 &self,
437 params: DocumentFormattingParams,
438 ) -> tower_lsp::jsonrpc::Result<Option<Vec<TextEdit>>> {
439 self.client
440 .log_message(MessageType::INFO, "formatting request")
441 .await;
442
443 let uri = params.text_document.uri;
444 let file_path = match uri.to_file_path() {
445 Ok(path) => path,
446 Err(_) => {
447 self.client
448 .log_message(MessageType::ERROR, "Invalid file URI for formatting")
449 .await;
450 return Ok(None);
451 }
452 };
453 let path_str = match file_path.to_str() {
454 Some(s) => s,
455 None => {
456 self.client
457 .log_message(MessageType::ERROR, "Invalid file path for formatting")
458 .await;
459 return Ok(None);
460 }
461 };
462
463 let original_content = {
465 let text_cache = self.text_cache.read().await;
466 if let Some((_, content)) = text_cache.get(&uri.to_string()) {
467 content.clone()
468 } else {
469 match std::fs::read_to_string(&file_path) {
471 Ok(content) => content,
472 Err(_) => {
473 self.client
474 .log_message(MessageType::ERROR, "Failed to read file for formatting")
475 .await;
476 return Ok(None);
477 }
478 }
479 }
480 };
481
482 let formatted_content = match self.compiler.format(path_str).await {
484 Ok(content) => content,
485 Err(e) => {
486 self.client
487 .log_message(MessageType::WARNING, format!("Formatting failed: {e}"))
488 .await;
489 return Ok(None);
490 }
491 };
492
493 if original_content != formatted_content {
495 let end = utils::byte_offset_to_position(&original_content, original_content.len());
496
497 {
499 let mut text_cache = self.text_cache.write().await;
500 let version = text_cache
501 .get(&uri.to_string())
502 .map(|(v, _)| *v)
503 .unwrap_or(0);
504 text_cache.insert(uri.to_string(), (version, formatted_content.clone()));
505 }
506
507 let edit = TextEdit {
508 range: Range {
509 start: Position::default(),
510 end,
511 },
512 new_text: formatted_content,
513 };
514 Ok(Some(vec![edit]))
515 } else {
516 Ok(None)
517 }
518 }
519
520 async fn did_close(&self, params: DidCloseTextDocumentParams) {
521 let uri = params.text_document.uri.to_string();
522 self.ast_cache.write().await.remove(&uri);
523 self.text_cache.write().await.remove(&uri);
524 self.completion_cache.write().await.remove(&uri);
525 self.client
526 .log_message(MessageType::INFO, "file closed, caches cleared.")
527 .await;
528 }
529
530 async fn did_change_configuration(&self, _: DidChangeConfigurationParams) {
531 self.client
532 .log_message(MessageType::INFO, "configuration changed.")
533 .await;
534 }
535 async fn did_change_workspace_folders(&self, _: DidChangeWorkspaceFoldersParams) {
536 self.client
537 .log_message(MessageType::INFO, "workdspace folders changed.")
538 .await;
539 }
540
541 async fn did_change_watched_files(&self, _: DidChangeWatchedFilesParams) {
542 self.client
543 .log_message(MessageType::INFO, "watched files have changed.")
544 .await;
545 }
546
547 async fn completion(
548 &self,
549 params: CompletionParams,
550 ) -> tower_lsp::jsonrpc::Result<Option<CompletionResponse>> {
551 let uri = params.text_document_position.text_document.uri;
552 let position = params.text_document_position.position;
553
554 let trigger_char = params
555 .context
556 .as_ref()
557 .and_then(|ctx| ctx.trigger_character.as_deref());
558
559 let source_text = {
561 let text_cache = self.text_cache.read().await;
562 if let Some((_, text)) = text_cache.get(&uri.to_string()) {
563 text.clone()
564 } else {
565 match uri.to_file_path() {
566 Ok(path) => std::fs::read_to_string(&path).unwrap_or_default(),
567 Err(_) => return Ok(None),
568 }
569 }
570 };
571
572 let cached: Option<Arc<completion::CompletionCache>> = {
574 let comp_cache = self.completion_cache.read().await;
575 comp_cache.get(&uri.to_string()).cloned()
576 };
577
578 if cached.is_none() {
579 let ast_cache = self.ast_cache.clone();
581 let completion_cache = self.completion_cache.clone();
582 let uri_string = uri.to_string();
583 tokio::spawn(async move {
584 let cached_build = {
585 let cache = ast_cache.read().await;
586 match cache.get(&uri_string) {
587 Some(v) => v.clone(),
588 None => return,
589 }
590 };
591 if let Some(sources) = cached_build.ast.get("sources") {
592 let contracts = cached_build.ast.get("contracts");
593 let cc = completion::build_completion_cache(sources, contracts);
594 completion_cache
595 .write()
596 .await
597 .insert(uri_string, Arc::new(cc));
598 }
599 });
600 }
601
602 let cache_ref = cached.as_deref();
603
604 let file_id = {
606 let uri_path = uri.to_file_path().ok();
607 cache_ref.and_then(|c| {
608 uri_path.as_ref().and_then(|p| {
609 let path_str = p.to_str()?;
610 c.path_to_file_id.get(path_str).copied()
611 })
612 })
613 };
614
615 let result =
616 completion::handle_completion(cache_ref, &source_text, position, trigger_char, file_id);
617 Ok(result)
618 }
619
620 async fn goto_definition(
621 &self,
622 params: GotoDefinitionParams,
623 ) -> tower_lsp::jsonrpc::Result<Option<GotoDefinitionResponse>> {
624 self.client
625 .log_message(MessageType::INFO, "got textDocument/definition request")
626 .await;
627
628 let uri = params.text_document_position_params.text_document.uri;
629 let position = params.text_document_position_params.position;
630
631 let file_path = match uri.to_file_path() {
632 Ok(path) => path,
633 Err(_) => {
634 self.client
635 .log_message(MessageType::ERROR, "Invalid file uri")
636 .await;
637 return Ok(None);
638 }
639 };
640
641 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
642 Some(bytes) => bytes,
643 None => return Ok(None),
644 };
645
646 let source_text = String::from_utf8_lossy(&source_bytes).to_string();
647
648 let cursor_name = goto::cursor_context(&source_text, position).map(|ctx| ctx.name);
650
651 let (is_dirty, cached_build) = {
655 let text_version = self
656 .text_cache
657 .read()
658 .await
659 .get(&uri.to_string())
660 .map(|(v, _)| *v)
661 .unwrap_or(0);
662 let cb = self.get_or_fetch_build(&uri, &file_path, false).await;
663 let build_version = cb.as_ref().map(|b| b.build_version).unwrap_or(0);
664 (text_version > build_version, cb)
665 };
666
667 let validate_ts = |loc: &Location| -> bool {
673 let Some(ref name) = cursor_name else {
674 return true; };
676 let target_src = if loc.uri == uri {
677 Some(source_text.clone())
678 } else {
679 loc.uri
680 .to_file_path()
681 .ok()
682 .and_then(|p| std::fs::read_to_string(&p).ok())
683 };
684 match target_src {
685 Some(src) => goto::validate_goto_target(&src, loc, name),
686 None => true, }
688 };
689
690 if is_dirty {
691 self.client
692 .log_message(MessageType::INFO, "file is dirty, trying tree-sitter first")
693 .await;
694
695 let ts_result = {
697 let comp_cache = self.completion_cache.read().await;
698 let text_cache = self.text_cache.read().await;
699 if let Some(cc) = comp_cache.get(&uri.to_string()) {
700 goto::goto_definition_ts(&source_text, position, &uri, cc, &text_cache)
701 } else {
702 None
703 }
704 };
705
706 if let Some(location) = ts_result {
707 if validate_ts(&location) {
708 self.client
709 .log_message(
710 MessageType::INFO,
711 format!(
712 "found definition (tree-sitter) at {}:{}",
713 location.uri, location.range.start.line
714 ),
715 )
716 .await;
717 return Ok(Some(GotoDefinitionResponse::from(location)));
718 }
719 self.client
720 .log_message(
721 MessageType::INFO,
722 "tree-sitter result failed validation, trying AST fallback",
723 )
724 .await;
725 }
726
727 if let Some(ref cb) = cached_build {
732 if let Some(ref name) = cursor_name {
733 let byte_hint = goto::pos_to_bytes(&source_bytes, position);
734 if let Some(location) =
735 goto::goto_declaration_by_name(cb, &uri, name, byte_hint)
736 {
737 self.client
738 .log_message(
739 MessageType::INFO,
740 format!(
741 "found definition (AST by name) at {}:{}",
742 location.uri, location.range.start.line
743 ),
744 )
745 .await;
746 return Ok(Some(GotoDefinitionResponse::from(location)));
747 }
748 }
749 }
750 } else {
751 if let Some(ref cb) = cached_build {
753 if let Some(location) =
754 goto::goto_declaration(&cb.ast, &uri, position, &source_bytes)
755 {
756 self.client
757 .log_message(
758 MessageType::INFO,
759 format!(
760 "found definition (AST) at {}:{}",
761 location.uri, location.range.start.line
762 ),
763 )
764 .await;
765 return Ok(Some(GotoDefinitionResponse::from(location)));
766 }
767 }
768
769 let ts_result = {
771 let comp_cache = self.completion_cache.read().await;
772 let text_cache = self.text_cache.read().await;
773 if let Some(cc) = comp_cache.get(&uri.to_string()) {
774 goto::goto_definition_ts(&source_text, position, &uri, cc, &text_cache)
775 } else {
776 None
777 }
778 };
779
780 if let Some(location) = ts_result {
781 if validate_ts(&location) {
782 self.client
783 .log_message(
784 MessageType::INFO,
785 format!(
786 "found definition (tree-sitter fallback) at {}:{}",
787 location.uri, location.range.start.line
788 ),
789 )
790 .await;
791 return Ok(Some(GotoDefinitionResponse::from(location)));
792 }
793 self.client
794 .log_message(MessageType::INFO, "tree-sitter fallback failed validation")
795 .await;
796 }
797 }
798
799 self.client
800 .log_message(MessageType::INFO, "no definition found")
801 .await;
802 Ok(None)
803 }
804
805 async fn goto_declaration(
806 &self,
807 params: request::GotoDeclarationParams,
808 ) -> tower_lsp::jsonrpc::Result<Option<request::GotoDeclarationResponse>> {
809 self.client
810 .log_message(MessageType::INFO, "got textDocument/declaration request")
811 .await;
812
813 let uri = params.text_document_position_params.text_document.uri;
814 let position = params.text_document_position_params.position;
815
816 let file_path = match uri.to_file_path() {
817 Ok(path) => path,
818 Err(_) => {
819 self.client
820 .log_message(MessageType::ERROR, "invalid file uri")
821 .await;
822 return Ok(None);
823 }
824 };
825
826 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
827 Some(bytes) => bytes,
828 None => return Ok(None),
829 };
830
831 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
832 let cached_build = match cached_build {
833 Some(cb) => cb,
834 None => return Ok(None),
835 };
836
837 if let Some(location) =
838 goto::goto_declaration(&cached_build.ast, &uri, position, &source_bytes)
839 {
840 self.client
841 .log_message(
842 MessageType::INFO,
843 format!(
844 "found declaration at {}:{}",
845 location.uri, location.range.start.line
846 ),
847 )
848 .await;
849 Ok(Some(request::GotoDeclarationResponse::from(location)))
850 } else {
851 self.client
852 .log_message(MessageType::INFO, "no declaration found")
853 .await;
854 Ok(None)
855 }
856 }
857
858 async fn references(
859 &self,
860 params: ReferenceParams,
861 ) -> tower_lsp::jsonrpc::Result<Option<Vec<Location>>> {
862 self.client
863 .log_message(MessageType::INFO, "Got a textDocument/references request")
864 .await;
865
866 let uri = params.text_document_position.text_document.uri;
867 let position = params.text_document_position.position;
868 let file_path = match uri.to_file_path() {
869 Ok(path) => path,
870 Err(_) => {
871 self.client
872 .log_message(MessageType::ERROR, "Invalid file URI")
873 .await;
874 return Ok(None);
875 }
876 };
877 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
878 Some(bytes) => bytes,
879 None => return Ok(None),
880 };
881 let cached_build = self.get_or_fetch_build(&uri, &file_path, true).await;
882 let cached_build = match cached_build {
883 Some(cb) => cb,
884 None => return Ok(None),
885 };
886
887 let mut locations = references::goto_references(
889 &cached_build.ast,
890 &uri,
891 position,
892 &source_bytes,
893 params.context.include_declaration,
894 );
895
896 if let Some((def_abs_path, def_byte_offset)) =
898 references::resolve_target_location(&cached_build, &uri, position, &source_bytes)
899 {
900 let cache = self.ast_cache.read().await;
901 for (cached_uri, other_build) in cache.iter() {
902 if *cached_uri == uri.to_string() {
903 continue;
904 }
905 let other_locations = references::goto_references_for_target(
906 other_build,
907 &def_abs_path,
908 def_byte_offset,
909 None,
910 params.context.include_declaration,
911 );
912 locations.extend(other_locations);
913 }
914 }
915
916 let mut seen = std::collections::HashSet::new();
918 locations.retain(|loc| {
919 seen.insert((
920 loc.uri.clone(),
921 loc.range.start.line,
922 loc.range.start.character,
923 loc.range.end.line,
924 loc.range.end.character,
925 ))
926 });
927
928 if locations.is_empty() {
929 self.client
930 .log_message(MessageType::INFO, "No references found")
931 .await;
932 Ok(None)
933 } else {
934 self.client
935 .log_message(
936 MessageType::INFO,
937 format!("Found {} references", locations.len()),
938 )
939 .await;
940 Ok(Some(locations))
941 }
942 }
943
944 async fn prepare_rename(
945 &self,
946 params: TextDocumentPositionParams,
947 ) -> tower_lsp::jsonrpc::Result<Option<PrepareRenameResponse>> {
948 self.client
949 .log_message(MessageType::INFO, "got textDocument/prepareRename request")
950 .await;
951
952 let uri = params.text_document.uri;
953 let position = params.position;
954
955 let file_path = match uri.to_file_path() {
956 Ok(path) => path,
957 Err(_) => {
958 self.client
959 .log_message(MessageType::ERROR, "invalid file uri")
960 .await;
961 return Ok(None);
962 }
963 };
964
965 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
966 Some(bytes) => bytes,
967 None => return Ok(None),
968 };
969
970 if let Some(range) = rename::get_identifier_range(&source_bytes, position) {
971 self.client
972 .log_message(
973 MessageType::INFO,
974 format!(
975 "prepare rename range: {}:{}",
976 range.start.line, range.start.character
977 ),
978 )
979 .await;
980 Ok(Some(PrepareRenameResponse::Range(range)))
981 } else {
982 self.client
983 .log_message(MessageType::INFO, "no identifier found for prepare rename")
984 .await;
985 Ok(None)
986 }
987 }
988
989 async fn rename(
990 &self,
991 params: RenameParams,
992 ) -> tower_lsp::jsonrpc::Result<Option<WorkspaceEdit>> {
993 self.client
994 .log_message(MessageType::INFO, "got textDocument/rename request")
995 .await;
996
997 let uri = params.text_document_position.text_document.uri;
998 let position = params.text_document_position.position;
999 let new_name = params.new_name;
1000 let file_path = match uri.to_file_path() {
1001 Ok(p) => p,
1002 Err(_) => {
1003 self.client
1004 .log_message(MessageType::ERROR, "invalid file uri")
1005 .await;
1006 return Ok(None);
1007 }
1008 };
1009 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
1010 Some(bytes) => bytes,
1011 None => return Ok(None),
1012 };
1013
1014 let current_identifier = match rename::get_identifier_at_position(&source_bytes, position) {
1015 Some(id) => id,
1016 None => {
1017 self.client
1018 .log_message(MessageType::ERROR, "No identifier found at position")
1019 .await;
1020 return Ok(None);
1021 }
1022 };
1023
1024 if !utils::is_valid_solidity_identifier(&new_name) {
1025 return Err(tower_lsp::jsonrpc::Error::invalid_params(
1026 "new name is not a valid solidity identifier",
1027 ));
1028 }
1029
1030 if new_name == current_identifier {
1031 self.client
1032 .log_message(
1033 MessageType::INFO,
1034 "new name is the same as current identifier",
1035 )
1036 .await;
1037 return Ok(None);
1038 }
1039
1040 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
1041 let cached_build = match cached_build {
1042 Some(cb) => cb,
1043 None => return Ok(None),
1044 };
1045 let other_builds: Vec<Arc<goto::CachedBuild>> = {
1046 let cache = self.ast_cache.read().await;
1047 cache
1048 .iter()
1049 .filter(|(key, _)| **key != uri.to_string())
1050 .map(|(_, v)| v.clone())
1051 .collect()
1052 };
1053 let other_refs: Vec<&goto::CachedBuild> = other_builds.iter().map(|v| v.as_ref()).collect();
1054
1055 let text_buffers: HashMap<String, Vec<u8>> = {
1059 let text_cache = self.text_cache.read().await;
1060 text_cache
1061 .iter()
1062 .map(|(uri, (_, content))| (uri.clone(), content.as_bytes().to_vec()))
1063 .collect()
1064 };
1065
1066 match rename::rename_symbol(
1067 &cached_build,
1068 &uri,
1069 position,
1070 &source_bytes,
1071 new_name,
1072 &other_refs,
1073 &text_buffers,
1074 ) {
1075 Some(workspace_edit) => {
1076 self.client
1077 .log_message(
1078 MessageType::INFO,
1079 format!(
1080 "created rename edit with {} file(s), {} total change(s)",
1081 workspace_edit
1082 .changes
1083 .as_ref()
1084 .map(|c| c.len())
1085 .unwrap_or(0),
1086 workspace_edit
1087 .changes
1088 .as_ref()
1089 .map(|c| c.values().map(|v| v.len()).sum::<usize>())
1090 .unwrap_or(0)
1091 ),
1092 )
1093 .await;
1094
1095 Ok(Some(workspace_edit))
1100 }
1101
1102 None => {
1103 self.client
1104 .log_message(MessageType::INFO, "No locations found for renaming")
1105 .await;
1106 Ok(None)
1107 }
1108 }
1109 }
1110
1111 async fn symbol(
1112 &self,
1113 params: WorkspaceSymbolParams,
1114 ) -> tower_lsp::jsonrpc::Result<Option<Vec<SymbolInformation>>> {
1115 self.client
1116 .log_message(MessageType::INFO, "got workspace/symbol request")
1117 .await;
1118
1119 let files: Vec<(Url, String)> = {
1121 let cache = self.text_cache.read().await;
1122 cache
1123 .iter()
1124 .filter(|(uri_str, _)| uri_str.ends_with(".sol"))
1125 .filter_map(|(uri_str, (_, content))| {
1126 Url::parse(uri_str).ok().map(|uri| (uri, content.clone()))
1127 })
1128 .collect()
1129 };
1130
1131 let mut all_symbols = symbols::extract_workspace_symbols(&files);
1132 if !params.query.is_empty() {
1133 let query = params.query.to_lowercase();
1134 all_symbols.retain(|symbol| symbol.name.to_lowercase().contains(&query));
1135 }
1136 if all_symbols.is_empty() {
1137 self.client
1138 .log_message(MessageType::INFO, "No symbols found")
1139 .await;
1140 Ok(None)
1141 } else {
1142 self.client
1143 .log_message(
1144 MessageType::INFO,
1145 format!("found {} symbols", all_symbols.len()),
1146 )
1147 .await;
1148 Ok(Some(all_symbols))
1149 }
1150 }
1151
1152 async fn document_symbol(
1153 &self,
1154 params: DocumentSymbolParams,
1155 ) -> tower_lsp::jsonrpc::Result<Option<DocumentSymbolResponse>> {
1156 self.client
1157 .log_message(MessageType::INFO, "got textDocument/documentSymbol request")
1158 .await;
1159 let uri = params.text_document.uri;
1160 let file_path = match uri.to_file_path() {
1161 Ok(path) => path,
1162 Err(_) => {
1163 self.client
1164 .log_message(MessageType::ERROR, "invalid file uri")
1165 .await;
1166 return Ok(None);
1167 }
1168 };
1169
1170 let source = {
1172 let cache = self.text_cache.read().await;
1173 cache
1174 .get(&uri.to_string())
1175 .map(|(_, content)| content.clone())
1176 };
1177 let source = match source {
1178 Some(s) => s,
1179 None => match std::fs::read_to_string(&file_path) {
1180 Ok(s) => s,
1181 Err(_) => return Ok(None),
1182 },
1183 };
1184
1185 let symbols = symbols::extract_document_symbols(&source);
1186 if symbols.is_empty() {
1187 self.client
1188 .log_message(MessageType::INFO, "no document symbols found")
1189 .await;
1190 Ok(None)
1191 } else {
1192 self.client
1193 .log_message(
1194 MessageType::INFO,
1195 format!("found {} document symbols", symbols.len()),
1196 )
1197 .await;
1198 Ok(Some(DocumentSymbolResponse::Nested(symbols)))
1199 }
1200 }
1201
1202 async fn hover(&self, params: HoverParams) -> tower_lsp::jsonrpc::Result<Option<Hover>> {
1203 self.client
1204 .log_message(MessageType::INFO, "got textDocument/hover request")
1205 .await;
1206
1207 let uri = params.text_document_position_params.text_document.uri;
1208 let position = params.text_document_position_params.position;
1209
1210 let file_path = match uri.to_file_path() {
1211 Ok(path) => path,
1212 Err(_) => {
1213 self.client
1214 .log_message(MessageType::ERROR, "invalid file uri")
1215 .await;
1216 return Ok(None);
1217 }
1218 };
1219
1220 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
1221 Some(bytes) => bytes,
1222 None => return Ok(None),
1223 };
1224
1225 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
1226 let cached_build = match cached_build {
1227 Some(cb) => cb,
1228 None => return Ok(None),
1229 };
1230
1231 let result = hover::hover_info(&cached_build.ast, &uri, position, &source_bytes);
1232
1233 if result.is_some() {
1234 self.client
1235 .log_message(MessageType::INFO, "hover info found")
1236 .await;
1237 } else {
1238 self.client
1239 .log_message(MessageType::INFO, "no hover info found")
1240 .await;
1241 }
1242
1243 Ok(result)
1244 }
1245
1246 async fn document_link(
1247 &self,
1248 params: DocumentLinkParams,
1249 ) -> tower_lsp::jsonrpc::Result<Option<Vec<DocumentLink>>> {
1250 self.client
1251 .log_message(MessageType::INFO, "got textDocument/documentLink request")
1252 .await;
1253
1254 let uri = params.text_document.uri;
1255 let file_path = match uri.to_file_path() {
1256 Ok(path) => path,
1257 Err(_) => {
1258 self.client
1259 .log_message(MessageType::ERROR, "invalid file uri")
1260 .await;
1261 return Ok(None);
1262 }
1263 };
1264
1265 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
1266 Some(bytes) => bytes,
1267 None => return Ok(None),
1268 };
1269
1270 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
1271 let cached_build = match cached_build {
1272 Some(cb) => cb,
1273 None => return Ok(None),
1274 };
1275
1276 let result = links::document_links(&cached_build, &uri, &source_bytes);
1277
1278 if result.is_empty() {
1279 self.client
1280 .log_message(MessageType::INFO, "no document links found")
1281 .await;
1282 Ok(None)
1283 } else {
1284 self.client
1285 .log_message(
1286 MessageType::INFO,
1287 format!("found {} document links", result.len()),
1288 )
1289 .await;
1290 Ok(Some(result))
1291 }
1292 }
1293
1294 async fn semantic_tokens_full(
1295 &self,
1296 params: SemanticTokensParams,
1297 ) -> tower_lsp::jsonrpc::Result<Option<SemanticTokensResult>> {
1298 self.client
1299 .log_message(
1300 MessageType::INFO,
1301 "got textDocument/semanticTokens/full request",
1302 )
1303 .await;
1304
1305 let uri = params.text_document.uri;
1306 let source = {
1307 let cache = self.text_cache.read().await;
1308 cache.get(&uri.to_string()).map(|(_, s)| s.clone())
1309 };
1310
1311 let source = match source {
1312 Some(s) => s,
1313 None => {
1314 let file_path = match uri.to_file_path() {
1316 Ok(p) => p,
1317 Err(_) => return Ok(None),
1318 };
1319 match std::fs::read_to_string(&file_path) {
1320 Ok(s) => s,
1321 Err(_) => return Ok(None),
1322 }
1323 }
1324 };
1325
1326 let tokens = semantic_tokens::semantic_tokens_full(&source);
1327
1328 Ok(Some(SemanticTokensResult::Tokens(tokens)))
1329 }
1330
1331 async fn inlay_hint(
1332 &self,
1333 params: InlayHintParams,
1334 ) -> tower_lsp::jsonrpc::Result<Option<Vec<InlayHint>>> {
1335 self.client
1336 .log_message(MessageType::INFO, "got textDocument/inlayHint request")
1337 .await;
1338
1339 let uri = params.text_document.uri;
1340 let range = params.range;
1341
1342 let file_path = match uri.to_file_path() {
1343 Ok(path) => path,
1344 Err(_) => {
1345 self.client
1346 .log_message(MessageType::ERROR, "invalid file uri")
1347 .await;
1348 return Ok(None);
1349 }
1350 };
1351
1352 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
1353 Some(bytes) => bytes,
1354 None => return Ok(None),
1355 };
1356
1357 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
1358 let cached_build = match cached_build {
1359 Some(cb) => cb,
1360 None => return Ok(None),
1361 };
1362
1363 let hints = inlay_hints::inlay_hints(&cached_build, &uri, range, &source_bytes);
1364
1365 if hints.is_empty() {
1366 self.client
1367 .log_message(MessageType::INFO, "no inlay hints found")
1368 .await;
1369 Ok(None)
1370 } else {
1371 self.client
1372 .log_message(
1373 MessageType::INFO,
1374 format!("found {} inlay hints", hints.len()),
1375 )
1376 .await;
1377 Ok(Some(hints))
1378 }
1379 }
1380}