solidity_language_server/
lsp.rs

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        // cached
67        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        // cache text
80        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        // invalidate cached ast
216        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        // update text cache
228        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        // Get original content
307        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                // Fallback to reading file
313                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        // Get formatted content
326        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 changed, return edit to replace whole document
337        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; // +1 for \n
990    });
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}