1use crate::completion;
2use crate::config::{self, FoundryConfig, LintConfig, Settings};
3use crate::goto;
4use crate::hover;
5use crate::inlay_hints;
6use crate::links;
7use crate::references;
8use crate::rename;
9use crate::runner::{ForgeRunner, Runner};
10use crate::semantic_tokens;
11use crate::symbols;
12use crate::utils;
13use std::collections::HashMap;
14use std::sync::Arc;
15use std::sync::atomic::{AtomicU64, Ordering};
16use tokio::sync::RwLock;
17use tower_lsp::{Client, LanguageServer, lsp_types::*};
18
19type SemanticTokenCache = HashMap<String, (String, Vec<SemanticToken>)>;
21
22pub struct ForgeLsp {
23 client: Client,
24 compiler: Arc<dyn Runner>,
25 ast_cache: Arc<RwLock<HashMap<String, Arc<goto::CachedBuild>>>>,
26 text_cache: Arc<RwLock<HashMap<String, (i32, String)>>>,
30 completion_cache: Arc<RwLock<HashMap<String, Arc<completion::CompletionCache>>>>,
31 lint_config: Arc<RwLock<LintConfig>>,
33 foundry_config: Arc<RwLock<FoundryConfig>>,
35 client_capabilities: Arc<RwLock<Option<ClientCapabilities>>>,
37 settings: Arc<RwLock<Settings>>,
39 use_solc: bool,
41 semantic_token_cache: Arc<RwLock<SemanticTokenCache>>,
43 semantic_token_id: Arc<AtomicU64>,
45 root_uri: Arc<RwLock<Option<Url>>>,
47 project_indexed: Arc<std::sync::atomic::AtomicBool>,
49}
50
51impl ForgeLsp {
52 pub fn new(client: Client, use_solar: bool, use_solc: bool) -> Self {
53 let compiler: Arc<dyn Runner> = if use_solar {
54 Arc::new(crate::solar_runner::SolarRunner)
55 } else {
56 Arc::new(ForgeRunner)
57 };
58 let ast_cache = Arc::new(RwLock::new(HashMap::new()));
59 let text_cache = Arc::new(RwLock::new(HashMap::new()));
60 let completion_cache = Arc::new(RwLock::new(HashMap::new()));
61 let lint_config = Arc::new(RwLock::new(LintConfig::default()));
62 let foundry_config = Arc::new(RwLock::new(FoundryConfig::default()));
63 let client_capabilities = Arc::new(RwLock::new(None));
64 let settings = Arc::new(RwLock::new(Settings::default()));
65 Self {
66 client,
67 compiler,
68 ast_cache,
69 text_cache,
70 completion_cache,
71 lint_config,
72 foundry_config,
73 client_capabilities,
74 settings,
75 use_solc,
76 semantic_token_cache: Arc::new(RwLock::new(HashMap::new())),
77 semantic_token_id: Arc::new(AtomicU64::new(0)),
78 root_uri: Arc::new(RwLock::new(None)),
79 project_indexed: Arc::new(std::sync::atomic::AtomicBool::new(false)),
80 }
81 }
82
83 async fn on_change(&self, params: TextDocumentItem) {
84 let uri = params.uri.clone();
85 let version = params.version;
86
87 let file_path = match uri.to_file_path() {
88 Ok(path) => path,
89 Err(_) => {
90 self.client
91 .log_message(MessageType::ERROR, "Invalid file URI")
92 .await;
93 return;
94 }
95 };
96
97 let path_str = match file_path.to_str() {
98 Some(s) => s,
99 None => {
100 self.client
101 .log_message(MessageType::ERROR, "Invalid file path")
102 .await;
103 return;
104 }
105 };
106
107 let (should_lint, lint_settings) = {
109 let lint_cfg = self.lint_config.read().await;
110 let settings = self.settings.read().await;
111 let enabled = lint_cfg.should_lint(&file_path) && settings.lint.enabled;
112 let ls = settings.lint.clone();
113 (enabled, ls)
114 };
115
116 let (lint_result, build_result, ast_result) = if self.use_solc {
120 let foundry_cfg = self.foundry_config.read().await.clone();
121 let solc_future = crate::solc::solc_ast(path_str, &foundry_cfg, Some(&self.client));
122
123 if should_lint {
124 let (lint, solc) = tokio::join!(
125 self.compiler.get_lint_diagnostics(&uri, &lint_settings),
126 solc_future
127 );
128 match solc {
129 Ok(data) => {
130 self.client
131 .log_message(
132 MessageType::INFO,
133 "solc: AST + diagnostics from single run",
134 )
135 .await;
136 let content = tokio::fs::read_to_string(&file_path)
138 .await
139 .unwrap_or_default();
140 let build_diags = crate::build::build_output_to_diagnostics(
141 &data,
142 &file_path,
143 &content,
144 &foundry_cfg.ignored_error_codes,
145 );
146 (Some(lint), Ok(build_diags), Ok(data))
147 }
148 Err(e) => {
149 self.client
150 .log_message(
151 MessageType::WARNING,
152 format!("solc failed, falling back to forge: {e}"),
153 )
154 .await;
155 let (build, ast) = tokio::join!(
156 self.compiler.get_build_diagnostics(&uri),
157 self.compiler.ast(path_str)
158 );
159 (Some(lint), build, ast)
160 }
161 }
162 } else {
163 self.client
164 .log_message(
165 MessageType::INFO,
166 format!("skipping lint for ignored file: {path_str}"),
167 )
168 .await;
169 match solc_future.await {
170 Ok(data) => {
171 self.client
172 .log_message(
173 MessageType::INFO,
174 "solc: AST + diagnostics from single run",
175 )
176 .await;
177 let content = tokio::fs::read_to_string(&file_path)
178 .await
179 .unwrap_or_default();
180 let build_diags = crate::build::build_output_to_diagnostics(
181 &data,
182 &file_path,
183 &content,
184 &foundry_cfg.ignored_error_codes,
185 );
186 (None, Ok(build_diags), Ok(data))
187 }
188 Err(e) => {
189 self.client
190 .log_message(
191 MessageType::WARNING,
192 format!("solc failed, falling back to forge: {e}"),
193 )
194 .await;
195 let (build, ast) = tokio::join!(
196 self.compiler.get_build_diagnostics(&uri),
197 self.compiler.ast(path_str)
198 );
199 (None, build, ast)
200 }
201 }
202 }
203 } else {
204 if should_lint {
206 let (lint, build, ast) = tokio::join!(
207 self.compiler.get_lint_diagnostics(&uri, &lint_settings),
208 self.compiler.get_build_diagnostics(&uri),
209 self.compiler.ast(path_str)
210 );
211 (Some(lint), build, ast)
212 } else {
213 self.client
214 .log_message(
215 MessageType::INFO,
216 format!("skipping lint for ignored file: {path_str}"),
217 )
218 .await;
219 let (build, ast) = tokio::join!(
220 self.compiler.get_build_diagnostics(&uri),
221 self.compiler.ast(path_str)
222 );
223 (None, build, ast)
224 }
225 };
226
227 let build_succeeded = matches!(&build_result, Ok(diagnostics) if diagnostics.iter().all(|d| d.severity != Some(DiagnosticSeverity::ERROR)));
229
230 if build_succeeded {
231 if let Ok(ast_data) = ast_result {
232 let cached_build = Arc::new(goto::CachedBuild::new(ast_data, version));
233 let mut cache = self.ast_cache.write().await;
234 cache.insert(uri.to_string(), cached_build.clone());
235 drop(cache);
236
237 {
239 let mut cc = self.completion_cache.write().await;
240 cc.insert(uri.to_string(), cached_build.completion_cache.clone());
241 }
242 self.client
243 .log_message(MessageType::INFO, "Build successful, AST cache updated")
244 .await;
245 } else if let Err(e) = ast_result {
246 self.client
247 .log_message(
248 MessageType::INFO,
249 format!("Build succeeded but failed to get AST: {e}"),
250 )
251 .await;
252 }
253 } else {
254 self.client
256 .log_message(
257 MessageType::INFO,
258 "Build errors detected, keeping existing AST cache",
259 )
260 .await;
261 }
262
263 {
265 let mut text_cache = self.text_cache.write().await;
266 let uri_str = uri.to_string();
267 let existing_version = text_cache.get(&uri_str).map(|(v, _)| *v).unwrap_or(-1);
268 if version >= existing_version {
269 text_cache.insert(uri_str, (version, params.text));
270 }
271 }
272
273 let mut all_diagnostics = vec![];
274
275 if let Some(lint_result) = lint_result {
276 match lint_result {
277 Ok(mut lints) => {
278 if !lint_settings.exclude.is_empty() {
280 lints.retain(|d| {
281 if let Some(NumberOrString::String(code)) = &d.code {
282 !lint_settings.exclude.iter().any(|ex| ex == code)
283 } else {
284 true
285 }
286 });
287 }
288 self.client
289 .log_message(
290 MessageType::INFO,
291 format!("found {} lint diagnostics", lints.len()),
292 )
293 .await;
294 all_diagnostics.append(&mut lints);
295 }
296 Err(e) => {
297 self.client
298 .log_message(
299 MessageType::ERROR,
300 format!("Forge lint diagnostics failed: {e}"),
301 )
302 .await;
303 }
304 }
305 }
306
307 match build_result {
308 Ok(mut builds) => {
309 self.client
310 .log_message(
311 MessageType::INFO,
312 format!("found {} build diagnostics", builds.len()),
313 )
314 .await;
315 all_diagnostics.append(&mut builds);
316 }
317 Err(e) => {
318 self.client
319 .log_message(
320 MessageType::WARNING,
321 format!("Forge build diagnostics failed: {e}"),
322 )
323 .await;
324 }
325 }
326
327 let is_test_or_script = path_str.ends_with(".t.sol") || path_str.ends_with(".s.sol");
335 if build_succeeded
336 && self.use_solc
337 && !is_test_or_script
338 && !self
339 .project_indexed
340 .load(std::sync::atomic::Ordering::Relaxed)
341 {
342 self.project_indexed
343 .store(true, std::sync::atomic::Ordering::Relaxed);
344 let foundry_config = self.foundry_config.read().await.clone();
345 let root_uri = self.root_uri.read().await.clone();
346
347 let cache_key = root_uri.as_ref().map(|u| u.to_string());
349
350 if let Some(cache_key) = cache_key {
351 if foundry_config
352 .root
353 .join(&foundry_config.sources_dir)
354 .is_dir()
355 {
356 match crate::solc::solc_project_index(&foundry_config, Some(&self.client)).await
357 {
358 Ok(ast_data) => {
359 let cached_build = Arc::new(crate::goto::CachedBuild::new(ast_data, 0));
360 let source_count = cached_build.nodes.len();
361 self.ast_cache.write().await.insert(cache_key, cached_build);
362 self.client
363 .log_message(
364 MessageType::INFO,
365 format!("project index: cached {} source files", source_count),
366 )
367 .await;
368 }
369 Err(e) => {
370 self.client
371 .log_message(
372 MessageType::WARNING,
373 format!("project index failed: {e}"),
374 )
375 .await;
376 }
377 }
378 } else {
379 self.client
380 .log_message(
381 MessageType::INFO,
382 format!(
383 "project index: {}/{} not found, skipping",
384 foundry_config.root.display(),
385 foundry_config.sources_dir
386 ),
387 )
388 .await;
389 }
390 }
391 }
392
393 for diag in &mut all_diagnostics {
397 if diag.message.is_empty() {
398 diag.message = "Unknown issue".to_string();
399 }
400 }
401
402 self.client
404 .publish_diagnostics(uri, all_diagnostics, None)
405 .await;
406
407 if build_succeeded {
409 let client = self.client.clone();
410 tokio::spawn(async move {
411 let _ = client.inlay_hint_refresh().await;
412 });
413 }
414 }
415
416 async fn get_or_fetch_build(
425 &self,
426 uri: &Url,
427 file_path: &std::path::Path,
428 insert_on_miss: bool,
429 ) -> Option<Arc<goto::CachedBuild>> {
430 let uri_str = uri.to_string();
431
432 {
435 let cache = self.ast_cache.read().await;
436 if let Some(cached) = cache.get(&uri_str) {
437 return Some(cached.clone());
438 }
439 }
440
441 if !insert_on_miss {
445 return None;
446 }
447
448 let path_str = file_path.to_str()?;
450 let ast_result = if self.use_solc {
451 let foundry_cfg = self.foundry_config.read().await.clone();
452 match crate::solc::solc_ast(path_str, &foundry_cfg, Some(&self.client)).await {
453 Ok(data) => Ok(data),
454 Err(_) => self.compiler.ast(path_str).await,
455 }
456 } else {
457 self.compiler.ast(path_str).await
458 };
459 match ast_result {
460 Ok(data) => {
461 let build = Arc::new(goto::CachedBuild::new(data, 0));
464 let mut cache = self.ast_cache.write().await;
465 cache.insert(uri_str.clone(), build.clone());
466 Some(build)
467 }
468 Err(e) => {
469 self.client
470 .log_message(MessageType::ERROR, format!("failed to get AST: {e}"))
471 .await;
472 None
473 }
474 }
475 }
476
477 async fn get_source_bytes(&self, uri: &Url, file_path: &std::path::Path) -> Option<Vec<u8>> {
480 {
481 let text_cache = self.text_cache.read().await;
482 if let Some((_, content)) = text_cache.get(&uri.to_string()) {
483 return Some(content.as_bytes().to_vec());
484 }
485 }
486 match std::fs::read(file_path) {
487 Ok(bytes) => Some(bytes),
488 Err(e) => {
489 self.client
490 .log_message(MessageType::ERROR, format!("failed to read file: {e}"))
491 .await;
492 None
493 }
494 }
495 }
496}
497
498#[tower_lsp::async_trait]
499impl LanguageServer for ForgeLsp {
500 async fn initialize(
501 &self,
502 params: InitializeParams,
503 ) -> tower_lsp::jsonrpc::Result<InitializeResult> {
504 {
506 let mut caps = self.client_capabilities.write().await;
507 *caps = Some(params.capabilities.clone());
508 }
509
510 if let Some(init_opts) = ¶ms.initialization_options {
512 let s = config::parse_settings(init_opts);
513 self.client
514 .log_message(
515 MessageType::INFO,
516 format!(
517 "settings: inlayHints.parameters={}, inlayHints.gasEstimates={}, lint.enabled={}, lint.severity={:?}, lint.only={:?}, lint.exclude={:?}",
518 s.inlay_hints.parameters, s.inlay_hints.gas_estimates, s.lint.enabled, s.lint.severity, s.lint.only, s.lint.exclude,
519 ),
520 )
521 .await;
522 let mut settings = self.settings.write().await;
523 *settings = s;
524 }
525
526 if let Some(uri) = params.root_uri.as_ref() {
528 let mut root = self.root_uri.write().await;
529 *root = Some(uri.clone());
530 }
531
532 if let Some(root_uri) = params
534 .root_uri
535 .as_ref()
536 .and_then(|uri| uri.to_file_path().ok())
537 {
538 let lint_cfg = config::load_lint_config(&root_uri);
539 self.client
540 .log_message(
541 MessageType::INFO,
542 format!(
543 "loaded foundry.toml lint config: lint_on_build={}, ignore_patterns={}",
544 lint_cfg.lint_on_build,
545 lint_cfg.ignore_patterns.len()
546 ),
547 )
548 .await;
549 let mut config = self.lint_config.write().await;
550 *config = lint_cfg;
551
552 let foundry_cfg = config::load_foundry_config(&root_uri);
553 self.client
554 .log_message(
555 MessageType::INFO,
556 format!(
557 "loaded foundry.toml project config: solc_version={:?}, remappings={}",
558 foundry_cfg.solc_version,
559 foundry_cfg.remappings.len()
560 ),
561 )
562 .await;
563 if foundry_cfg.via_ir {
564 self.client
565 .log_message(
566 MessageType::WARNING,
567 "via_ir is enabled in foundry.toml — gas estimate inlay hints are disabled to avoid slow compilation",
568 )
569 .await;
570 }
571 let mut fc = self.foundry_config.write().await;
572 *fc = foundry_cfg;
573 }
574
575 let client_encodings = params
577 .capabilities
578 .general
579 .as_ref()
580 .and_then(|g| g.position_encodings.as_deref());
581 let encoding = utils::PositionEncoding::negotiate(client_encodings);
582 utils::set_encoding(encoding);
583
584 Ok(InitializeResult {
585 server_info: Some(ServerInfo {
586 name: "Solidity Language Server".to_string(),
587 version: Some(env!("LONG_VERSION").to_string()),
588 }),
589 capabilities: ServerCapabilities {
590 position_encoding: Some(encoding.into()),
591 completion_provider: Some(CompletionOptions {
592 trigger_characters: Some(vec![".".to_string()]),
593 resolve_provider: Some(false),
594 ..Default::default()
595 }),
596 signature_help_provider: Some(SignatureHelpOptions {
597 trigger_characters: Some(vec![
598 "(".to_string(),
599 ",".to_string(),
600 "[".to_string(),
601 ]),
602 retrigger_characters: None,
603 work_done_progress_options: WorkDoneProgressOptions {
604 work_done_progress: None,
605 },
606 }),
607 definition_provider: Some(OneOf::Left(true)),
608 declaration_provider: Some(DeclarationCapability::Simple(true)),
609 references_provider: Some(OneOf::Left(true)),
610 rename_provider: Some(OneOf::Right(RenameOptions {
611 prepare_provider: Some(true),
612 work_done_progress_options: WorkDoneProgressOptions {
613 work_done_progress: Some(true),
614 },
615 })),
616 workspace_symbol_provider: Some(OneOf::Left(true)),
617 document_symbol_provider: Some(OneOf::Left(true)),
618 hover_provider: Some(HoverProviderCapability::Simple(true)),
619 document_link_provider: Some(DocumentLinkOptions {
620 resolve_provider: Some(false),
621 work_done_progress_options: WorkDoneProgressOptions {
622 work_done_progress: None,
623 },
624 }),
625 document_formatting_provider: Some(OneOf::Left(true)),
626 code_lens_provider: None,
627 inlay_hint_provider: Some(OneOf::Right(InlayHintServerCapabilities::Options(
628 InlayHintOptions {
629 resolve_provider: Some(false),
630 work_done_progress_options: WorkDoneProgressOptions {
631 work_done_progress: None,
632 },
633 },
634 ))),
635 semantic_tokens_provider: Some(
636 SemanticTokensServerCapabilities::SemanticTokensOptions(
637 SemanticTokensOptions {
638 legend: semantic_tokens::legend(),
639 full: Some(SemanticTokensFullOptions::Delta { delta: Some(true) }),
640 range: Some(true),
641 work_done_progress_options: WorkDoneProgressOptions {
642 work_done_progress: None,
643 },
644 },
645 ),
646 ),
647 text_document_sync: Some(TextDocumentSyncCapability::Options(
648 TextDocumentSyncOptions {
649 will_save: Some(true),
650 will_save_wait_until: None,
651 open_close: Some(true),
652 save: Some(TextDocumentSyncSaveOptions::SaveOptions(SaveOptions {
653 include_text: Some(true),
654 })),
655 change: Some(TextDocumentSyncKind::FULL),
656 },
657 )),
658 ..ServerCapabilities::default()
659 },
660 })
661 }
662
663 async fn initialized(&self, _: InitializedParams) {
664 self.client
665 .log_message(MessageType::INFO, "lsp server initialized.")
666 .await;
667
668 let supports_dynamic = self
670 .client_capabilities
671 .read()
672 .await
673 .as_ref()
674 .and_then(|caps| caps.workspace.as_ref())
675 .and_then(|ws| ws.did_change_watched_files.as_ref())
676 .and_then(|dcwf| dcwf.dynamic_registration)
677 .unwrap_or(false);
678
679 if supports_dynamic {
680 let registration = Registration {
681 id: "foundry-toml-watcher".to_string(),
682 method: "workspace/didChangeWatchedFiles".to_string(),
683 register_options: Some(
684 serde_json::to_value(DidChangeWatchedFilesRegistrationOptions {
685 watchers: vec![
686 FileSystemWatcher {
687 glob_pattern: GlobPattern::String("**/foundry.toml".to_string()),
688 kind: Some(WatchKind::all()),
689 },
690 FileSystemWatcher {
691 glob_pattern: GlobPattern::String("**/remappings.txt".to_string()),
692 kind: Some(WatchKind::all()),
693 },
694 ],
695 })
696 .unwrap(),
697 ),
698 };
699
700 if let Err(e) = self.client.register_capability(vec![registration]).await {
701 self.client
702 .log_message(
703 MessageType::WARNING,
704 format!("failed to register foundry.toml watcher: {e}"),
705 )
706 .await;
707 } else {
708 self.client
709 .log_message(MessageType::INFO, "registered foundry.toml file watcher")
710 .await;
711 }
712 }
713 }
714
715 async fn shutdown(&self) -> tower_lsp::jsonrpc::Result<()> {
716 self.client
717 .log_message(MessageType::INFO, "lsp server shutting down.")
718 .await;
719 Ok(())
720 }
721
722 async fn did_open(&self, params: DidOpenTextDocumentParams) {
723 self.client
724 .log_message(MessageType::INFO, "file opened")
725 .await;
726
727 self.on_change(params.text_document).await
728 }
729
730 async fn did_change(&self, params: DidChangeTextDocumentParams) {
731 self.client
732 .log_message(MessageType::INFO, "file changed")
733 .await;
734
735 if let Some(change) = params.content_changes.into_iter().next() {
737 let mut text_cache = self.text_cache.write().await;
738 text_cache.insert(
739 params.text_document.uri.to_string(),
740 (params.text_document.version, change.text),
741 );
742 }
743 }
744
745 async fn did_save(&self, params: DidSaveTextDocumentParams) {
746 self.client
747 .log_message(MessageType::INFO, "file saved")
748 .await;
749
750 let text_content = if let Some(text) = params.text {
751 text
752 } else {
753 let cached = {
755 let text_cache = self.text_cache.read().await;
756 text_cache
757 .get(params.text_document.uri.as_str())
758 .map(|(_, content)| content.clone())
759 };
760 if let Some(content) = cached {
761 content
762 } else {
763 match std::fs::read_to_string(params.text_document.uri.path()) {
764 Ok(content) => content,
765 Err(e) => {
766 self.client
767 .log_message(
768 MessageType::ERROR,
769 format!("Failed to read file on save: {e}"),
770 )
771 .await;
772 return;
773 }
774 }
775 }
776 };
777
778 let version = self
779 .text_cache
780 .read()
781 .await
782 .get(params.text_document.uri.as_str())
783 .map(|(version, _)| *version)
784 .unwrap_or_default();
785
786 self.on_change(TextDocumentItem {
787 uri: params.text_document.uri,
788 text: text_content,
789 version,
790 language_id: "".to_string(),
791 })
792 .await;
793 }
794
795 async fn will_save(&self, params: WillSaveTextDocumentParams) {
796 self.client
797 .log_message(
798 MessageType::INFO,
799 format!(
800 "file will save reason:{:?} {}",
801 params.reason, params.text_document.uri
802 ),
803 )
804 .await;
805 }
806
807 async fn formatting(
808 &self,
809 params: DocumentFormattingParams,
810 ) -> tower_lsp::jsonrpc::Result<Option<Vec<TextEdit>>> {
811 self.client
812 .log_message(MessageType::INFO, "formatting request")
813 .await;
814
815 let uri = params.text_document.uri;
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 for formatting")
821 .await;
822 return Ok(None);
823 }
824 };
825 let path_str = match file_path.to_str() {
826 Some(s) => s,
827 None => {
828 self.client
829 .log_message(MessageType::ERROR, "Invalid file path for formatting")
830 .await;
831 return Ok(None);
832 }
833 };
834
835 let original_content = {
837 let text_cache = self.text_cache.read().await;
838 if let Some((_, content)) = text_cache.get(&uri.to_string()) {
839 content.clone()
840 } else {
841 match std::fs::read_to_string(&file_path) {
843 Ok(content) => content,
844 Err(_) => {
845 self.client
846 .log_message(MessageType::ERROR, "Failed to read file for formatting")
847 .await;
848 return Ok(None);
849 }
850 }
851 }
852 };
853
854 let formatted_content = match self.compiler.format(path_str).await {
856 Ok(content) => content,
857 Err(e) => {
858 self.client
859 .log_message(MessageType::WARNING, format!("Formatting failed: {e}"))
860 .await;
861 return Ok(None);
862 }
863 };
864
865 if original_content != formatted_content {
867 let end = utils::byte_offset_to_position(&original_content, original_content.len());
868
869 {
871 let mut text_cache = self.text_cache.write().await;
872 let version = text_cache
873 .get(&uri.to_string())
874 .map(|(v, _)| *v)
875 .unwrap_or(0);
876 text_cache.insert(uri.to_string(), (version, formatted_content.clone()));
877 }
878
879 let edit = TextEdit {
880 range: Range {
881 start: Position::default(),
882 end,
883 },
884 new_text: formatted_content,
885 };
886 Ok(Some(vec![edit]))
887 } else {
888 Ok(None)
889 }
890 }
891
892 async fn did_close(&self, params: DidCloseTextDocumentParams) {
893 let uri = params.text_document.uri.to_string();
894 self.ast_cache.write().await.remove(&uri);
895 self.text_cache.write().await.remove(&uri);
896 self.completion_cache.write().await.remove(&uri);
897 self.client
898 .log_message(MessageType::INFO, "file closed, caches cleared.")
899 .await;
900 }
901
902 async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
903 let s = config::parse_settings(¶ms.settings);
904 self.client
905 .log_message(
906 MessageType::INFO,
907 format!(
908 "settings updated: inlayHints.parameters={}, inlayHints.gasEstimates={}, lint.enabled={}, lint.severity={:?}, lint.only={:?}, lint.exclude={:?}",
909 s.inlay_hints.parameters, s.inlay_hints.gas_estimates, s.lint.enabled, s.lint.severity, s.lint.only, s.lint.exclude,
910 ),
911 )
912 .await;
913 let mut settings = self.settings.write().await;
914 *settings = s;
915
916 let client = self.client.clone();
918 tokio::spawn(async move {
919 let _ = client.inlay_hint_refresh().await;
920 });
921 }
922 async fn did_change_workspace_folders(&self, _: DidChangeWorkspaceFoldersParams) {
923 self.client
924 .log_message(MessageType::INFO, "workdspace folders changed.")
925 .await;
926 }
927
928 async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
929 self.client
930 .log_message(MessageType::INFO, "watched files have changed.")
931 .await;
932
933 for change in ¶ms.changes {
935 let path = match change.uri.to_file_path() {
936 Ok(p) => p,
937 Err(_) => continue,
938 };
939
940 let filename = path.file_name().and_then(|n| n.to_str());
941
942 if filename == Some("foundry.toml") {
943 let lint_cfg = config::load_lint_config_from_toml(&path);
944 self.client
945 .log_message(
946 MessageType::INFO,
947 format!(
948 "reloaded foundry.toml lint config: lint_on_build={}, ignore_patterns={}",
949 lint_cfg.lint_on_build,
950 lint_cfg.ignore_patterns.len()
951 ),
952 )
953 .await;
954 let mut lc = self.lint_config.write().await;
955 *lc = lint_cfg;
956
957 let foundry_cfg = config::load_foundry_config_from_toml(&path);
958 self.client
959 .log_message(
960 MessageType::INFO,
961 format!(
962 "reloaded foundry.toml project config: solc_version={:?}, remappings={}",
963 foundry_cfg.solc_version,
964 foundry_cfg.remappings.len()
965 ),
966 )
967 .await;
968 if foundry_cfg.via_ir {
969 self.client
970 .log_message(
971 MessageType::WARNING,
972 "via_ir is enabled in foundry.toml — gas estimate inlay hints are disabled to avoid slow compilation",
973 )
974 .await;
975 }
976 let mut fc = self.foundry_config.write().await;
977 *fc = foundry_cfg;
978 break;
979 }
980
981 if filename == Some("remappings.txt") {
982 self.client
983 .log_message(
984 MessageType::INFO,
985 "remappings.txt changed, config may need refresh",
986 )
987 .await;
988 }
991 }
992 }
993
994 async fn completion(
995 &self,
996 params: CompletionParams,
997 ) -> tower_lsp::jsonrpc::Result<Option<CompletionResponse>> {
998 let uri = params.text_document_position.text_document.uri;
999 let position = params.text_document_position.position;
1000
1001 let trigger_char = params
1002 .context
1003 .as_ref()
1004 .and_then(|ctx| ctx.trigger_character.as_deref());
1005
1006 let source_text = {
1008 let text_cache = self.text_cache.read().await;
1009 if let Some((_, text)) = text_cache.get(&uri.to_string()) {
1010 text.clone()
1011 } else {
1012 match uri.to_file_path() {
1013 Ok(path) => std::fs::read_to_string(&path).unwrap_or_default(),
1014 Err(_) => return Ok(None),
1015 }
1016 }
1017 };
1018
1019 let cached: Option<Arc<completion::CompletionCache>> = {
1021 let comp_cache = self.completion_cache.read().await;
1022 comp_cache.get(&uri.to_string()).cloned()
1023 };
1024
1025 if cached.is_none() {
1026 let ast_cache = self.ast_cache.clone();
1028 let completion_cache = self.completion_cache.clone();
1029 let uri_string = uri.to_string();
1030 tokio::spawn(async move {
1031 let cached_build = {
1032 let cache = ast_cache.read().await;
1033 match cache.get(&uri_string) {
1034 Some(v) => v.clone(),
1035 None => return,
1036 }
1037 };
1038 completion_cache
1039 .write()
1040 .await
1041 .insert(uri_string, cached_build.completion_cache.clone());
1042 });
1043 }
1044
1045 let cache_ref = cached.as_deref();
1046
1047 let file_id = {
1049 let uri_path = uri.to_file_path().ok();
1050 cache_ref.and_then(|c| {
1051 uri_path.as_ref().and_then(|p| {
1052 let path_str = p.to_str()?;
1053 c.path_to_file_id.get(path_str).copied()
1054 })
1055 })
1056 };
1057
1058 let result =
1059 completion::handle_completion(cache_ref, &source_text, position, trigger_char, file_id);
1060 Ok(result)
1061 }
1062
1063 async fn goto_definition(
1064 &self,
1065 params: GotoDefinitionParams,
1066 ) -> tower_lsp::jsonrpc::Result<Option<GotoDefinitionResponse>> {
1067 self.client
1068 .log_message(MessageType::INFO, "got textDocument/definition request")
1069 .await;
1070
1071 let uri = params.text_document_position_params.text_document.uri;
1072 let position = params.text_document_position_params.position;
1073
1074 let file_path = match uri.to_file_path() {
1075 Ok(path) => path,
1076 Err(_) => {
1077 self.client
1078 .log_message(MessageType::ERROR, "Invalid file uri")
1079 .await;
1080 return Ok(None);
1081 }
1082 };
1083
1084 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
1085 Some(bytes) => bytes,
1086 None => return Ok(None),
1087 };
1088
1089 let source_text = String::from_utf8_lossy(&source_bytes).to_string();
1090
1091 let cursor_name = goto::cursor_context(&source_text, position).map(|ctx| ctx.name);
1093
1094 let (is_dirty, cached_build) = {
1098 let text_version = self
1099 .text_cache
1100 .read()
1101 .await
1102 .get(&uri.to_string())
1103 .map(|(v, _)| *v)
1104 .unwrap_or(0);
1105 let cb = self.get_or_fetch_build(&uri, &file_path, false).await;
1106 let build_version = cb.as_ref().map(|b| b.build_version).unwrap_or(0);
1107 (text_version > build_version, cb)
1108 };
1109
1110 let validate_ts = |loc: &Location| -> bool {
1116 let Some(ref name) = cursor_name else {
1117 return true; };
1119 let target_src = if loc.uri == uri {
1120 Some(source_text.clone())
1121 } else {
1122 loc.uri
1123 .to_file_path()
1124 .ok()
1125 .and_then(|p| std::fs::read_to_string(&p).ok())
1126 };
1127 match target_src {
1128 Some(src) => goto::validate_goto_target(&src, loc, name),
1129 None => true, }
1131 };
1132
1133 if is_dirty {
1134 self.client
1135 .log_message(MessageType::INFO, "file is dirty, trying tree-sitter first")
1136 .await;
1137
1138 let ts_result = {
1140 let comp_cache = self.completion_cache.read().await;
1141 let text_cache = self.text_cache.read().await;
1142 if let Some(cc) = comp_cache.get(&uri.to_string()) {
1143 goto::goto_definition_ts(&source_text, position, &uri, cc, &text_cache)
1144 } else {
1145 None
1146 }
1147 };
1148
1149 if let Some(location) = ts_result {
1150 if validate_ts(&location) {
1151 self.client
1152 .log_message(
1153 MessageType::INFO,
1154 format!(
1155 "found definition (tree-sitter) at {}:{}",
1156 location.uri, location.range.start.line
1157 ),
1158 )
1159 .await;
1160 return Ok(Some(GotoDefinitionResponse::from(location)));
1161 }
1162 self.client
1163 .log_message(
1164 MessageType::INFO,
1165 "tree-sitter result failed validation, trying AST fallback",
1166 )
1167 .await;
1168 }
1169
1170 if let Some(ref cb) = cached_build
1175 && let Some(ref name) = cursor_name
1176 {
1177 let byte_hint = goto::pos_to_bytes(&source_bytes, position);
1178 if let Some(location) = goto::goto_declaration_by_name(cb, &uri, name, byte_hint) {
1179 self.client
1180 .log_message(
1181 MessageType::INFO,
1182 format!(
1183 "found definition (AST by name) at {}:{}",
1184 location.uri, location.range.start.line
1185 ),
1186 )
1187 .await;
1188 return Ok(Some(GotoDefinitionResponse::from(location)));
1189 }
1190 }
1191 } else {
1192 if let Some(ref cb) = cached_build
1194 && let Some(location) =
1195 goto::goto_declaration_cached(cb, &uri, position, &source_bytes)
1196 {
1197 self.client
1198 .log_message(
1199 MessageType::INFO,
1200 format!(
1201 "found definition (AST) at {}:{}",
1202 location.uri, location.range.start.line
1203 ),
1204 )
1205 .await;
1206 return Ok(Some(GotoDefinitionResponse::from(location)));
1207 }
1208
1209 let ts_result = {
1211 let comp_cache = self.completion_cache.read().await;
1212 let text_cache = self.text_cache.read().await;
1213 if let Some(cc) = comp_cache.get(&uri.to_string()) {
1214 goto::goto_definition_ts(&source_text, position, &uri, cc, &text_cache)
1215 } else {
1216 None
1217 }
1218 };
1219
1220 if let Some(location) = ts_result {
1221 if validate_ts(&location) {
1222 self.client
1223 .log_message(
1224 MessageType::INFO,
1225 format!(
1226 "found definition (tree-sitter fallback) at {}:{}",
1227 location.uri, location.range.start.line
1228 ),
1229 )
1230 .await;
1231 return Ok(Some(GotoDefinitionResponse::from(location)));
1232 }
1233 self.client
1234 .log_message(MessageType::INFO, "tree-sitter fallback failed validation")
1235 .await;
1236 }
1237 }
1238
1239 self.client
1240 .log_message(MessageType::INFO, "no definition found")
1241 .await;
1242 Ok(None)
1243 }
1244
1245 async fn goto_declaration(
1246 &self,
1247 params: request::GotoDeclarationParams,
1248 ) -> tower_lsp::jsonrpc::Result<Option<request::GotoDeclarationResponse>> {
1249 self.client
1250 .log_message(MessageType::INFO, "got textDocument/declaration request")
1251 .await;
1252
1253 let uri = params.text_document_position_params.text_document.uri;
1254 let position = params.text_document_position_params.position;
1255
1256 let file_path = match uri.to_file_path() {
1257 Ok(path) => path,
1258 Err(_) => {
1259 self.client
1260 .log_message(MessageType::ERROR, "invalid file uri")
1261 .await;
1262 return Ok(None);
1263 }
1264 };
1265
1266 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
1267 Some(bytes) => bytes,
1268 None => return Ok(None),
1269 };
1270
1271 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
1272 let cached_build = match cached_build {
1273 Some(cb) => cb,
1274 None => return Ok(None),
1275 };
1276
1277 if let Some(location) =
1278 goto::goto_declaration_cached(&cached_build, &uri, position, &source_bytes)
1279 {
1280 self.client
1281 .log_message(
1282 MessageType::INFO,
1283 format!(
1284 "found declaration at {}:{}",
1285 location.uri, location.range.start.line
1286 ),
1287 )
1288 .await;
1289 Ok(Some(request::GotoDeclarationResponse::from(location)))
1290 } else {
1291 self.client
1292 .log_message(MessageType::INFO, "no declaration found")
1293 .await;
1294 Ok(None)
1295 }
1296 }
1297
1298 async fn references(
1299 &self,
1300 params: ReferenceParams,
1301 ) -> tower_lsp::jsonrpc::Result<Option<Vec<Location>>> {
1302 self.client
1303 .log_message(MessageType::INFO, "Got a textDocument/references request")
1304 .await;
1305
1306 let uri = params.text_document_position.text_document.uri;
1307 let position = params.text_document_position.position;
1308 let file_path = match uri.to_file_path() {
1309 Ok(path) => path,
1310 Err(_) => {
1311 self.client
1312 .log_message(MessageType::ERROR, "Invalid file URI")
1313 .await;
1314 return Ok(None);
1315 }
1316 };
1317 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
1318 Some(bytes) => bytes,
1319 None => return Ok(None),
1320 };
1321 let cached_build = self.get_or_fetch_build(&uri, &file_path, true).await;
1322 let cached_build = match cached_build {
1323 Some(cb) => cb,
1324 None => return Ok(None),
1325 };
1326
1327 let mut locations = references::goto_references_cached(
1329 &cached_build,
1330 &uri,
1331 position,
1332 &source_bytes,
1333 None,
1334 params.context.include_declaration,
1335 );
1336
1337 if let Some((def_abs_path, def_byte_offset)) =
1339 references::resolve_target_location(&cached_build, &uri, position, &source_bytes)
1340 {
1341 let cache = self.ast_cache.read().await;
1342 for (cached_uri, other_build) in cache.iter() {
1343 if *cached_uri == uri.to_string() {
1344 continue;
1345 }
1346 let other_locations = references::goto_references_for_target(
1347 other_build,
1348 &def_abs_path,
1349 def_byte_offset,
1350 None,
1351 params.context.include_declaration,
1352 );
1353 locations.extend(other_locations);
1354 }
1355 }
1356
1357 let mut seen = std::collections::HashSet::new();
1359 locations.retain(|loc| {
1360 seen.insert((
1361 loc.uri.clone(),
1362 loc.range.start.line,
1363 loc.range.start.character,
1364 loc.range.end.line,
1365 loc.range.end.character,
1366 ))
1367 });
1368
1369 if locations.is_empty() {
1370 self.client
1371 .log_message(MessageType::INFO, "No references found")
1372 .await;
1373 Ok(None)
1374 } else {
1375 self.client
1376 .log_message(
1377 MessageType::INFO,
1378 format!("Found {} references", locations.len()),
1379 )
1380 .await;
1381 Ok(Some(locations))
1382 }
1383 }
1384
1385 async fn prepare_rename(
1386 &self,
1387 params: TextDocumentPositionParams,
1388 ) -> tower_lsp::jsonrpc::Result<Option<PrepareRenameResponse>> {
1389 self.client
1390 .log_message(MessageType::INFO, "got textDocument/prepareRename request")
1391 .await;
1392
1393 let uri = params.text_document.uri;
1394 let position = params.position;
1395
1396 let file_path = match uri.to_file_path() {
1397 Ok(path) => path,
1398 Err(_) => {
1399 self.client
1400 .log_message(MessageType::ERROR, "invalid file uri")
1401 .await;
1402 return Ok(None);
1403 }
1404 };
1405
1406 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
1407 Some(bytes) => bytes,
1408 None => return Ok(None),
1409 };
1410
1411 if let Some(range) = rename::get_identifier_range(&source_bytes, position) {
1412 self.client
1413 .log_message(
1414 MessageType::INFO,
1415 format!(
1416 "prepare rename range: {}:{}",
1417 range.start.line, range.start.character
1418 ),
1419 )
1420 .await;
1421 Ok(Some(PrepareRenameResponse::Range(range)))
1422 } else {
1423 self.client
1424 .log_message(MessageType::INFO, "no identifier found for prepare rename")
1425 .await;
1426 Ok(None)
1427 }
1428 }
1429
1430 async fn rename(
1431 &self,
1432 params: RenameParams,
1433 ) -> tower_lsp::jsonrpc::Result<Option<WorkspaceEdit>> {
1434 self.client
1435 .log_message(MessageType::INFO, "got textDocument/rename request")
1436 .await;
1437
1438 let uri = params.text_document_position.text_document.uri;
1439 let position = params.text_document_position.position;
1440 let new_name = params.new_name;
1441 let file_path = match uri.to_file_path() {
1442 Ok(p) => p,
1443 Err(_) => {
1444 self.client
1445 .log_message(MessageType::ERROR, "invalid file uri")
1446 .await;
1447 return Ok(None);
1448 }
1449 };
1450 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
1451 Some(bytes) => bytes,
1452 None => return Ok(None),
1453 };
1454
1455 let current_identifier = match rename::get_identifier_at_position(&source_bytes, position) {
1456 Some(id) => id,
1457 None => {
1458 self.client
1459 .log_message(MessageType::ERROR, "No identifier found at position")
1460 .await;
1461 return Ok(None);
1462 }
1463 };
1464
1465 if !utils::is_valid_solidity_identifier(&new_name) {
1466 return Err(tower_lsp::jsonrpc::Error::invalid_params(
1467 "new name is not a valid solidity identifier",
1468 ));
1469 }
1470
1471 if new_name == current_identifier {
1472 self.client
1473 .log_message(
1474 MessageType::INFO,
1475 "new name is the same as current identifier",
1476 )
1477 .await;
1478 return Ok(None);
1479 }
1480
1481 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
1482 let cached_build = match cached_build {
1483 Some(cb) => cb,
1484 None => return Ok(None),
1485 };
1486 let other_builds: Vec<Arc<goto::CachedBuild>> = {
1487 let cache = self.ast_cache.read().await;
1488 cache
1489 .iter()
1490 .filter(|(key, _)| **key != uri.to_string())
1491 .map(|(_, v)| v.clone())
1492 .collect()
1493 };
1494 let other_refs: Vec<&goto::CachedBuild> = other_builds.iter().map(|v| v.as_ref()).collect();
1495
1496 let text_buffers: HashMap<String, Vec<u8>> = {
1500 let text_cache = self.text_cache.read().await;
1501 text_cache
1502 .iter()
1503 .map(|(uri, (_, content))| (uri.clone(), content.as_bytes().to_vec()))
1504 .collect()
1505 };
1506
1507 match rename::rename_symbol(
1508 &cached_build,
1509 &uri,
1510 position,
1511 &source_bytes,
1512 new_name,
1513 &other_refs,
1514 &text_buffers,
1515 ) {
1516 Some(workspace_edit) => {
1517 self.client
1518 .log_message(
1519 MessageType::INFO,
1520 format!(
1521 "created rename edit with {} file(s), {} total change(s)",
1522 workspace_edit
1523 .changes
1524 .as_ref()
1525 .map(|c| c.len())
1526 .unwrap_or(0),
1527 workspace_edit
1528 .changes
1529 .as_ref()
1530 .map(|c| c.values().map(|v| v.len()).sum::<usize>())
1531 .unwrap_or(0)
1532 ),
1533 )
1534 .await;
1535
1536 Ok(Some(workspace_edit))
1541 }
1542
1543 None => {
1544 self.client
1545 .log_message(MessageType::INFO, "No locations found for renaming")
1546 .await;
1547 Ok(None)
1548 }
1549 }
1550 }
1551
1552 async fn symbol(
1553 &self,
1554 params: WorkspaceSymbolParams,
1555 ) -> tower_lsp::jsonrpc::Result<Option<Vec<SymbolInformation>>> {
1556 self.client
1557 .log_message(MessageType::INFO, "got workspace/symbol request")
1558 .await;
1559
1560 let files: Vec<(Url, String)> = {
1562 let cache = self.text_cache.read().await;
1563 cache
1564 .iter()
1565 .filter(|(uri_str, _)| uri_str.ends_with(".sol"))
1566 .filter_map(|(uri_str, (_, content))| {
1567 Url::parse(uri_str).ok().map(|uri| (uri, content.clone()))
1568 })
1569 .collect()
1570 };
1571
1572 let mut all_symbols = symbols::extract_workspace_symbols(&files);
1573 if !params.query.is_empty() {
1574 let query = params.query.to_lowercase();
1575 all_symbols.retain(|symbol| symbol.name.to_lowercase().contains(&query));
1576 }
1577 if all_symbols.is_empty() {
1578 self.client
1579 .log_message(MessageType::INFO, "No symbols found")
1580 .await;
1581 Ok(None)
1582 } else {
1583 self.client
1584 .log_message(
1585 MessageType::INFO,
1586 format!("found {} symbols", all_symbols.len()),
1587 )
1588 .await;
1589 Ok(Some(all_symbols))
1590 }
1591 }
1592
1593 async fn document_symbol(
1594 &self,
1595 params: DocumentSymbolParams,
1596 ) -> tower_lsp::jsonrpc::Result<Option<DocumentSymbolResponse>> {
1597 self.client
1598 .log_message(MessageType::INFO, "got textDocument/documentSymbol request")
1599 .await;
1600 let uri = params.text_document.uri;
1601 let file_path = match uri.to_file_path() {
1602 Ok(path) => path,
1603 Err(_) => {
1604 self.client
1605 .log_message(MessageType::ERROR, "invalid file uri")
1606 .await;
1607 return Ok(None);
1608 }
1609 };
1610
1611 let source = {
1613 let cache = self.text_cache.read().await;
1614 cache
1615 .get(&uri.to_string())
1616 .map(|(_, content)| content.clone())
1617 };
1618 let source = match source {
1619 Some(s) => s,
1620 None => match std::fs::read_to_string(&file_path) {
1621 Ok(s) => s,
1622 Err(_) => return Ok(None),
1623 },
1624 };
1625
1626 let symbols = symbols::extract_document_symbols(&source);
1627 if symbols.is_empty() {
1628 self.client
1629 .log_message(MessageType::INFO, "no document symbols found")
1630 .await;
1631 Ok(None)
1632 } else {
1633 self.client
1634 .log_message(
1635 MessageType::INFO,
1636 format!("found {} document symbols", symbols.len()),
1637 )
1638 .await;
1639 Ok(Some(DocumentSymbolResponse::Nested(symbols)))
1640 }
1641 }
1642
1643 async fn hover(&self, params: HoverParams) -> tower_lsp::jsonrpc::Result<Option<Hover>> {
1644 self.client
1645 .log_message(MessageType::INFO, "got textDocument/hover request")
1646 .await;
1647
1648 let uri = params.text_document_position_params.text_document.uri;
1649 let position = params.text_document_position_params.position;
1650
1651 let file_path = match uri.to_file_path() {
1652 Ok(path) => path,
1653 Err(_) => {
1654 self.client
1655 .log_message(MessageType::ERROR, "invalid file uri")
1656 .await;
1657 return Ok(None);
1658 }
1659 };
1660
1661 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
1662 Some(bytes) => bytes,
1663 None => return Ok(None),
1664 };
1665
1666 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
1667 let cached_build = match cached_build {
1668 Some(cb) => cb,
1669 None => return Ok(None),
1670 };
1671
1672 let result = hover::hover_info(&cached_build, &uri, position, &source_bytes);
1673
1674 if result.is_some() {
1675 self.client
1676 .log_message(MessageType::INFO, "hover info found")
1677 .await;
1678 } else {
1679 self.client
1680 .log_message(MessageType::INFO, "no hover info found")
1681 .await;
1682 }
1683
1684 Ok(result)
1685 }
1686
1687 async fn signature_help(
1688 &self,
1689 params: SignatureHelpParams,
1690 ) -> tower_lsp::jsonrpc::Result<Option<SignatureHelp>> {
1691 self.client
1692 .log_message(MessageType::INFO, "got textDocument/signatureHelp request")
1693 .await;
1694
1695 let uri = params.text_document_position_params.text_document.uri;
1696 let position = params.text_document_position_params.position;
1697
1698 let file_path = match uri.to_file_path() {
1699 Ok(path) => path,
1700 Err(_) => {
1701 self.client
1702 .log_message(MessageType::ERROR, "invalid file uri")
1703 .await;
1704 return Ok(None);
1705 }
1706 };
1707
1708 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
1709 Some(bytes) => bytes,
1710 None => return Ok(None),
1711 };
1712
1713 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
1714 let cached_build = match cached_build {
1715 Some(cb) => cb,
1716 None => return Ok(None),
1717 };
1718
1719 let result = hover::signature_help(&cached_build, &source_bytes, position);
1720
1721 Ok(result)
1722 }
1723
1724 async fn document_link(
1725 &self,
1726 params: DocumentLinkParams,
1727 ) -> tower_lsp::jsonrpc::Result<Option<Vec<DocumentLink>>> {
1728 self.client
1729 .log_message(MessageType::INFO, "got textDocument/documentLink request")
1730 .await;
1731
1732 let uri = params.text_document.uri;
1733 let file_path = match uri.to_file_path() {
1734 Ok(path) => path,
1735 Err(_) => {
1736 self.client
1737 .log_message(MessageType::ERROR, "invalid file uri")
1738 .await;
1739 return Ok(None);
1740 }
1741 };
1742
1743 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
1744 Some(bytes) => bytes,
1745 None => return Ok(None),
1746 };
1747
1748 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
1749 let cached_build = match cached_build {
1750 Some(cb) => cb,
1751 None => return Ok(None),
1752 };
1753
1754 let result = links::document_links(&cached_build, &uri, &source_bytes);
1755
1756 if result.is_empty() {
1757 self.client
1758 .log_message(MessageType::INFO, "no document links found")
1759 .await;
1760 Ok(None)
1761 } else {
1762 self.client
1763 .log_message(
1764 MessageType::INFO,
1765 format!("found {} document links", result.len()),
1766 )
1767 .await;
1768 Ok(Some(result))
1769 }
1770 }
1771
1772 async fn semantic_tokens_full(
1773 &self,
1774 params: SemanticTokensParams,
1775 ) -> tower_lsp::jsonrpc::Result<Option<SemanticTokensResult>> {
1776 self.client
1777 .log_message(
1778 MessageType::INFO,
1779 "got textDocument/semanticTokens/full request",
1780 )
1781 .await;
1782
1783 let uri = params.text_document.uri;
1784 let source = {
1785 let cache = self.text_cache.read().await;
1786 cache.get(&uri.to_string()).map(|(_, s)| s.clone())
1787 };
1788
1789 let source = match source {
1790 Some(s) => s,
1791 None => {
1792 let file_path = match uri.to_file_path() {
1794 Ok(p) => p,
1795 Err(_) => return Ok(None),
1796 };
1797 match std::fs::read_to_string(&file_path) {
1798 Ok(s) => s,
1799 Err(_) => return Ok(None),
1800 }
1801 }
1802 };
1803
1804 let mut tokens = semantic_tokens::semantic_tokens_full(&source);
1805
1806 let id = self.semantic_token_id.fetch_add(1, Ordering::Relaxed);
1808 let result_id = id.to_string();
1809 tokens.result_id = Some(result_id.clone());
1810
1811 {
1812 let mut cache = self.semantic_token_cache.write().await;
1813 cache.insert(uri.to_string(), (result_id, tokens.data.clone()));
1814 }
1815
1816 Ok(Some(SemanticTokensResult::Tokens(tokens)))
1817 }
1818
1819 async fn semantic_tokens_range(
1820 &self,
1821 params: SemanticTokensRangeParams,
1822 ) -> tower_lsp::jsonrpc::Result<Option<SemanticTokensRangeResult>> {
1823 self.client
1824 .log_message(
1825 MessageType::INFO,
1826 "got textDocument/semanticTokens/range request",
1827 )
1828 .await;
1829
1830 let uri = params.text_document.uri;
1831 let range = params.range;
1832 let source = {
1833 let cache = self.text_cache.read().await;
1834 cache.get(&uri.to_string()).map(|(_, s)| s.clone())
1835 };
1836
1837 let source = match source {
1838 Some(s) => s,
1839 None => {
1840 let file_path = match uri.to_file_path() {
1841 Ok(p) => p,
1842 Err(_) => return Ok(None),
1843 };
1844 match std::fs::read_to_string(&file_path) {
1845 Ok(s) => s,
1846 Err(_) => return Ok(None),
1847 }
1848 }
1849 };
1850
1851 let tokens =
1852 semantic_tokens::semantic_tokens_range(&source, range.start.line, range.end.line);
1853
1854 Ok(Some(SemanticTokensRangeResult::Tokens(tokens)))
1855 }
1856
1857 async fn semantic_tokens_full_delta(
1858 &self,
1859 params: SemanticTokensDeltaParams,
1860 ) -> tower_lsp::jsonrpc::Result<Option<SemanticTokensFullDeltaResult>> {
1861 self.client
1862 .log_message(
1863 MessageType::INFO,
1864 "got textDocument/semanticTokens/full/delta request",
1865 )
1866 .await;
1867
1868 let uri = params.text_document.uri;
1869 let previous_result_id = params.previous_result_id;
1870
1871 let source = {
1872 let cache = self.text_cache.read().await;
1873 cache.get(&uri.to_string()).map(|(_, s)| s.clone())
1874 };
1875
1876 let source = match source {
1877 Some(s) => s,
1878 None => {
1879 let file_path = match uri.to_file_path() {
1880 Ok(p) => p,
1881 Err(_) => return Ok(None),
1882 };
1883 match std::fs::read_to_string(&file_path) {
1884 Ok(s) => s,
1885 Err(_) => return Ok(None),
1886 }
1887 }
1888 };
1889
1890 let mut new_tokens = semantic_tokens::semantic_tokens_full(&source);
1891
1892 let id = self.semantic_token_id.fetch_add(1, Ordering::Relaxed);
1894 let new_result_id = id.to_string();
1895 new_tokens.result_id = Some(new_result_id.clone());
1896
1897 let uri_str = uri.to_string();
1898
1899 let old_tokens = {
1901 let cache = self.semantic_token_cache.read().await;
1902 cache
1903 .get(&uri_str)
1904 .filter(|(rid, _)| *rid == previous_result_id)
1905 .map(|(_, tokens)| tokens.clone())
1906 };
1907
1908 {
1910 let mut cache = self.semantic_token_cache.write().await;
1911 cache.insert(uri_str, (new_result_id.clone(), new_tokens.data.clone()));
1912 }
1913
1914 match old_tokens {
1915 Some(old) => {
1916 let edits = semantic_tokens::compute_delta(&old, &new_tokens.data);
1918 Ok(Some(SemanticTokensFullDeltaResult::TokensDelta(
1919 SemanticTokensDelta {
1920 result_id: Some(new_result_id),
1921 edits,
1922 },
1923 )))
1924 }
1925 None => {
1926 Ok(Some(SemanticTokensFullDeltaResult::Tokens(new_tokens)))
1928 }
1929 }
1930 }
1931
1932 async fn inlay_hint(
1933 &self,
1934 params: InlayHintParams,
1935 ) -> tower_lsp::jsonrpc::Result<Option<Vec<InlayHint>>> {
1936 self.client
1937 .log_message(MessageType::INFO, "got textDocument/inlayHint request")
1938 .await;
1939
1940 let uri = params.text_document.uri;
1941 let range = params.range;
1942
1943 let file_path = match uri.to_file_path() {
1944 Ok(path) => path,
1945 Err(_) => {
1946 self.client
1947 .log_message(MessageType::ERROR, "invalid file uri")
1948 .await;
1949 return Ok(None);
1950 }
1951 };
1952
1953 let source_bytes = match self.get_source_bytes(&uri, &file_path).await {
1954 Some(bytes) => bytes,
1955 None => return Ok(None),
1956 };
1957
1958 let cached_build = self.get_or_fetch_build(&uri, &file_path, false).await;
1959 let cached_build = match cached_build {
1960 Some(cb) => cb,
1961 None => return Ok(None),
1962 };
1963
1964 let mut hints = inlay_hints::inlay_hints(&cached_build, &uri, range, &source_bytes);
1965
1966 let settings = self.settings.read().await;
1968 if !settings.inlay_hints.parameters {
1969 hints.retain(|h| h.kind != Some(InlayHintKind::PARAMETER));
1970 }
1971 if !settings.inlay_hints.gas_estimates {
1972 hints.retain(|h| h.kind != Some(InlayHintKind::TYPE));
1973 }
1974
1975 if hints.is_empty() {
1976 self.client
1977 .log_message(MessageType::INFO, "no inlay hints found")
1978 .await;
1979 Ok(None)
1980 } else {
1981 self.client
1982 .log_message(
1983 MessageType::INFO,
1984 format!("found {} inlay hints", hints.len()),
1985 )
1986 .await;
1987 Ok(Some(hints))
1988 }
1989 }
1990}