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