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