1#![allow(dead_code)]
3
4use std::{
5 collections::HashMap,
6 io::Write,
7 str::FromStr,
8 sync::{Arc, Mutex},
9};
10
11use tokio::sync::RwLock;
12
13pub mod custom_notifications;
14
15use anyhow::Result;
16#[cfg(feature = "cli")]
17use clap::Parser;
18use dashmap::DashMap;
19use sha2::Digest;
20use tower_lsp::{
21 jsonrpc::Result as RpcResult,
22 lsp_types::{
23 CodeAction, CodeActionKind, CodeActionOrCommand, CodeActionParams, CodeActionResponse, CompletionItem,
24 CompletionItemKind, CompletionOptions, CompletionParams, CompletionResponse, CreateFilesParams,
25 DeleteFilesParams, Diagnostic, DiagnosticOptions, DiagnosticServerCapabilities, DiagnosticSeverity,
26 DidChangeConfigurationParams, DidChangeTextDocumentParams, DidChangeWatchedFilesParams,
27 DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
28 DidSaveTextDocumentParams, DocumentDiagnosticParams, DocumentDiagnosticReport, DocumentDiagnosticReportResult,
29 DocumentFilter, DocumentFormattingParams, DocumentSymbol, DocumentSymbolParams, DocumentSymbolResponse,
30 Documentation, FoldingRange, FoldingRangeParams, FoldingRangeProviderCapability, FullDocumentDiagnosticReport,
31 Hover, HoverContents, HoverParams, HoverProviderCapability, InitializeParams, InitializeResult,
32 InitializedParams, InlayHint, InlayHintParams, InsertTextFormat, MarkupContent, MarkupKind, MessageType, OneOf,
33 Position, RelatedFullDocumentDiagnosticReport, RenameFilesParams, RenameParams, SemanticToken,
34 SemanticTokenModifier, SemanticTokenType, SemanticTokens, SemanticTokensFullOptions, SemanticTokensLegend,
35 SemanticTokensOptions, SemanticTokensParams, SemanticTokensRegistrationOptions, SemanticTokensResult,
36 SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelp, SignatureHelpOptions, SignatureHelpParams,
37 StaticRegistrationOptions, TextDocumentItem, TextDocumentRegistrationOptions, TextDocumentSyncCapability,
38 TextDocumentSyncKind, TextDocumentSyncOptions, TextEdit, WorkDoneProgressOptions, WorkspaceEdit,
39 WorkspaceFolder, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
40 },
41 Client, LanguageServer,
42};
43
44use crate::{
45 docs::kcl_doc::DocData,
46 errors::Suggestion,
47 lsp::{backend::Backend as _, util::IntoDiagnostic},
48 parsing::{
49 ast::types::{Expr, Node, VariableKind},
50 token::TokenStream,
51 PIPE_OPERATOR,
52 },
53 ModuleId, Program, SourceRange,
54};
55const SEMANTIC_TOKEN_TYPES: [SemanticTokenType; 10] = [
56 SemanticTokenType::NUMBER,
57 SemanticTokenType::VARIABLE,
58 SemanticTokenType::KEYWORD,
59 SemanticTokenType::TYPE,
60 SemanticTokenType::STRING,
61 SemanticTokenType::OPERATOR,
62 SemanticTokenType::COMMENT,
63 SemanticTokenType::FUNCTION,
64 SemanticTokenType::PARAMETER,
65 SemanticTokenType::PROPERTY,
66];
67
68const SEMANTIC_TOKEN_MODIFIERS: [SemanticTokenModifier; 5] = [
69 SemanticTokenModifier::DECLARATION,
70 SemanticTokenModifier::DEFINITION,
71 SemanticTokenModifier::DEFAULT_LIBRARY,
72 SemanticTokenModifier::READONLY,
73 SemanticTokenModifier::STATIC,
74];
75
76#[derive(Clone, Debug)]
78#[cfg_attr(feature = "cli", derive(Parser))]
79pub struct Server {
80 #[cfg_attr(feature = "cli", clap(long, default_value = "8080"))]
82 pub socket: i32,
83
84 #[cfg_attr(feature = "cli", clap(short, long, default_value = "false"))]
86 pub stdio: bool,
87}
88
89#[derive(Clone)]
91pub struct Backend {
92 pub client: Client,
94 pub fs: Arc<crate::fs::FileManager>,
96 pub workspace_folders: DashMap<String, WorkspaceFolder>,
98 pub stdlib_completions: HashMap<String, CompletionItem>,
100 pub stdlib_signatures: HashMap<String, SignatureHelp>,
102 pub(super) token_map: DashMap<String, TokenStream>,
104 pub ast_map: DashMap<String, Node<crate::parsing::ast::types::Program>>,
106 pub code_map: DashMap<String, Vec<u8>>,
108 pub diagnostics_map: DashMap<String, Vec<Diagnostic>>,
110 pub symbols_map: DashMap<String, Vec<DocumentSymbol>>,
112 pub semantic_tokens_map: DashMap<String, Vec<SemanticToken>>,
114 pub zoo_client: kittycad::Client,
116 pub can_send_telemetry: bool,
118 pub executor_ctx: Arc<RwLock<Option<crate::execution::ExecutorContext>>>,
120 pub can_execute: Arc<RwLock<bool>>,
122
123 pub is_initialized: Arc<RwLock<bool>>,
124}
125
126impl Backend {
127 #[cfg(target_arch = "wasm32")]
128 pub fn new_wasm(
129 client: Client,
130 executor_ctx: Option<crate::execution::ExecutorContext>,
131 fs: crate::fs::wasm::FileSystemManager,
132 zoo_client: kittycad::Client,
133 can_send_telemetry: bool,
134 ) -> Result<Self, String> {
135 Self::with_file_manager(
136 client,
137 executor_ctx,
138 crate::fs::FileManager::new(fs),
139 zoo_client,
140 can_send_telemetry,
141 )
142 }
143
144 #[cfg(not(target_arch = "wasm32"))]
145 pub fn new(
146 client: Client,
147 executor_ctx: Option<crate::execution::ExecutorContext>,
148 zoo_client: kittycad::Client,
149 can_send_telemetry: bool,
150 ) -> Result<Self, String> {
151 Self::with_file_manager(
152 client,
153 executor_ctx,
154 crate::fs::FileManager::new(),
155 zoo_client,
156 can_send_telemetry,
157 )
158 }
159
160 fn with_file_manager(
161 client: Client,
162 executor_ctx: Option<crate::execution::ExecutorContext>,
163 fs: crate::fs::FileManager,
164 zoo_client: kittycad::Client,
165 can_send_telemetry: bool,
166 ) -> Result<Self, String> {
167 let stdlib = crate::std::StdLib::new();
168 let kcl_std = crate::docs::kcl_doc::walk_prelude();
169 let stdlib_completions = get_completions_from_stdlib(&stdlib, &kcl_std).map_err(|e| e.to_string())?;
170 let stdlib_signatures = get_signatures_from_stdlib(&stdlib, &kcl_std);
171
172 Ok(Self {
173 client,
174 fs: Arc::new(fs),
175 stdlib_completions,
176 stdlib_signatures,
177 zoo_client,
178 can_send_telemetry,
179 can_execute: Arc::new(RwLock::new(executor_ctx.is_some())),
180 executor_ctx: Arc::new(RwLock::new(executor_ctx)),
181 workspace_folders: Default::default(),
182 token_map: Default::default(),
183 ast_map: Default::default(),
184 code_map: Default::default(),
185 diagnostics_map: Default::default(),
186 symbols_map: Default::default(),
187 semantic_tokens_map: Default::default(),
188 is_initialized: Default::default(),
189 })
190 }
191
192 fn remove_from_ast_maps(&self, filename: &str) {
193 self.ast_map.remove(filename);
194 self.symbols_map.remove(filename);
195 }
196}
197
198#[async_trait::async_trait]
200impl crate::lsp::backend::Backend for Backend {
201 fn client(&self) -> &Client {
202 &self.client
203 }
204
205 fn fs(&self) -> &Arc<crate::fs::FileManager> {
206 &self.fs
207 }
208
209 async fn is_initialized(&self) -> bool {
210 *self.is_initialized.read().await
211 }
212
213 async fn set_is_initialized(&self, is_initialized: bool) {
214 *self.is_initialized.write().await = is_initialized;
215 }
216
217 async fn workspace_folders(&self) -> Vec<WorkspaceFolder> {
218 self.workspace_folders.iter().map(|i| i.clone()).collect()
220 }
221
222 async fn add_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
223 for folder in folders {
224 self.workspace_folders.insert(folder.name.to_string(), folder);
225 }
226 }
227
228 async fn remove_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
229 for folder in folders {
230 self.workspace_folders.remove(&folder.name);
231 }
232 }
233
234 fn code_map(&self) -> &DashMap<String, Vec<u8>> {
235 &self.code_map
236 }
237
238 async fn insert_code_map(&self, uri: String, text: Vec<u8>) {
239 self.code_map.insert(uri, text);
240 }
241
242 async fn remove_from_code_map(&self, uri: String) -> Option<Vec<u8>> {
243 self.code_map.remove(&uri).map(|x| x.1)
244 }
245
246 async fn clear_code_state(&self) {
247 self.code_map.clear();
248 self.token_map.clear();
249 self.ast_map.clear();
250 self.diagnostics_map.clear();
251 self.symbols_map.clear();
252 self.semantic_tokens_map.clear();
253 }
254
255 fn current_diagnostics_map(&self) -> &DashMap<String, Vec<Diagnostic>> {
256 &self.diagnostics_map
257 }
258
259 async fn inner_on_change(&self, params: TextDocumentItem, force: bool) {
260 if force {
261 crate::bust_cache().await;
262 }
263
264 let filename = params.uri.to_string();
265 let module_id = ModuleId::default();
269 let tokens = match crate::parsing::token::lex(¶ms.text, module_id) {
270 Ok(tokens) => tokens,
271 Err(err) => {
272 self.add_to_diagnostics(¶ms, &[err], true).await;
273 self.token_map.remove(&filename);
274 self.remove_from_ast_maps(&filename);
275 self.semantic_tokens_map.remove(&filename);
276 return;
277 }
278 };
279
280 let tokens_changed = if let Some(previous_tokens) = self.token_map.get(&filename) {
282 *previous_tokens != tokens
283 } else {
284 true
285 };
286
287 let had_diagnostics = self.has_diagnostics(params.uri.as_ref()).await;
288
289 if !tokens_changed && !force && !had_diagnostics {
291 return;
293 }
294
295 if tokens_changed {
296 self.token_map.insert(params.uri.to_string(), tokens.clone());
298 self.update_semantic_tokens(&tokens, ¶ms).await;
300 }
301
302 let (ast, errs) = match crate::parsing::parse_tokens(tokens.clone()).0 {
305 Ok(result) => result,
306 Err(err) => {
307 self.add_to_diagnostics(¶ms, &[err], true).await;
308 self.remove_from_ast_maps(&filename);
309 return;
310 }
311 };
312
313 self.add_to_diagnostics(¶ms, &errs, true).await;
314
315 if errs.iter().any(|e| e.severity == crate::errors::Severity::Fatal) {
316 self.remove_from_ast_maps(&filename);
317 return;
318 }
319
320 let Some(mut ast) = ast else {
321 self.remove_from_ast_maps(&filename);
322 return;
323 };
324
325 ast.compute_digest();
329
330 let ast_changed = match self.ast_map.get(&filename) {
332 Some(old_ast) => {
333 *old_ast != ast
335 }
336 None => true,
337 };
338
339 if !ast_changed && !force && !had_diagnostics {
340 return;
342 }
343
344 if ast_changed {
345 self.ast_map.insert(params.uri.to_string(), ast.clone());
346 self.symbols_map.insert(
348 params.uri.to_string(),
349 ast.get_lsp_symbols(¶ms.text).unwrap_or_default(),
350 );
351
352 self.update_semantic_tokens(&tokens, ¶ms).await;
354
355 let discovered_findings = ast.lint_all().into_iter().flatten().collect::<Vec<_>>();
356 self.add_to_diagnostics(¶ms, &discovered_findings, false).await;
357 }
358
359 if self.can_execute().await || self.executor_ctx().await.is_none() {
361 self.client
364 .send_notification::<custom_notifications::AstUpdated>(ast.clone())
365 .await;
366 }
367
368 if self.execute(¶ms, &ast.into()).await.is_err() {
372 return;
373 }
374
375 self.clear_diagnostics_map(¶ms.uri, Some(DiagnosticSeverity::ERROR))
377 .await;
378 }
379}
380
381impl Backend {
382 pub async fn can_execute(&self) -> bool {
383 *self.can_execute.read().await
384 }
385
386 pub async fn executor_ctx(&self) -> tokio::sync::RwLockReadGuard<'_, Option<crate::execution::ExecutorContext>> {
387 self.executor_ctx.read().await
388 }
389
390 async fn update_semantic_tokens(&self, tokens: &TokenStream, params: &TextDocumentItem) {
391 let mut semantic_tokens = vec![];
393 let mut last_position = Position::new(0, 0);
394 for token in tokens.as_slice() {
395 let Ok(token_type) = SemanticTokenType::try_from(token.token_type) else {
396 continue;
399 };
400
401 let mut token_type_index = match self.get_semantic_token_type_index(&token_type) {
402 Some(index) => index,
403 None => {
406 self.client
407 .log_message(
408 MessageType::ERROR,
409 format!("token type `{:?}` not accounted for", token_type),
410 )
411 .await;
412 continue;
413 }
414 };
415
416 let source_range: SourceRange = token.into();
417 let position = source_range.start_to_lsp_position(¶ms.text);
418
419 let token_modifiers_bitset = if let Some(ast) = self.ast_map.get(params.uri.as_str()) {
422 let token_index = Arc::new(Mutex::new(token_type_index));
423 let modifier_index: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
424 crate::walk::walk(&ast, |node: crate::walk::Node| {
425 let Ok(node_range): Result<SourceRange, _> = (&node).try_into() else {
426 return Ok(true);
427 };
428
429 if !node_range.contains(source_range.start()) {
430 return Ok(true);
431 }
432
433 let get_modifier = |modifier: Vec<SemanticTokenModifier>| -> Result<bool> {
434 let mut mods = modifier_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
435 let Some(token_modifier_index) = self.get_semantic_token_modifier_index(modifier) else {
436 return Ok(true);
437 };
438 if *mods == 0 {
439 *mods = token_modifier_index;
440 } else {
441 *mods |= token_modifier_index;
442 }
443 Ok(false)
444 };
445
446 match node {
447 crate::walk::Node::TagDeclarator(_) => {
448 return get_modifier(vec![
449 SemanticTokenModifier::DEFINITION,
450 SemanticTokenModifier::STATIC,
451 ]);
452 }
453 crate::walk::Node::VariableDeclarator(variable) => {
454 let sr: SourceRange = (&variable.id).into();
455 if sr.contains(source_range.start()) {
456 if let Expr::FunctionExpression(_) = &variable.init {
457 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
458 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::FUNCTION) {
459 Some(index) => index,
460 None => token_type_index,
461 };
462 }
463
464 return get_modifier(vec![
465 SemanticTokenModifier::DECLARATION,
466 SemanticTokenModifier::READONLY,
467 ]);
468 }
469 }
470 crate::walk::Node::Parameter(_) => {
471 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
472 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PARAMETER) {
473 Some(index) => index,
474 None => token_type_index,
475 };
476 return Ok(false);
477 }
478 crate::walk::Node::MemberExpression(member_expression) => {
479 let sr: SourceRange = (&member_expression.property).into();
480 if sr.contains(source_range.start()) {
481 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
482 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PROPERTY) {
483 Some(index) => index,
484 None => token_type_index,
485 };
486 return Ok(false);
487 }
488 }
489 crate::walk::Node::ObjectProperty(object_property) => {
490 let sr: SourceRange = (&object_property.key).into();
491 if sr.contains(source_range.start()) {
492 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
493 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PROPERTY) {
494 Some(index) => index,
495 None => token_type_index,
496 };
497 }
498 return get_modifier(vec![SemanticTokenModifier::DECLARATION]);
499 }
500 crate::walk::Node::CallExpression(call_expr) => {
501 let sr: SourceRange = (&call_expr.callee).into();
502 if sr.contains(source_range.start()) {
503 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
504 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::FUNCTION) {
505 Some(index) => index,
506 None => token_type_index,
507 };
508
509 if self.stdlib_completions.contains_key(&call_expr.callee.name) {
510 return get_modifier(vec![SemanticTokenModifier::DEFAULT_LIBRARY]);
512 }
513
514 return Ok(false);
515 }
516 }
517 _ => {}
518 }
519 Ok(true)
520 })
521 .unwrap_or_default();
522
523 let t = if let Ok(guard) = token_index.lock() { *guard } else { 0 };
524 token_type_index = t;
525
526 let m = if let Ok(guard) = modifier_index.lock() {
527 *guard
528 } else {
529 0
530 };
531 m
532 } else {
533 0
534 };
535
536 if let Some(line) = params.text.lines().nth(position.line as usize) {
540 if line.len() == position.character as usize {
541 let semantic_token = SemanticToken {
544 delta_line: position.line - last_position.line + 1,
545 delta_start: 0,
546 length: (token.end - token.start) as u32,
547 token_type: token_type_index,
548 token_modifiers_bitset,
549 };
550
551 semantic_tokens.push(semantic_token);
552
553 last_position = Position::new(position.line + 1, 0);
554 continue;
555 }
556 }
557
558 let semantic_token = SemanticToken {
559 delta_line: position.line - last_position.line,
560 delta_start: if position.line != last_position.line {
561 position.character
562 } else {
563 position.character - last_position.character
564 },
565 length: (token.end - token.start) as u32,
566 token_type: token_type_index,
567 token_modifiers_bitset,
568 };
569
570 semantic_tokens.push(semantic_token);
571
572 last_position = position;
573 }
574 self.semantic_tokens_map.insert(params.uri.to_string(), semantic_tokens);
575 }
576
577 async fn clear_diagnostics_map(&self, uri: &url::Url, severity: Option<DiagnosticSeverity>) {
578 let Some(mut items) = self.diagnostics_map.get_mut(uri.as_str()) else {
579 return;
580 };
581
582 if let Some(severity) = severity {
584 items.retain(|x| x.severity != Some(severity));
585 } else {
586 items.clear();
587 }
588
589 if items.is_empty() {
590 #[cfg(not(target_arch = "wasm32"))]
591 {
592 self.client.publish_diagnostics(uri.clone(), items.clone(), None).await;
593 }
594
595 drop(items);
597
598 self.diagnostics_map.remove(uri.as_str());
599 } else {
600 #[cfg(not(target_arch = "wasm32"))]
603 {
604 self.client.publish_diagnostics(uri.clone(), items.clone(), None).await;
605 }
606 }
607 }
608
609 async fn add_to_diagnostics<DiagT: IntoDiagnostic + std::fmt::Debug>(
610 &self,
611 params: &TextDocumentItem,
612 diagnostics: &[DiagT],
613 clear_all_before_add: bool,
614 ) {
615 if diagnostics.is_empty() {
616 return;
617 }
618
619 if clear_all_before_add {
620 self.clear_diagnostics_map(¶ms.uri, None).await;
621 } else if diagnostics.iter().all(|x| x.severity() == DiagnosticSeverity::ERROR) {
622 self.clear_diagnostics_map(¶ms.uri, Some(DiagnosticSeverity::ERROR))
626 .await;
627 } else if diagnostics
628 .iter()
629 .all(|x| x.severity() == DiagnosticSeverity::INFORMATION)
630 {
631 self.clear_diagnostics_map(¶ms.uri, Some(DiagnosticSeverity::INFORMATION))
634 .await;
635 }
636
637 let mut items = if let Some(items) = self.diagnostics_map.get(params.uri.as_str()) {
638 items.clone()
640 } else {
641 vec![]
642 };
643
644 for diagnostic in diagnostics {
645 let lsp_d = diagnostic.to_lsp_diagnostics(¶ms.text);
646 for d in lsp_d {
648 if !items.iter().any(|x| x == &d) {
649 items.push(d);
650 }
651 }
652 }
653
654 self.diagnostics_map.insert(params.uri.to_string(), items.clone());
655
656 self.client.publish_diagnostics(params.uri.clone(), items, None).await;
657 }
658
659 async fn execute(&self, params: &TextDocumentItem, ast: &Program) -> Result<()> {
660 if !self.can_execute().await {
662 return Ok(());
663 }
664
665 let ctx = self.executor_ctx().await;
667 let Some(ref executor_ctx) = *ctx else {
668 return Ok(());
669 };
670
671 if !self.is_initialized().await {
672 return Ok(());
674 }
675
676 match executor_ctx.run_with_caching(ast.clone()).await {
677 Err(err) => {
678 self.add_to_diagnostics(params, &[err], false).await;
679
680 Err(anyhow::anyhow!("failed to execute code"))
683 }
684 Ok(_) => Ok(()),
685 }
686 }
687
688 pub fn get_semantic_token_type_index(&self, token_type: &SemanticTokenType) -> Option<u32> {
689 SEMANTIC_TOKEN_TYPES
690 .iter()
691 .position(|x| *x == *token_type)
692 .map(|y| y as u32)
693 }
694
695 pub fn get_semantic_token_modifier_index(&self, token_types: Vec<SemanticTokenModifier>) -> Option<u32> {
696 if token_types.is_empty() {
697 return None;
698 }
699
700 let mut modifier = None;
701 for token_type in token_types {
702 if let Some(index) = SEMANTIC_TOKEN_MODIFIERS
703 .iter()
704 .position(|x| *x == token_type)
705 .map(|y| y as u32)
706 {
707 modifier = match modifier {
708 Some(modifier) => Some(modifier | index),
709 None => Some(index),
710 };
711 }
712 }
713 modifier
714 }
715
716 pub async fn create_zip(&self) -> Result<Vec<u8>> {
717 let mut buf = vec![];
719 let mut zip = zip::ZipWriter::new(std::io::Cursor::new(&mut buf));
720 for code in self.code_map.iter() {
721 let entry = code.key();
722 let value = code.value();
723 let file_name = entry.replace("file://", "").to_string();
724
725 let options = zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
726 zip.start_file(file_name, options)?;
727 zip.write_all(value)?;
728 }
729 zip.finish()?;
732
733 Ok(buf)
734 }
735
736 pub async fn send_telemetry(&self) -> Result<()> {
737 let user = self
739 .zoo_client
740 .users()
741 .get_self()
742 .await
743 .map_err(|e| anyhow::anyhow!(e.to_string()))?;
744
745 let mut hasher = sha2::Sha256::new();
748 hasher.update(user.id);
750 let result = hasher.finalize();
752 let user_id_hash = format!("{:x}", result);
754
755 let workspace_folders = self.workspace_folders().await;
758 let project_names: Vec<&str> = workspace_folders.iter().map(|v| v.name.as_str()).collect::<Vec<_>>();
759 let project_name = project_names
761 .first()
762 .ok_or_else(|| anyhow::anyhow!("no project names"))?
763 .to_string();
764
765 self.zoo_client
767 .meta()
768 .create_event(
769 vec![kittycad::types::multipart::Attachment {
770 name: "attachment".to_string(),
772 filename: Some("attachment.zip".to_string()),
773 content_type: Some("application/x-zip".to_string()),
774 data: self.create_zip().await?,
775 }],
776 &kittycad::types::Event {
777 attachment_uri: None,
779 created_at: chrono::Utc::now(),
780 event_type: kittycad::types::ModelingAppEventType::SuccessfulCompileBeforeClose,
781 last_compiled_at: Some(chrono::Utc::now()),
782 project_description: None,
784 project_name,
785 source_id: uuid::Uuid::from_str("70178592-dfca-47b3-bd2d-6fce2bcaee04").unwrap(),
788 type_: kittycad::types::Type::ModelingAppEvent,
789 user_id: user_id_hash,
790 },
791 )
792 .await
793 .map_err(|e| anyhow::anyhow!(e.to_string()))?;
794
795 Ok(())
796 }
797
798 pub async fn update_units(
799 &self,
800 params: custom_notifications::UpdateUnitsParams,
801 ) -> RpcResult<Option<custom_notifications::UpdateUnitsResponse>> {
802 {
803 let mut ctx = self.executor_ctx.write().await;
804 let Some(ref mut executor_ctx) = *ctx else {
806 self.client
807 .log_message(MessageType::ERROR, "no executor context set to update units for")
808 .await;
809 return Ok(None);
810 };
811
812 self.client
813 .log_message(MessageType::INFO, format!("update units: {:?}", params))
814 .await;
815
816 if executor_ctx.settings.units == params.units
817 && !self.has_diagnostics(params.text_document.uri.as_ref()).await
818 {
819 return Ok(None);
821 }
822
823 executor_ctx.update_units(params.units);
825 }
826 let new_params = TextDocumentItem {
830 uri: params.text_document.uri.clone(),
831 text: std::mem::take(&mut params.text.to_string()),
832 version: Default::default(),
833 language_id: Default::default(),
834 };
835
836 self.inner_on_change(new_params, true).await;
838
839 if self.has_diagnostics(params.text_document.uri.as_ref()).await {
842 return Ok(None);
843 }
844
845 Ok(Some(custom_notifications::UpdateUnitsResponse {}))
846 }
847
848 pub async fn update_can_execute(
849 &self,
850 params: custom_notifications::UpdateCanExecuteParams,
851 ) -> RpcResult<custom_notifications::UpdateCanExecuteResponse> {
852 let mut can_execute = self.can_execute.write().await;
853
854 if *can_execute == params.can_execute {
855 return Ok(custom_notifications::UpdateCanExecuteResponse {});
856 }
857
858 *can_execute = params.can_execute;
859
860 Ok(custom_notifications::UpdateCanExecuteResponse {})
861 }
862}
863
864#[tower_lsp::async_trait]
865impl LanguageServer for Backend {
866 async fn initialize(&self, params: InitializeParams) -> RpcResult<InitializeResult> {
867 self.client
868 .log_message(MessageType::INFO, format!("initialize: {:?}", params))
869 .await;
870
871 Ok(InitializeResult {
872 capabilities: ServerCapabilities {
873 completion_provider: Some(CompletionOptions {
874 resolve_provider: Some(false),
875 trigger_characters: Some(vec![".".to_string()]),
876 work_done_progress_options: Default::default(),
877 all_commit_characters: None,
878 ..Default::default()
879 }),
880 diagnostic_provider: Some(DiagnosticServerCapabilities::Options(DiagnosticOptions {
881 ..Default::default()
882 })),
883 document_formatting_provider: Some(OneOf::Left(true)),
884 folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
885 hover_provider: Some(HoverProviderCapability::Simple(true)),
886 inlay_hint_provider: Some(OneOf::Left(true)),
887 rename_provider: Some(OneOf::Left(true)),
888 semantic_tokens_provider: Some(SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(
889 SemanticTokensRegistrationOptions {
890 text_document_registration_options: {
891 TextDocumentRegistrationOptions {
892 document_selector: Some(vec![DocumentFilter {
893 language: Some("kcl".to_string()),
894 scheme: Some("file".to_string()),
895 pattern: None,
896 }]),
897 }
898 },
899 semantic_tokens_options: SemanticTokensOptions {
900 work_done_progress_options: WorkDoneProgressOptions::default(),
901 legend: SemanticTokensLegend {
902 token_types: SEMANTIC_TOKEN_TYPES.to_vec(),
903 token_modifiers: SEMANTIC_TOKEN_MODIFIERS.to_vec(),
904 },
905 range: Some(false),
906 full: Some(SemanticTokensFullOptions::Bool(true)),
907 },
908 static_registration_options: StaticRegistrationOptions::default(),
909 },
910 )),
911 signature_help_provider: Some(SignatureHelpOptions {
912 trigger_characters: None,
913 retrigger_characters: None,
914 ..Default::default()
915 }),
916 text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
917 open_close: Some(true),
918 change: Some(TextDocumentSyncKind::FULL),
919 ..Default::default()
920 })),
921 workspace: Some(WorkspaceServerCapabilities {
922 workspace_folders: Some(WorkspaceFoldersServerCapabilities {
923 supported: Some(true),
924 change_notifications: Some(OneOf::Left(true)),
925 }),
926 file_operations: None,
927 }),
928 ..Default::default()
929 },
930 ..Default::default()
931 })
932 }
933
934 async fn initialized(&self, params: InitializedParams) {
935 self.do_initialized(params).await
936 }
937
938 async fn shutdown(&self) -> RpcResult<()> {
939 self.do_shutdown().await
940 }
941
942 async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
943 self.do_did_change_workspace_folders(params).await
944 }
945
946 async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
947 self.do_did_change_configuration(params).await
948 }
949
950 async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
951 self.do_did_change_watched_files(params).await
952 }
953
954 async fn did_create_files(&self, params: CreateFilesParams) {
955 self.do_did_create_files(params).await
956 }
957
958 async fn did_rename_files(&self, params: RenameFilesParams) {
959 self.do_did_rename_files(params).await
960 }
961
962 async fn did_delete_files(&self, params: DeleteFilesParams) {
963 self.do_did_delete_files(params).await
964 }
965
966 async fn did_open(&self, params: DidOpenTextDocumentParams) {
967 self.do_did_open(params).await
968 }
969
970 async fn did_change(&self, params: DidChangeTextDocumentParams) {
971 self.do_did_change(params).await;
972 }
973
974 async fn did_save(&self, params: DidSaveTextDocumentParams) {
975 self.do_did_save(params).await
976 }
977
978 async fn did_close(&self, params: DidCloseTextDocumentParams) {
979 self.do_did_close(params).await;
980
981 if !self.can_send_telemetry {
984 return;
985 }
986
987 #[cfg(target_arch = "wasm32")]
989 {
990 let be = self.clone();
991 wasm_bindgen_futures::spawn_local(async move {
992 if let Err(err) = be.send_telemetry().await {
993 be.client
994 .log_message(MessageType::WARNING, format!("failed to send telemetry: {}", err))
995 .await;
996 }
997 });
998 }
999 #[cfg(not(target_arch = "wasm32"))]
1000 if let Err(err) = self.send_telemetry().await {
1001 self.client
1002 .log_message(MessageType::WARNING, format!("failed to send telemetry: {}", err))
1003 .await;
1004 }
1005 }
1006
1007 async fn hover(&self, params: HoverParams) -> RpcResult<Option<Hover>> {
1008 let filename = params.text_document_position_params.text_document.uri.to_string();
1009
1010 let Some(current_code) = self.code_map.get(&filename) else {
1011 return Ok(None);
1012 };
1013 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1014 return Ok(None);
1015 };
1016
1017 let pos = position_to_char_index(params.text_document_position_params.position, current_code);
1018
1019 let Some(ast) = self.ast_map.get(&filename) else {
1021 return Ok(None);
1022 };
1023
1024 let Some(hover) = ast.get_hover_value_for_position(pos, current_code) else {
1025 return Ok(None);
1026 };
1027
1028 match hover {
1029 crate::parsing::ast::types::Hover::Function { name, range } => {
1030 let Some(completion) = self.stdlib_completions.get(&name) else {
1032 return Ok(None);
1033 };
1034 let Some(docs) = &completion.documentation else {
1035 return Ok(None);
1036 };
1037
1038 let docs = match docs {
1039 Documentation::String(docs) => docs,
1040 Documentation::MarkupContent(MarkupContent { value, .. }) => value,
1041 };
1042
1043 let Some(label_details) = &completion.label_details else {
1044 return Ok(None);
1045 };
1046
1047 Ok(Some(Hover {
1048 contents: HoverContents::Markup(MarkupContent {
1049 kind: MarkupKind::Markdown,
1050 value: format!(
1051 "```{}{}```\n{}",
1052 name,
1053 if let Some(detail) = &label_details.detail {
1054 detail
1055 } else {
1056 ""
1057 },
1058 docs
1059 ),
1060 }),
1061 range: Some(range),
1062 }))
1063 }
1064 crate::parsing::ast::types::Hover::Signature { .. } => Ok(None),
1065 crate::parsing::ast::types::Hover::Comment { value, range } => Ok(Some(Hover {
1066 contents: HoverContents::Markup(MarkupContent {
1067 kind: MarkupKind::Markdown,
1068 value,
1069 }),
1070 range: Some(range),
1071 })),
1072 }
1073 }
1074
1075 async fn completion(&self, params: CompletionParams) -> RpcResult<Option<CompletionResponse>> {
1076 let mut completions = vec![CompletionItem {
1077 label: PIPE_OPERATOR.to_string(),
1078 label_details: None,
1079 kind: Some(CompletionItemKind::OPERATOR),
1080 detail: Some("A pipe operator.".to_string()),
1081 documentation: Some(Documentation::MarkupContent(MarkupContent {
1082 kind: MarkupKind::Markdown,
1083 value: "A pipe operator.".to_string(),
1084 })),
1085 deprecated: Some(false),
1086 preselect: None,
1087 sort_text: None,
1088 filter_text: None,
1089 insert_text: Some("|> ".to_string()),
1090 insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
1091 insert_text_mode: None,
1092 text_edit: None,
1093 additional_text_edits: None,
1094 command: None,
1095 commit_characters: None,
1096 data: None,
1097 tags: None,
1098 }];
1099
1100 let Some(current_code) = self
1102 .code_map
1103 .get(params.text_document_position.text_document.uri.as_ref())
1104 else {
1105 return Ok(Some(CompletionResponse::Array(completions)));
1106 };
1107 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1108 return Ok(Some(CompletionResponse::Array(completions)));
1109 };
1110
1111 if let Some(line) = current_code
1113 .lines()
1114 .nth(params.text_document_position.position.line as usize)
1115 {
1116 let char_pos = params.text_document_position.position.character as usize;
1117 if char_pos <= line.len() {
1118 let line_prefix = &line[..char_pos];
1119 let last_word = line_prefix
1121 .split(|c: char| c.is_whitespace() || c.is_ascii_punctuation())
1122 .last()
1123 .unwrap_or("");
1124
1125 if !last_word.is_empty() && last_word.chars().next().unwrap().is_ascii_digit() {
1127 return Ok(None);
1128 }
1129 }
1130 }
1131
1132 completions.extend(self.stdlib_completions.values().cloned());
1133
1134 let Some(ast) = self
1136 .ast_map
1137 .get(params.text_document_position.text_document.uri.as_ref())
1138 else {
1139 return Ok(Some(CompletionResponse::Array(completions)));
1140 };
1141
1142 let Some(current_code) = self
1143 .code_map
1144 .get(params.text_document_position.text_document.uri.as_ref())
1145 else {
1146 return Ok(Some(CompletionResponse::Array(completions)));
1147 };
1148 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1149 return Ok(Some(CompletionResponse::Array(completions)));
1150 };
1151
1152 let position = position_to_char_index(params.text_document_position.position, current_code);
1153 if ast.get_non_code_meta_for_position(position).is_some() {
1154 return Ok(None);
1156 }
1157
1158 let Ok(variables) = ast.completion_items() else {
1160 return Ok(Some(CompletionResponse::Array(completions)));
1161 };
1162
1163 completions.extend(variables);
1165
1166 Ok(Some(CompletionResponse::Array(completions)))
1167 }
1168
1169 async fn diagnostic(&self, params: DocumentDiagnosticParams) -> RpcResult<DocumentDiagnosticReportResult> {
1170 let filename = params.text_document.uri.to_string();
1171
1172 let Some(items) = self.diagnostics_map.get(&filename) else {
1174 return Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1176 RelatedFullDocumentDiagnosticReport {
1177 related_documents: None,
1178 full_document_diagnostic_report: FullDocumentDiagnosticReport {
1179 result_id: None,
1180 items: vec![],
1181 },
1182 },
1183 )));
1184 };
1185
1186 Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1187 RelatedFullDocumentDiagnosticReport {
1188 related_documents: None,
1189 full_document_diagnostic_report: FullDocumentDiagnosticReport {
1190 result_id: None,
1191 items: items.clone(),
1192 },
1193 },
1194 )))
1195 }
1196
1197 async fn signature_help(&self, params: SignatureHelpParams) -> RpcResult<Option<SignatureHelp>> {
1198 let filename = params.text_document_position_params.text_document.uri.to_string();
1199
1200 let Some(current_code) = self.code_map.get(&filename) else {
1201 return Ok(None);
1202 };
1203 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1204 return Ok(None);
1205 };
1206
1207 let pos = position_to_char_index(params.text_document_position_params.position, current_code);
1208
1209 let Some(ast) = self.ast_map.get(&filename) else {
1211 return Ok(None);
1212 };
1213
1214 let Some(value) = ast.get_expr_for_position(pos) else {
1215 return Ok(None);
1216 };
1217
1218 let Some(hover) = value.get_hover_value_for_position(pos, current_code) else {
1219 return Ok(None);
1220 };
1221
1222 match hover {
1223 crate::parsing::ast::types::Hover::Function { name, range: _ } => {
1224 let Some(signature) = self.stdlib_signatures.get(&name) else {
1226 return Ok(None);
1227 };
1228
1229 Ok(Some(signature.clone()))
1230 }
1231 crate::parsing::ast::types::Hover::Signature {
1232 name,
1233 parameter_index,
1234 range: _,
1235 } => {
1236 let Some(signature) = self.stdlib_signatures.get(&name) else {
1237 return Ok(None);
1238 };
1239
1240 let mut signature = signature.clone();
1241
1242 signature.active_parameter = Some(parameter_index);
1243
1244 Ok(Some(signature))
1245 }
1246 crate::parsing::ast::types::Hover::Comment { value: _, range: _ } => {
1247 return Ok(None);
1248 }
1249 }
1250 }
1251
1252 async fn inlay_hint(&self, _params: InlayHintParams) -> RpcResult<Option<Vec<InlayHint>>> {
1253 Ok(None)
1256 }
1257
1258 async fn semantic_tokens_full(&self, params: SemanticTokensParams) -> RpcResult<Option<SemanticTokensResult>> {
1259 let filename = params.text_document.uri.to_string();
1260
1261 let Some(semantic_tokens) = self.semantic_tokens_map.get(&filename) else {
1262 return Ok(None);
1263 };
1264
1265 Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
1266 result_id: None,
1267 data: semantic_tokens.clone(),
1268 })))
1269 }
1270
1271 async fn document_symbol(&self, params: DocumentSymbolParams) -> RpcResult<Option<DocumentSymbolResponse>> {
1272 let filename = params.text_document.uri.to_string();
1273
1274 let Some(symbols) = self.symbols_map.get(&filename) else {
1275 return Ok(None);
1276 };
1277
1278 Ok(Some(DocumentSymbolResponse::Nested(symbols.clone())))
1279 }
1280
1281 async fn formatting(&self, params: DocumentFormattingParams) -> RpcResult<Option<Vec<TextEdit>>> {
1282 let filename = params.text_document.uri.to_string();
1283
1284 let Some(current_code) = self.code_map.get(&filename) else {
1285 return Ok(None);
1286 };
1287 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1288 return Ok(None);
1289 };
1290
1291 let module_id = ModuleId::default();
1295 let Ok(ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
1296 return Ok(None);
1297 };
1298 let recast = ast.recast(
1300 &crate::parsing::ast::types::FormatOptions {
1301 tab_size: params.options.tab_size as usize,
1302 insert_final_newline: params.options.insert_final_newline.unwrap_or(false),
1303 use_tabs: !params.options.insert_spaces,
1304 },
1305 0,
1306 );
1307 let source_range = SourceRange::new(0, current_code.len(), module_id);
1308 let range = source_range.to_lsp_range(current_code);
1309 Ok(Some(vec![TextEdit {
1310 new_text: recast,
1311 range,
1312 }]))
1313 }
1314
1315 async fn rename(&self, params: RenameParams) -> RpcResult<Option<WorkspaceEdit>> {
1316 let filename = params.text_document_position.text_document.uri.to_string();
1317
1318 let Some(current_code) = self.code_map.get(&filename) else {
1319 return Ok(None);
1320 };
1321 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1322 return Ok(None);
1323 };
1324
1325 let module_id = ModuleId::default();
1329 let Ok(mut ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
1330 return Ok(None);
1331 };
1332
1333 let pos = position_to_char_index(params.text_document_position.position, current_code);
1335 ast.rename_symbol(¶ms.new_name, pos);
1337 let recast = ast.recast(&Default::default(), 0);
1339 let source_range = SourceRange::new(0, current_code.len() - 1, module_id);
1340 let range = source_range.to_lsp_range(current_code);
1341 Ok(Some(WorkspaceEdit {
1342 changes: Some(HashMap::from([(
1343 params.text_document_position.text_document.uri,
1344 vec![TextEdit {
1345 new_text: recast,
1346 range,
1347 }],
1348 )])),
1349 document_changes: None,
1350 change_annotations: None,
1351 }))
1352 }
1353
1354 async fn folding_range(&self, params: FoldingRangeParams) -> RpcResult<Option<Vec<FoldingRange>>> {
1355 let filename = params.text_document.uri.to_string();
1356
1357 let Some(ast) = self.ast_map.get(&filename) else {
1359 return Ok(None);
1360 };
1361
1362 let folding_ranges = ast.get_lsp_folding_ranges();
1364
1365 if folding_ranges.is_empty() {
1366 return Ok(None);
1367 }
1368
1369 Ok(Some(folding_ranges))
1370 }
1371
1372 async fn code_action(&self, params: CodeActionParams) -> RpcResult<Option<CodeActionResponse>> {
1373 let actions = params
1374 .context
1375 .diagnostics
1376 .into_iter()
1377 .filter_map(|diagnostic| {
1378 let (suggestion, range) = diagnostic.data.as_ref().and_then(|data| {
1379 serde_json::from_value::<(Suggestion, tower_lsp::lsp_types::Range)>(data.clone()).ok()
1380 })?;
1381 let edit = TextEdit {
1382 range,
1383 new_text: suggestion.insert,
1384 };
1385 let changes = HashMap::from([(params.text_document.uri.clone(), vec![edit])]);
1386 Some(CodeActionOrCommand::CodeAction(CodeAction {
1387 title: suggestion.title,
1388 kind: Some(CodeActionKind::QUICKFIX),
1389 diagnostics: Some(vec![diagnostic]),
1390 edit: Some(WorkspaceEdit {
1391 changes: Some(changes),
1392 document_changes: None,
1393 change_annotations: None,
1394 }),
1395 command: None,
1396 is_preferred: Some(true),
1397 disabled: None,
1398 data: None,
1399 }))
1400 })
1401 .collect();
1402
1403 Ok(Some(actions))
1404 }
1405}
1406
1407pub fn get_completions_from_stdlib(
1409 stdlib: &crate::std::StdLib,
1410 kcl_std: &[DocData],
1411) -> Result<HashMap<String, CompletionItem>> {
1412 let mut completions = HashMap::new();
1413 let combined = stdlib.combined();
1414
1415 for internal_fn in combined.values() {
1416 completions.insert(internal_fn.name(), internal_fn.to_completion_item()?);
1417 }
1418
1419 for d in kcl_std {
1420 completions.insert(d.name().to_owned(), d.to_completion_item());
1421 }
1422
1423 let variable_kinds = VariableKind::to_completion_items();
1424 for variable_kind in variable_kinds {
1425 completions.insert(variable_kind.label.clone(), variable_kind);
1426 }
1427
1428 Ok(completions)
1429}
1430
1431pub fn get_signatures_from_stdlib(stdlib: &crate::std::StdLib, kcl_std: &[DocData]) -> HashMap<String, SignatureHelp> {
1433 let mut signatures = HashMap::new();
1434 let combined = stdlib.combined();
1435
1436 for internal_fn in combined.values() {
1437 signatures.insert(internal_fn.name(), internal_fn.to_signature_help());
1438 }
1439
1440 for d in kcl_std {
1441 if let Some(sig) = d.to_signature_help() {
1442 signatures.insert(d.name().to_owned(), sig);
1443 }
1444 }
1445
1446 signatures
1447}
1448
1449fn position_to_char_index(position: Position, code: &str) -> usize {
1451 let mut char_position = 0;
1453 for (index, line) in code.lines().enumerate() {
1454 if index == position.line as usize {
1455 char_position += position.character as usize;
1456 break;
1457 } else {
1458 char_position += line.len() + 1;
1459 }
1460 }
1461
1462 char_position
1463}