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