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