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