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