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