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