Skip to main content

fresh/app/
lsp_actions.rs

1//! LSP-related action handlers.
2//!
3//! This module contains handlers for LSP actions that require complex logic,
4//! such as restarting LSP servers and managing server lifecycle.
5
6use super::Editor;
7use crate::input::commands::Suggestion;
8use crate::view::prompt::{Prompt, PromptType};
9use rust_i18n::t;
10
11impl Editor {
12    /// Handle the LspRestart action.
13    ///
14    /// Restarts the LSP server for the current buffer's language and re-sends
15    /// didOpen notifications for all buffers of that language.
16    pub fn handle_lsp_restart(&mut self) {
17        // Get the language for the current buffer
18        let Some(metadata) = self.buffer_metadata.get(&self.active_buffer()) else {
19            return;
20        };
21
22        let Some(path) = metadata.file_path() else {
23            self.set_status_message(t!("lsp.buffer_has_no_file").to_string());
24            return;
25        };
26
27        let Some(language) =
28            crate::services::lsp::manager::detect_language(path, &self.config.languages)
29        else {
30            self.set_status_message(t!("lsp.no_server_configured").to_string());
31            return;
32        };
33
34        // Attempt restart
35        let Some(lsp) = self.lsp.as_mut() else {
36            self.set_status_message(t!("lsp.no_manager").to_string());
37            return;
38        };
39
40        let (success, message) = lsp.manual_restart(&language);
41        self.status_message = Some(message);
42
43        if !success {
44            return;
45        }
46
47        // Re-send didOpen for all buffers of this language
48        self.reopen_buffers_for_language(&language);
49    }
50
51    /// Re-send didOpen notifications for all buffers of a given language.
52    ///
53    /// Called after LSP server restart to re-register open files.
54    fn reopen_buffers_for_language(&mut self, language: &str) {
55        // Collect buffer info first to avoid borrow conflicts
56        let buffers_for_language: Vec<_> = self
57            .buffer_metadata
58            .iter()
59            .filter_map(|(buf_id, meta)| {
60                let path = meta.file_path()?;
61                let detected =
62                    crate::services::lsp::manager::detect_language(path, &self.config.languages)?;
63                if detected == language {
64                    Some((*buf_id, path.clone()))
65                } else {
66                    None
67                }
68            })
69            .collect();
70
71        for (buffer_id, buf_path) in buffers_for_language {
72            let Some(state) = self.buffers.get(&buffer_id) else {
73                continue;
74            };
75
76            let Some(content) = state.buffer.to_string() else {
77                continue; // Skip buffers that aren't fully loaded
78            };
79
80            let Some(uri) = url::Url::from_file_path(&buf_path)
81                .ok()
82                .and_then(|u| u.as_str().parse::<lsp_types::Uri>().ok())
83            else {
84                continue;
85            };
86
87            let Some(lang_id) =
88                crate::services::lsp::manager::detect_language(&buf_path, &self.config.languages)
89            else {
90                continue;
91            };
92
93            if let Some(lsp) = self.lsp.as_mut() {
94                // Respect auto_start setting for this user action
95                use crate::services::lsp::manager::LspSpawnResult;
96                if lsp.try_spawn(&lang_id) == LspSpawnResult::Spawned {
97                    if let Some(handle) = lsp.get_handle_mut(&lang_id) {
98                        let _ = handle.did_open(uri, content, lang_id);
99                    }
100                }
101            }
102        }
103    }
104
105    /// Handle the LspStop action.
106    ///
107    /// Shows a prompt to select which LSP server to stop, with suggestions
108    /// for all currently running servers.
109    pub fn handle_lsp_stop(&mut self) {
110        let running_servers: Vec<String> = self
111            .lsp
112            .as_ref()
113            .map(|lsp| lsp.running_servers())
114            .unwrap_or_default();
115
116        if running_servers.is_empty() {
117            self.set_status_message(t!("lsp.no_servers_running").to_string());
118            return;
119        }
120
121        // Create suggestions from running servers
122        let suggestions: Vec<Suggestion> = running_servers
123            .iter()
124            .map(|lang| {
125                let description = self
126                    .lsp
127                    .as_ref()
128                    .and_then(|lsp| lsp.get_config(lang))
129                    .filter(|c| !c.command.is_empty())
130                    .map(|c| format!("Command: {}", c.command));
131
132                Suggestion {
133                    text: lang.clone(),
134                    description,
135                    value: Some(lang.clone()),
136                    disabled: false,
137                    keybinding: None,
138                    source: None,
139                }
140            })
141            .collect();
142
143        // Start prompt with suggestions
144        self.prompt = Some(Prompt::with_suggestions(
145            "Stop LSP server: ".to_string(),
146            PromptType::StopLspServer,
147            suggestions,
148        ));
149
150        // Configure initial selection
151        if let Some(prompt) = self.prompt.as_mut() {
152            if running_servers.len() == 1 {
153                // If only one server, pre-fill the input with it
154                prompt.input = running_servers[0].clone();
155                prompt.cursor_pos = prompt.input.len();
156                prompt.selected_suggestion = Some(0);
157            } else if !prompt.suggestions.is_empty() {
158                // Auto-select first suggestion
159                prompt.selected_suggestion = Some(0);
160            }
161        }
162    }
163}