1use 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#[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
28pub 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 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(¶ms)
51 .send()
52 .await?;
53
54 self.client.handle_response_empty(response).await
55 }
56
57 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 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 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 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 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 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 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 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 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 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 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 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
346pub trait SandboxLspExt {
348 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}