1#![allow(dead_code)]
3
4pub mod cache;
5pub mod types;
6
7use std::{
8 borrow::Cow,
9 fmt::Debug,
10 sync::{Arc, RwLock},
11};
12
13use dashmap::DashMap;
14use serde::{Deserialize, Serialize};
15use tower_lsp::{
16 LanguageServer,
17 jsonrpc::{Error, Result},
18 lsp_types::{
19 CreateFilesParams, DeleteFilesParams, Diagnostic, DidChangeConfigurationParams, DidChangeTextDocumentParams,
20 DidChangeWatchedFilesParams, DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams,
21 DidOpenTextDocumentParams, DidSaveTextDocumentParams, InitializeParams, InitializeResult, InitializedParams,
22 MessageType, OneOf, RenameFilesParams, ServerCapabilities, TextDocumentItem, TextDocumentSyncCapability,
23 TextDocumentSyncKind, TextDocumentSyncOptions, WorkspaceFolder, WorkspaceFoldersServerCapabilities,
24 WorkspaceServerCapabilities,
25 },
26};
27
28use crate::lsp::{
29 backend::Backend as _,
30 copilot::{
31 cache::CopilotCache,
32 types::{
33 CopilotAcceptCompletionParams, CopilotCompletionResponse, CopilotCompletionTelemetry, CopilotEditorInfo,
34 CopilotLspCompletionParams, CopilotRejectCompletionParams, DocParams,
35 },
36 },
37};
38
39#[derive(Deserialize, Serialize, Debug)]
40pub struct Success {
41 success: bool,
42}
43impl Success {
44 pub fn new(success: bool) -> Self {
45 Self { success }
46 }
47}
48
49#[derive(Clone)]
50pub struct Backend {
51 pub client: tower_lsp::Client,
53 pub fs: Arc<crate::fs::FileManager>,
55 pub workspace_folders: DashMap<String, WorkspaceFolder>,
57 pub code_map: DashMap<String, Vec<u8>>,
59 pub zoo_client: kittycad::Client,
61 pub editor_info: Arc<RwLock<CopilotEditorInfo>>,
63 pub cache: Arc<cache::CopilotCache>,
65 pub telemetry: DashMap<uuid::Uuid, CopilotCompletionTelemetry>,
67 pub diagnostics_map: DashMap<String, Vec<Diagnostic>>,
69
70 pub is_initialized: Arc<tokio::sync::RwLock<bool>>,
71
72 pub dev_mode: bool,
74}
75
76#[async_trait::async_trait]
78impl crate::lsp::backend::Backend for Backend {
79 fn client(&self) -> &tower_lsp::Client {
80 &self.client
81 }
82
83 fn fs(&self) -> &Arc<crate::fs::FileManager> {
84 &self.fs
85 }
86
87 async fn is_initialized(&self) -> bool {
88 *self.is_initialized.read().await
89 }
90
91 async fn set_is_initialized(&self, is_initialized: bool) {
92 *self.is_initialized.write().await = is_initialized;
93 }
94
95 async fn workspace_folders(&self) -> Vec<WorkspaceFolder> {
96 self.workspace_folders.iter().map(|i| i.clone()).collect()
98 }
99
100 async fn add_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
101 for folder in folders {
102 self.workspace_folders.insert(folder.name.to_string(), folder);
103 }
104 }
105
106 async fn remove_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
107 for folder in folders {
108 self.workspace_folders.remove(&folder.name);
109 }
110 }
111
112 fn code_map(&self) -> &DashMap<String, Vec<u8>> {
113 &self.code_map
114 }
115
116 async fn insert_code_map(&self, uri: String, text: Vec<u8>) {
117 self.code_map.insert(uri, text);
118 }
119
120 async fn remove_from_code_map(&self, uri: String) -> Option<Vec<u8>> {
121 self.code_map.remove(&uri).map(|(_, v)| v)
122 }
123
124 async fn clear_code_state(&self) {
125 self.code_map.clear();
126 }
127
128 fn current_diagnostics_map(&self) -> &DashMap<String, Vec<Diagnostic>> {
129 &self.diagnostics_map
130 }
131
132 async fn inner_on_change(&self, _params: TextDocumentItem, _force: bool) {
133 }
135}
136
137impl Backend {
138 #[cfg(target_arch = "wasm32")]
139 pub fn new_wasm(
140 client: tower_lsp::Client,
141 fs: crate::fs::wasm::FileSystemManager,
142 zoo_client: kittycad::Client,
143 dev_mode: bool,
144 ) -> Self {
145 Self::new(client, crate::fs::FileManager::new(fs), zoo_client, dev_mode)
146 }
147
148 pub fn new(
149 client: tower_lsp::Client,
150 fs: crate::fs::FileManager,
151 zoo_client: kittycad::Client,
152 dev_mode: bool,
153 ) -> Self {
154 Self {
155 client,
156 fs: Arc::new(fs),
157 workspace_folders: Default::default(),
158 code_map: Default::default(),
159 editor_info: Arc::new(RwLock::new(CopilotEditorInfo::default())),
160 cache: Arc::new(CopilotCache::new()),
161 telemetry: Default::default(),
162 zoo_client,
163
164 is_initialized: Default::default(),
165 diagnostics_map: Default::default(),
166 dev_mode,
167 }
168 }
169
170 pub async fn get_completions(&self, language: String, prompt: String, suffix: String) -> Result<Vec<String>> {
172 let body = kittycad::types::KclCodeCompletionRequest {
173 extra: Some(kittycad::types::KclCodeCompletionParams {
174 language: Some(language.to_string()),
175 next_indent: None,
176 trim_by_indentation: true,
177 prompt_tokens: Some(prompt.len() as u32),
178 suffix_tokens: Some(suffix.len() as u32),
179 }),
180 prompt: Some(prompt),
181 suffix: Some(suffix),
182 max_tokens: Some(500),
183 temperature: Some(1.0),
184 top_p: Some(1.0),
185 n: Some(1),
187 stop: Some(["unset".to_string()].to_vec()),
188 nwo: None,
189 stream: false,
191 model_version: None,
192 };
193
194 let resp = self
195 .zoo_client
196 .ml()
197 .create_kcl_code_completions(&body)
198 .await
199 .map_err(|err| Error {
200 code: tower_lsp::jsonrpc::ErrorCode::from(69),
201 data: None,
202 message: Cow::from(format!("Failed to get completions from zoo api: {err}")),
203 })?;
204 Ok(resp.completions)
205 }
206
207 pub async fn set_editor_info(&self, params: CopilotEditorInfo) -> Result<Success> {
208 self.client.log_message(MessageType::INFO, "setEditorInfo").await;
209 let copy = Arc::clone(&self.editor_info);
210 let mut lock = copy.write().map_err(|err| Error {
211 code: tower_lsp::jsonrpc::ErrorCode::from(69),
212 data: None,
213 message: Cow::from(format!("Failed lock: {err}")),
214 })?;
215 *lock = params;
216 Ok(Success::new(true))
217 }
218
219 pub fn get_doc_params(&self, params: &CopilotLspCompletionParams) -> Result<DocParams> {
220 let pos = params.doc.position;
221 let uri = params.doc.uri.to_string();
222 let rope = ropey::Rope::from_str(¶ms.doc.source);
223 let offset = crate::lsp::util::position_to_offset(pos.into(), &rope).unwrap_or_default();
224
225 Ok(DocParams {
226 uri,
227 pos,
228 language: params.doc.language_id.to_string(),
229 prefix: crate::lsp::util::get_text_before(offset, &rope).unwrap_or_default(),
230 suffix: crate::lsp::util::get_text_after(offset, &rope).unwrap_or_default(),
231 line_before: crate::lsp::util::get_line_before(pos.into(), &rope).unwrap_or_default(),
232 rope,
233 })
234 }
235
236 pub async fn get_completions_cycling(
237 &self,
238 params: CopilotLspCompletionParams,
239 ) -> Result<CopilotCompletionResponse> {
240 let doc_params = self.get_doc_params(¶ms)?;
241 let cached_result = self.cache.get_cached_result(&doc_params.uri, doc_params.pos.line);
242 if let Some(cached_result) = cached_result {
243 return Ok(cached_result);
244 }
245
246 let doc_params = self.get_doc_params(¶ms)?;
247 let line_before = doc_params.line_before.to_string();
248
249 #[cfg(test)]
252 let mut completion_list = self
253 .get_completions(doc_params.language, doc_params.prefix, doc_params.suffix)
254 .await
255 .map_err(|err| Error {
256 code: tower_lsp::jsonrpc::ErrorCode::from(69),
257 data: None,
258 message: Cow::from(format!("Failed to get completions: {err}")),
259 })?;
260 #[cfg(not(test))]
261 let mut completion_list = vec![];
262
263 if false {
265 completion_list.push(
266 r#"fn cube(pos, scale) {
267 sg = startSketchOn(XY)
268 |> startProfile(at = pos)
269 |> line(end = [0, scale])
270 |> line(end = [scale, 0])
271 |> line(end = [0, -scale])
272 return sg
273}
274part001 = cube(pos = [0,0], scale = 20)
275 |> close()
276 |> extrude(length=20)"#
277 .to_string(),
278 );
279 }
280
281 let response = CopilotCompletionResponse::from_str_vec(completion_list, line_before, doc_params.pos);
282 for completion in response.completions.iter() {
284 let telemetry = CopilotCompletionTelemetry {
285 completion: completion.clone(),
286 params: params.clone(),
287 };
288 self.telemetry.insert(completion.uuid, telemetry);
289 }
290 self.cache
291 .set_cached_result(&doc_params.uri, &doc_params.pos.line, &response);
292
293 Ok(response)
294 }
295
296 pub async fn accept_completion(&self, params: CopilotAcceptCompletionParams) {
297 self.client
298 .log_message(MessageType::INFO, format!("Accepted completions: {params:?}"))
299 .await;
300
301 let Some(original) = self.telemetry.remove(¶ms.uuid) else {
303 return;
304 };
305
306 self.client
307 .log_message(MessageType::INFO, format!("Original telemetry: {original:?}"))
308 .await;
309
310 }
312
313 pub async fn reject_completions(&self, params: CopilotRejectCompletionParams) {
314 self.client
315 .log_message(MessageType::INFO, format!("Rejected completions: {params:?}"))
316 .await;
317
318 let mut originals: Vec<CopilotCompletionTelemetry> = Default::default();
320 for uuid in params.uuids {
321 if let Some(original) = self.telemetry.remove(&uuid).map(|(_, v)| v) {
322 originals.push(original);
323 }
324 }
325
326 self.client
327 .log_message(MessageType::INFO, format!("Original telemetry: {originals:?}"))
328 .await;
329
330 }
332}
333
334#[tower_lsp::async_trait]
335impl LanguageServer for Backend {
336 async fn initialize(&self, _: InitializeParams) -> Result<InitializeResult> {
337 Ok(InitializeResult {
338 capabilities: ServerCapabilities {
339 text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
340 open_close: Some(true),
341 change: Some(TextDocumentSyncKind::FULL),
342 ..Default::default()
343 })),
344 workspace: Some(WorkspaceServerCapabilities {
345 workspace_folders: Some(WorkspaceFoldersServerCapabilities {
346 supported: Some(true),
347 change_notifications: Some(OneOf::Left(true)),
348 }),
349 file_operations: None,
350 }),
351 ..ServerCapabilities::default()
352 },
353 ..Default::default()
354 })
355 }
356
357 async fn initialized(&self, params: InitializedParams) {
358 self.do_initialized(params).await
359 }
360
361 async fn shutdown(&self) -> tower_lsp::jsonrpc::Result<()> {
362 self.do_shutdown().await
363 }
364
365 async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
366 self.do_did_change_workspace_folders(params).await
367 }
368
369 async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
370 self.do_did_change_configuration(params).await
371 }
372
373 async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
374 self.do_did_change_watched_files(params).await
375 }
376
377 async fn did_create_files(&self, params: CreateFilesParams) {
378 self.do_did_create_files(params).await
379 }
380
381 async fn did_rename_files(&self, params: RenameFilesParams) {
382 self.do_did_rename_files(params).await
383 }
384
385 async fn did_delete_files(&self, params: DeleteFilesParams) {
386 self.do_did_delete_files(params).await
387 }
388
389 async fn did_open(&self, params: DidOpenTextDocumentParams) {
390 self.do_did_open(params).await
391 }
392
393 async fn did_change(&self, params: DidChangeTextDocumentParams) {
394 self.do_did_change(params).await;
395 }
396
397 async fn did_save(&self, params: DidSaveTextDocumentParams) {
398 self.do_did_save(params).await
399 }
400
401 async fn did_close(&self, params: DidCloseTextDocumentParams) {
402 self.do_did_close(params).await
403 }
404}