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