Skip to main content

daytona_client/
lsp.rs

1//! Language Server Protocol operations
2
3use serde::{Deserialize, Serialize};
4
5use crate::{
6    client::DaytonaClient,
7    error::Result,
8    models::lsp::{
9        CodeAction, CompletionItem, Hover, Location, LspInitializeParams, SymbolInformation,
10        TextDocumentPosition,
11    },
12};
13
14/// LSP language identifiers
15#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
16#[serde(rename_all = "lowercase")]
17pub enum LspLanguage {
18    Python,
19    TypeScript,
20    JavaScript,
21    Rust,
22    Go,
23    Java,
24    CSharp,
25    Cpp,
26}
27
28/// LSP server manager for a specific sandbox
29pub struct LspManager<'a> {
30    client: &'a DaytonaClient,
31    sandbox_id: String,
32}
33
34impl<'a> LspManager<'a> {
35    pub(crate) fn new(client: &'a DaytonaClient, sandbox_id: impl Into<String>) -> Self {
36        Self {
37            client,
38            sandbox_id: sandbox_id.into(),
39        }
40    }
41
42    /// Initialize the LSP server for a specific language and project
43    pub async fn initialize(&self, params: LspInitializeParams) -> Result<()> {
44        let response = self
45            .client
46            .build_request(
47                reqwest::Method::POST,
48                &format!("/sandbox/{}/lsp/initialize", self.sandbox_id),
49            )
50            .json(&params)
51            .send()
52            .await?;
53
54        self.client.handle_response_empty(response).await
55    }
56
57    /// Start the LSP server
58    pub async fn start(&self, language: LspLanguage, project_path: &str) -> Result<()> {
59        #[derive(Serialize)]
60        struct StartRequest {
61            language_id: String,
62            path_to_project: String,
63        }
64
65        let request = StartRequest {
66            language_id: serde_json::to_value(language)?
67                .as_str()
68                .unwrap_or("typescript")
69                .to_string(),
70            path_to_project: project_path.to_string(),
71        };
72
73        let response = self
74            .client
75            .build_request(
76                reqwest::Method::POST,
77                &format!("/sandbox/{}/lsp/start", self.sandbox_id),
78            )
79            .json(&request)
80            .send()
81            .await?;
82
83        self.client.handle_response_empty(response).await
84    }
85
86    /// Stop the LSP server
87    pub async fn stop(&self) -> Result<()> {
88        let response = self
89            .client
90            .build_request(
91                reqwest::Method::POST,
92                &format!("/sandbox/{}/lsp/stop", self.sandbox_id),
93            )
94            .send()
95            .await?;
96
97        self.client.handle_response_empty(response).await
98    }
99
100    /// Get code completions at a specific position
101    pub async fn get_completions(
102        &self,
103        position: TextDocumentPosition,
104    ) -> Result<Vec<CompletionItem>> {
105        let response = self
106            .client
107            .build_request(
108                reqwest::Method::POST,
109                &format!("/sandbox/{}/lsp/completions", self.sandbox_id),
110            )
111            .json(&position)
112            .send()
113            .await?;
114
115        #[derive(Deserialize)]
116        struct CompletionResponse {
117            items: Vec<CompletionItem>,
118        }
119
120        let resp: CompletionResponse = self.client.handle_response(response).await?;
121        Ok(resp.items)
122    }
123
124    /// Get hover information at a specific position
125    pub async fn get_hover(&self, position: TextDocumentPosition) -> Result<Option<Hover>> {
126        let response = self
127            .client
128            .build_request(
129                reqwest::Method::POST,
130                &format!("/sandbox/{}/lsp/hover", self.sandbox_id),
131            )
132            .json(&position)
133            .send()
134            .await?;
135
136        self.client.handle_response(response).await
137    }
138
139    /// Go to definition
140    pub async fn go_to_definition(&self, position: TextDocumentPosition) -> Result<Vec<Location>> {
141        let response = self
142            .client
143            .build_request(
144                reqwest::Method::POST,
145                &format!("/sandbox/{}/lsp/definition", self.sandbox_id),
146            )
147            .json(&position)
148            .send()
149            .await?;
150
151        self.client.handle_response(response).await
152    }
153
154    /// Find references
155    pub async fn find_references(
156        &self,
157        position: TextDocumentPosition,
158        include_declaration: bool,
159    ) -> Result<Vec<Location>> {
160        #[derive(Serialize)]
161        struct ReferencesRequest {
162            #[serde(flatten)]
163            position: TextDocumentPosition,
164            include_declaration: bool,
165        }
166
167        let request = ReferencesRequest {
168            position,
169            include_declaration,
170        };
171
172        let response = self
173            .client
174            .build_request(
175                reqwest::Method::POST,
176                &format!("/sandbox/{}/lsp/references", self.sandbox_id),
177            )
178            .json(&request)
179            .send()
180            .await?;
181
182        self.client.handle_response(response).await
183    }
184
185    /// Get document symbols
186    pub async fn get_document_symbols(&self, uri: &str) -> Result<Vec<SymbolInformation>> {
187        #[derive(Serialize)]
188        struct SymbolsRequest {
189            uri: String,
190        }
191
192        let request = SymbolsRequest {
193            uri: uri.to_string(),
194        };
195
196        let response = self
197            .client
198            .build_request(
199                reqwest::Method::POST,
200                &format!("/sandbox/{}/lsp/symbols", self.sandbox_id),
201            )
202            .json(&request)
203            .send()
204            .await?;
205
206        self.client.handle_response(response).await
207    }
208
209    /// Get workspace symbols
210    pub async fn get_workspace_symbols(&self, query: &str) -> Result<Vec<SymbolInformation>> {
211        #[derive(Serialize)]
212        struct WorkspaceSymbolsRequest {
213            query: String,
214        }
215
216        let request = WorkspaceSymbolsRequest {
217            query: query.to_string(),
218        };
219
220        let response = self
221            .client
222            .build_request(
223                reqwest::Method::POST,
224                &format!("/sandbox/{}/lsp/workspace-symbols", self.sandbox_id),
225            )
226            .json(&request)
227            .send()
228            .await?;
229
230        self.client.handle_response(response).await
231    }
232
233    /// Get diagnostics for a document
234    pub async fn get_diagnostics(&self, uri: &str) -> Result<Vec<crate::models::lsp::Diagnostic>> {
235        #[derive(Serialize)]
236        struct DiagnosticsRequest {
237            uri: String,
238        }
239
240        let request = DiagnosticsRequest {
241            uri: uri.to_string(),
242        };
243
244        let response = self
245            .client
246            .build_request(
247                reqwest::Method::POST,
248                &format!("/sandbox/{}/lsp/diagnostics", self.sandbox_id),
249            )
250            .json(&request)
251            .send()
252            .await?;
253
254        self.client.handle_response(response).await
255    }
256
257    /// Get code actions for a range
258    pub async fn get_code_actions(
259        &self,
260        uri: &str,
261        range: crate::models::lsp::Range,
262        diagnostics: Vec<crate::models::lsp::Diagnostic>,
263    ) -> Result<Vec<CodeAction>> {
264        #[derive(Serialize)]
265        struct CodeActionsRequest {
266            uri: String,
267            range: crate::models::lsp::Range,
268            diagnostics: Vec<crate::models::lsp::Diagnostic>,
269        }
270
271        let request = CodeActionsRequest {
272            uri: uri.to_string(),
273            range,
274            diagnostics,
275        };
276
277        let response = self
278            .client
279            .build_request(
280                reqwest::Method::POST,
281                &format!("/sandbox/{}/lsp/code-actions", self.sandbox_id),
282            )
283            .json(&request)
284            .send()
285            .await?;
286
287        self.client.handle_response(response).await
288    }
289
290    /// Format document
291    pub async fn format_document(&self, uri: &str) -> Result<Vec<crate::models::lsp::TextEdit>> {
292        #[derive(Serialize)]
293        struct FormatRequest {
294            uri: String,
295        }
296
297        let request = FormatRequest {
298            uri: uri.to_string(),
299        };
300
301        let response = self
302            .client
303            .build_request(
304                reqwest::Method::POST,
305                &format!("/sandbox/{}/lsp/format", self.sandbox_id),
306            )
307            .json(&request)
308            .send()
309            .await?;
310
311        self.client.handle_response(response).await
312    }
313
314    /// Rename symbol
315    pub async fn rename(
316        &self,
317        position: TextDocumentPosition,
318        new_name: &str,
319    ) -> Result<crate::models::lsp::WorkspaceEdit> {
320        #[derive(Serialize)]
321        struct RenameRequest {
322            #[serde(flatten)]
323            position: TextDocumentPosition,
324            new_name: String,
325        }
326
327        let request = RenameRequest {
328            position,
329            new_name: new_name.to_string(),
330        };
331
332        let response = self
333            .client
334            .build_request(
335                reqwest::Method::POST,
336                &format!("/sandbox/{}/lsp/rename", self.sandbox_id),
337            )
338            .json(&request)
339            .send()
340            .await?;
341
342        self.client.handle_response(response).await
343    }
344}
345
346/// Extension trait to add LSP functionality to sandboxes
347pub trait SandboxLspExt {
348    /// Create an LSP manager for this sandbox
349    fn lsp<'a>(&self, client: &'a DaytonaClient) -> LspManager<'a>;
350}
351
352impl SandboxLspExt for crate::models::Sandbox {
353    fn lsp<'a>(&self, client: &'a DaytonaClient) -> LspManager<'a> {
354        LspManager::new(client, self.id.to_string())
355    }
356}