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, 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, crate::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 = crate::Program {
332 ast,
333 original_file_contents: params.text.clone(),
334 };
335
336 let ast_changed = match self.ast_map.get(&filename) {
338 Some(old_ast) => {
339 *old_ast.ast != *ast.ast
341 }
342 None => true,
343 };
344
345 if !ast_changed && !force && !had_diagnostics {
346 return;
348 }
349
350 if ast_changed {
351 self.ast_map.insert(params.uri.to_string(), ast.clone());
352 self.symbols_map.insert(
354 params.uri.to_string(),
355 ast.ast.get_lsp_symbols(¶ms.text).unwrap_or_default(),
356 );
357
358 self.update_semantic_tokens(&tokens, ¶ms).await;
360
361 let discovered_findings = ast.lint_all().into_iter().flatten().collect::<Vec<_>>();
362 self.add_to_diagnostics(¶ms, &discovered_findings, false).await;
363 }
364
365 if self.can_execute().await || self.executor_ctx().await.is_none() {
367 self.client
370 .send_notification::<custom_notifications::AstUpdated>(ast.ast.clone())
371 .await;
372 }
373
374 if self.execute(¶ms, &ast).await.is_err() {
378 return;
379 }
380
381 self.clear_diagnostics_map(¶ms.uri, Some(DiagnosticSeverity::ERROR))
383 .await;
384 }
385}
386
387impl Backend {
388 pub async fn can_execute(&self) -> bool {
389 *self.can_execute.read().await
390 }
391
392 pub async fn executor_ctx(&self) -> tokio::sync::RwLockReadGuard<'_, Option<crate::execution::ExecutorContext>> {
393 self.executor_ctx.read().await
394 }
395
396 async fn update_semantic_tokens(&self, tokens: &TokenStream, params: &TextDocumentItem) {
397 let mut semantic_tokens = vec![];
399 let mut last_position = Position::new(0, 0);
400 for token in tokens.as_slice() {
401 let Ok(token_type) = SemanticTokenType::try_from(token.token_type) else {
402 continue;
405 };
406
407 let mut token_type_index = match self.get_semantic_token_type_index(&token_type) {
408 Some(index) => index,
409 None => {
412 self.client
413 .log_message(
414 MessageType::ERROR,
415 format!("token type `{:?}` not accounted for", token_type),
416 )
417 .await;
418 continue;
419 }
420 };
421
422 let source_range: SourceRange = token.into();
423 let position = source_range.start_to_lsp_position(¶ms.text);
424
425 let token_modifiers_bitset = if let Some(ast) = self.ast_map.get(params.uri.as_str()) {
428 let token_index = Arc::new(Mutex::new(token_type_index));
429 let modifier_index: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
430 crate::walk::walk(&ast.ast, |node: crate::walk::Node| {
431 let Ok(node_range): Result<SourceRange, _> = (&node).try_into() else {
432 return Ok(true);
433 };
434
435 if !node_range.contains(source_range.start()) {
436 return Ok(true);
437 }
438
439 let get_modifier = |modifier: Vec<SemanticTokenModifier>| -> Result<bool> {
440 let mut mods = modifier_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
441 let Some(token_modifier_index) = self.get_semantic_token_modifier_index(modifier) else {
442 return Ok(true);
443 };
444 if *mods == 0 {
445 *mods = token_modifier_index;
446 } else {
447 *mods |= token_modifier_index;
448 }
449 Ok(false)
450 };
451
452 match node {
453 crate::walk::Node::TagDeclarator(_) => {
454 return get_modifier(vec![
455 SemanticTokenModifier::DEFINITION,
456 SemanticTokenModifier::STATIC,
457 ]);
458 }
459 crate::walk::Node::VariableDeclarator(variable) => {
460 let sr: SourceRange = (&variable.id).into();
461 if sr.contains(source_range.start()) {
462 if let Expr::FunctionExpression(_) = &variable.init {
463 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
464 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::FUNCTION) {
465 Some(index) => index,
466 None => token_type_index,
467 };
468 }
469
470 return get_modifier(vec![
471 SemanticTokenModifier::DECLARATION,
472 SemanticTokenModifier::READONLY,
473 ]);
474 }
475 }
476 crate::walk::Node::Parameter(_) => {
477 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
478 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PARAMETER) {
479 Some(index) => index,
480 None => token_type_index,
481 };
482 return Ok(false);
483 }
484 crate::walk::Node::MemberExpression(member_expression) => {
485 let sr: SourceRange = (&member_expression.property).into();
486 if sr.contains(source_range.start()) {
487 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
488 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PROPERTY) {
489 Some(index) => index,
490 None => token_type_index,
491 };
492 return Ok(false);
493 }
494 }
495 crate::walk::Node::ObjectProperty(object_property) => {
496 let sr: SourceRange = (&object_property.key).into();
497 if sr.contains(source_range.start()) {
498 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
499 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PROPERTY) {
500 Some(index) => index,
501 None => token_type_index,
502 };
503 }
504 return get_modifier(vec![SemanticTokenModifier::DECLARATION]);
505 }
506 crate::walk::Node::CallExpression(call_expr) => {
507 let sr: SourceRange = (&call_expr.callee).into();
508 if sr.contains(source_range.start()) {
509 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
510 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::FUNCTION) {
511 Some(index) => index,
512 None => token_type_index,
513 };
514
515 if self.stdlib_completions.contains_key(&call_expr.callee.name) {
516 return get_modifier(vec![SemanticTokenModifier::DEFAULT_LIBRARY]);
518 }
519
520 return Ok(false);
521 }
522 }
523 _ => {}
524 }
525 Ok(true)
526 })
527 .unwrap_or_default();
528
529 let t = if let Ok(guard) = token_index.lock() { *guard } else { 0 };
530 token_type_index = t;
531
532 let m = if let Ok(guard) = modifier_index.lock() {
533 *guard
534 } else {
535 0
536 };
537 m
538 } else {
539 0
540 };
541
542 if let Some(line) = params.text.lines().nth(position.line as usize) {
546 if line.len() == position.character as usize {
547 let semantic_token = SemanticToken {
550 delta_line: position.line - last_position.line + 1,
551 delta_start: 0,
552 length: (token.end - token.start) as u32,
553 token_type: token_type_index,
554 token_modifiers_bitset,
555 };
556
557 semantic_tokens.push(semantic_token);
558
559 last_position = Position::new(position.line + 1, 0);
560 continue;
561 }
562 }
563
564 let semantic_token = SemanticToken {
565 delta_line: position.line - last_position.line,
566 delta_start: if position.line != last_position.line {
567 position.character
568 } else {
569 position.character - last_position.character
570 },
571 length: (token.end - token.start) as u32,
572 token_type: token_type_index,
573 token_modifiers_bitset,
574 };
575
576 semantic_tokens.push(semantic_token);
577
578 last_position = position;
579 }
580 self.semantic_tokens_map.insert(params.uri.to_string(), semantic_tokens);
581 }
582
583 async fn clear_diagnostics_map(&self, uri: &url::Url, severity: Option<DiagnosticSeverity>) {
584 let Some(mut items) = self.diagnostics_map.get_mut(uri.as_str()) else {
585 return;
586 };
587
588 if let Some(severity) = severity {
590 items.retain(|x| x.severity != Some(severity));
591 } else {
592 items.clear();
593 }
594
595 if items.is_empty() {
596 #[cfg(not(target_arch = "wasm32"))]
597 {
598 self.client.publish_diagnostics(uri.clone(), items.clone(), None).await;
599 }
600
601 drop(items);
603
604 self.diagnostics_map.remove(uri.as_str());
605 } else {
606 #[cfg(not(target_arch = "wasm32"))]
609 {
610 self.client.publish_diagnostics(uri.clone(), items.clone(), None).await;
611 }
612 }
613 }
614
615 async fn add_to_diagnostics<DiagT: IntoDiagnostic + std::fmt::Debug>(
616 &self,
617 params: &TextDocumentItem,
618 diagnostics: &[DiagT],
619 clear_all_before_add: bool,
620 ) {
621 if diagnostics.is_empty() {
622 return;
623 }
624
625 if clear_all_before_add {
626 self.clear_diagnostics_map(¶ms.uri, None).await;
627 } else if diagnostics.iter().all(|x| x.severity() == DiagnosticSeverity::ERROR) {
628 self.clear_diagnostics_map(¶ms.uri, Some(DiagnosticSeverity::ERROR))
632 .await;
633 } else if diagnostics
634 .iter()
635 .all(|x| x.severity() == DiagnosticSeverity::INFORMATION)
636 {
637 self.clear_diagnostics_map(¶ms.uri, Some(DiagnosticSeverity::INFORMATION))
640 .await;
641 }
642
643 let mut items = if let Some(items) = self.diagnostics_map.get(params.uri.as_str()) {
644 items.clone()
646 } else {
647 vec![]
648 };
649
650 for diagnostic in diagnostics {
651 let lsp_d = diagnostic.to_lsp_diagnostics(¶ms.text);
652 for d in lsp_d {
654 if !items.iter().any(|x| x == &d) {
655 items.push(d);
656 }
657 }
658 }
659
660 self.diagnostics_map.insert(params.uri.to_string(), items.clone());
661
662 self.client.publish_diagnostics(params.uri.clone(), items, None).await;
663 }
664
665 async fn execute(&self, params: &TextDocumentItem, ast: &Program) -> Result<()> {
666 if !self.can_execute().await {
668 return Ok(());
669 }
670
671 let ctx = self.executor_ctx().await;
673 let Some(ref executor_ctx) = *ctx else {
674 return Ok(());
675 };
676
677 if !self.is_initialized().await {
678 return Ok(());
680 }
681
682 match executor_ctx.run_with_caching(ast.clone()).await {
683 Err(err) => {
684 self.add_to_diagnostics(params, &[err], false).await;
685
686 Err(anyhow::anyhow!("failed to execute code"))
689 }
690 Ok(_) => Ok(()),
691 }
692 }
693
694 pub fn get_semantic_token_type_index(&self, token_type: &SemanticTokenType) -> Option<u32> {
695 SEMANTIC_TOKEN_TYPES
696 .iter()
697 .position(|x| *x == *token_type)
698 .map(|y| y as u32)
699 }
700
701 pub fn get_semantic_token_modifier_index(&self, token_types: Vec<SemanticTokenModifier>) -> Option<u32> {
702 if token_types.is_empty() {
703 return None;
704 }
705
706 let mut modifier = None;
707 for token_type in token_types {
708 if let Some(index) = SEMANTIC_TOKEN_MODIFIERS
709 .iter()
710 .position(|x| *x == token_type)
711 .map(|y| y as u32)
712 {
713 modifier = match modifier {
714 Some(modifier) => Some(modifier | index),
715 None => Some(index),
716 };
717 }
718 }
719 modifier
720 }
721
722 pub async fn create_zip(&self) -> Result<Vec<u8>> {
723 let mut buf = vec![];
725 let mut zip = zip::ZipWriter::new(std::io::Cursor::new(&mut buf));
726 for code in self.code_map.iter() {
727 let entry = code.key();
728 let value = code.value();
729 let file_name = entry.replace("file://", "").to_string();
730
731 let options = zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
732 zip.start_file(file_name, options)?;
733 zip.write_all(value)?;
734 }
735 zip.finish()?;
738
739 Ok(buf)
740 }
741
742 pub async fn send_telemetry(&self) -> Result<()> {
743 let user = self
745 .zoo_client
746 .users()
747 .get_self()
748 .await
749 .map_err(|e| anyhow::anyhow!(e.to_string()))?;
750
751 let mut hasher = sha2::Sha256::new();
754 hasher.update(user.id);
756 let result = hasher.finalize();
758 let user_id_hash = format!("{:x}", result);
760
761 let workspace_folders = self.workspace_folders().await;
764 let project_names: Vec<&str> = workspace_folders.iter().map(|v| v.name.as_str()).collect::<Vec<_>>();
765 let project_name = project_names
767 .first()
768 .ok_or_else(|| anyhow::anyhow!("no project names"))?
769 .to_string();
770
771 self.zoo_client
773 .meta()
774 .create_event(
775 vec![kittycad::types::multipart::Attachment {
776 name: "attachment".to_string(),
778 filename: Some("attachment.zip".to_string()),
779 content_type: Some("application/x-zip".to_string()),
780 data: self.create_zip().await?,
781 }],
782 &kittycad::types::Event {
783 attachment_uri: None,
785 created_at: chrono::Utc::now(),
786 event_type: kittycad::types::ModelingAppEventType::SuccessfulCompileBeforeClose,
787 last_compiled_at: Some(chrono::Utc::now()),
788 project_description: None,
790 project_name,
791 source_id: uuid::Uuid::from_str("70178592-dfca-47b3-bd2d-6fce2bcaee04").unwrap(),
794 type_: kittycad::types::Type::ModelingAppEvent,
795 user_id: user_id_hash,
796 },
797 )
798 .await
799 .map_err(|e| anyhow::anyhow!(e.to_string()))?;
800
801 Ok(())
802 }
803
804 pub async fn update_units(
805 &self,
806 params: custom_notifications::UpdateUnitsParams,
807 ) -> RpcResult<Option<custom_notifications::UpdateUnitsResponse>> {
808 {
809 let mut ctx = self.executor_ctx.write().await;
810 let Some(ref mut executor_ctx) = *ctx else {
812 self.client
813 .log_message(MessageType::ERROR, "no executor context set to update units for")
814 .await;
815 return Ok(None);
816 };
817
818 self.client
819 .log_message(MessageType::INFO, format!("update units: {:?}", params))
820 .await;
821
822 if executor_ctx.settings.units == params.units
823 && !self.has_diagnostics(params.text_document.uri.as_ref()).await
824 {
825 return Ok(None);
827 }
828
829 executor_ctx.update_units(params.units);
831 }
832 let new_params = TextDocumentItem {
836 uri: params.text_document.uri.clone(),
837 text: std::mem::take(&mut params.text.to_string()),
838 version: Default::default(),
839 language_id: Default::default(),
840 };
841
842 self.inner_on_change(new_params, true).await;
844
845 if self.has_diagnostics(params.text_document.uri.as_ref()).await {
848 return Ok(None);
849 }
850
851 Ok(Some(custom_notifications::UpdateUnitsResponse {}))
852 }
853
854 pub async fn update_can_execute(
855 &self,
856 params: custom_notifications::UpdateCanExecuteParams,
857 ) -> RpcResult<custom_notifications::UpdateCanExecuteResponse> {
858 let mut can_execute = self.can_execute.write().await;
859
860 if *can_execute == params.can_execute {
861 return Ok(custom_notifications::UpdateCanExecuteResponse {});
862 }
863
864 *can_execute = params.can_execute;
865
866 Ok(custom_notifications::UpdateCanExecuteResponse {})
867 }
868}
869
870#[tower_lsp::async_trait]
871impl LanguageServer for Backend {
872 async fn initialize(&self, params: InitializeParams) -> RpcResult<InitializeResult> {
873 self.client
874 .log_message(MessageType::INFO, format!("initialize: {:?}", params))
875 .await;
876
877 Ok(InitializeResult {
878 capabilities: ServerCapabilities {
879 completion_provider: Some(CompletionOptions {
880 resolve_provider: Some(false),
881 trigger_characters: Some(vec![".".to_string()]),
882 work_done_progress_options: Default::default(),
883 all_commit_characters: None,
884 ..Default::default()
885 }),
886 diagnostic_provider: Some(DiagnosticServerCapabilities::Options(DiagnosticOptions {
887 ..Default::default()
888 })),
889 document_formatting_provider: Some(OneOf::Left(true)),
890 folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
891 hover_provider: Some(HoverProviderCapability::Simple(true)),
892 inlay_hint_provider: Some(OneOf::Left(true)),
893 rename_provider: Some(OneOf::Left(true)),
894 semantic_tokens_provider: Some(SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(
895 SemanticTokensRegistrationOptions {
896 text_document_registration_options: {
897 TextDocumentRegistrationOptions {
898 document_selector: Some(vec![DocumentFilter {
899 language: Some("kcl".to_string()),
900 scheme: Some("file".to_string()),
901 pattern: None,
902 }]),
903 }
904 },
905 semantic_tokens_options: SemanticTokensOptions {
906 work_done_progress_options: WorkDoneProgressOptions::default(),
907 legend: SemanticTokensLegend {
908 token_types: SEMANTIC_TOKEN_TYPES.to_vec(),
909 token_modifiers: SEMANTIC_TOKEN_MODIFIERS.to_vec(),
910 },
911 range: Some(false),
912 full: Some(SemanticTokensFullOptions::Bool(true)),
913 },
914 static_registration_options: StaticRegistrationOptions::default(),
915 },
916 )),
917 signature_help_provider: Some(SignatureHelpOptions {
918 trigger_characters: None,
919 retrigger_characters: None,
920 ..Default::default()
921 }),
922 text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
923 open_close: Some(true),
924 change: Some(TextDocumentSyncKind::FULL),
925 ..Default::default()
926 })),
927 workspace: Some(WorkspaceServerCapabilities {
928 workspace_folders: Some(WorkspaceFoldersServerCapabilities {
929 supported: Some(true),
930 change_notifications: Some(OneOf::Left(true)),
931 }),
932 file_operations: None,
933 }),
934 ..Default::default()
935 },
936 ..Default::default()
937 })
938 }
939
940 async fn initialized(&self, params: InitializedParams) {
941 self.do_initialized(params).await
942 }
943
944 async fn shutdown(&self) -> RpcResult<()> {
945 self.do_shutdown().await
946 }
947
948 async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
949 self.do_did_change_workspace_folders(params).await
950 }
951
952 async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
953 self.do_did_change_configuration(params).await
954 }
955
956 async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
957 self.do_did_change_watched_files(params).await
958 }
959
960 async fn did_create_files(&self, params: CreateFilesParams) {
961 self.do_did_create_files(params).await
962 }
963
964 async fn did_rename_files(&self, params: RenameFilesParams) {
965 self.do_did_rename_files(params).await
966 }
967
968 async fn did_delete_files(&self, params: DeleteFilesParams) {
969 self.do_did_delete_files(params).await
970 }
971
972 async fn did_open(&self, params: DidOpenTextDocumentParams) {
973 self.do_did_open(params).await
974 }
975
976 async fn did_change(&self, params: DidChangeTextDocumentParams) {
977 self.do_did_change(params).await;
978 }
979
980 async fn did_save(&self, params: DidSaveTextDocumentParams) {
981 self.do_did_save(params).await
982 }
983
984 async fn did_close(&self, params: DidCloseTextDocumentParams) {
985 self.do_did_close(params).await;
986
987 if !self.can_send_telemetry {
990 return;
991 }
992
993 #[cfg(target_arch = "wasm32")]
995 {
996 let be = self.clone();
997 wasm_bindgen_futures::spawn_local(async move {
998 if let Err(err) = be.send_telemetry().await {
999 be.client
1000 .log_message(MessageType::WARNING, format!("failed to send telemetry: {}", err))
1001 .await;
1002 }
1003 });
1004 }
1005 #[cfg(not(target_arch = "wasm32"))]
1006 if let Err(err) = self.send_telemetry().await {
1007 self.client
1008 .log_message(MessageType::WARNING, format!("failed to send telemetry: {}", err))
1009 .await;
1010 }
1011 }
1012
1013 async fn hover(&self, params: HoverParams) -> RpcResult<Option<Hover>> {
1014 let filename = params.text_document_position_params.text_document.uri.to_string();
1015
1016 let Some(current_code) = self.code_map.get(&filename) else {
1017 return Ok(None);
1018 };
1019 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1020 return Ok(None);
1021 };
1022
1023 let pos = position_to_char_index(params.text_document_position_params.position, current_code);
1024
1025 let Some(ast) = self.ast_map.get(&filename) else {
1027 return Ok(None);
1028 };
1029
1030 let Some(hover) = ast.ast.get_hover_value_for_position(pos, current_code) else {
1031 return Ok(None);
1032 };
1033
1034 match hover {
1035 crate::parsing::ast::types::Hover::Function { name, range } => {
1036 let Some(completion) = self.stdlib_completions.get(&name) else {
1038 return Ok(None);
1039 };
1040 let Some(docs) = &completion.documentation else {
1041 return Ok(None);
1042 };
1043
1044 let docs = match docs {
1045 Documentation::String(docs) => docs,
1046 Documentation::MarkupContent(MarkupContent { value, .. }) => value,
1047 };
1048
1049 let Some(label_details) = &completion.label_details else {
1050 return Ok(None);
1051 };
1052
1053 Ok(Some(Hover {
1054 contents: HoverContents::Markup(MarkupContent {
1055 kind: MarkupKind::Markdown,
1056 value: format!(
1057 "```\n{}{}\n```\n\n{}",
1058 name,
1059 if let Some(detail) = &label_details.detail {
1060 detail
1061 } else {
1062 ""
1063 },
1064 docs
1065 ),
1066 }),
1067 range: Some(range),
1068 }))
1069 }
1070 crate::parsing::ast::types::Hover::Signature { .. } => Ok(None),
1071 crate::parsing::ast::types::Hover::Comment { value, range } => Ok(Some(Hover {
1072 contents: HoverContents::Markup(MarkupContent {
1073 kind: MarkupKind::Markdown,
1074 value,
1075 }),
1076 range: Some(range),
1077 })),
1078 }
1079 }
1080
1081 async fn completion(&self, params: CompletionParams) -> RpcResult<Option<CompletionResponse>> {
1082 let mut completions = vec![CompletionItem {
1083 label: PIPE_OPERATOR.to_string(),
1084 label_details: None,
1085 kind: Some(CompletionItemKind::OPERATOR),
1086 detail: Some("A pipe operator.".to_string()),
1087 documentation: Some(Documentation::MarkupContent(MarkupContent {
1088 kind: MarkupKind::Markdown,
1089 value: "A pipe operator.".to_string(),
1090 })),
1091 deprecated: Some(false),
1092 preselect: None,
1093 sort_text: None,
1094 filter_text: None,
1095 insert_text: Some("|> ".to_string()),
1096 insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
1097 insert_text_mode: None,
1098 text_edit: None,
1099 additional_text_edits: None,
1100 command: None,
1101 commit_characters: None,
1102 data: None,
1103 tags: None,
1104 }];
1105
1106 let Some(current_code) = self
1108 .code_map
1109 .get(params.text_document_position.text_document.uri.as_ref())
1110 else {
1111 return Ok(Some(CompletionResponse::Array(completions)));
1112 };
1113 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1114 return Ok(Some(CompletionResponse::Array(completions)));
1115 };
1116
1117 if let Some(line) = current_code
1119 .lines()
1120 .nth(params.text_document_position.position.line as usize)
1121 {
1122 let char_pos = params.text_document_position.position.character as usize;
1123 if char_pos <= line.len() {
1124 let line_prefix = &line[..char_pos];
1125 let last_word = line_prefix
1127 .split(|c: char| c.is_whitespace() || c.is_ascii_punctuation())
1128 .last()
1129 .unwrap_or("");
1130
1131 if !last_word.is_empty() && last_word.chars().next().unwrap().is_ascii_digit() {
1133 return Ok(None);
1134 }
1135 }
1136 }
1137
1138 completions.extend(self.stdlib_completions.values().cloned());
1139
1140 let Some(ast) = self
1142 .ast_map
1143 .get(params.text_document_position.text_document.uri.as_ref())
1144 else {
1145 return Ok(Some(CompletionResponse::Array(completions)));
1146 };
1147
1148 let Some(current_code) = self
1149 .code_map
1150 .get(params.text_document_position.text_document.uri.as_ref())
1151 else {
1152 return Ok(Some(CompletionResponse::Array(completions)));
1153 };
1154 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1155 return Ok(Some(CompletionResponse::Array(completions)));
1156 };
1157
1158 let position = position_to_char_index(params.text_document_position.position, current_code);
1159 if ast.ast.get_non_code_meta_for_position(position).is_some() {
1160 return Ok(None);
1162 }
1163
1164 let Ok(variables) = ast.ast.completion_items() else {
1166 return Ok(Some(CompletionResponse::Array(completions)));
1167 };
1168
1169 completions.extend(variables);
1171
1172 Ok(Some(CompletionResponse::Array(completions)))
1173 }
1174
1175 async fn diagnostic(&self, params: DocumentDiagnosticParams) -> RpcResult<DocumentDiagnosticReportResult> {
1176 let filename = params.text_document.uri.to_string();
1177
1178 let Some(items) = self.diagnostics_map.get(&filename) else {
1180 return Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1182 RelatedFullDocumentDiagnosticReport {
1183 related_documents: None,
1184 full_document_diagnostic_report: FullDocumentDiagnosticReport {
1185 result_id: None,
1186 items: vec![],
1187 },
1188 },
1189 )));
1190 };
1191
1192 Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1193 RelatedFullDocumentDiagnosticReport {
1194 related_documents: None,
1195 full_document_diagnostic_report: FullDocumentDiagnosticReport {
1196 result_id: None,
1197 items: items.clone(),
1198 },
1199 },
1200 )))
1201 }
1202
1203 async fn signature_help(&self, params: SignatureHelpParams) -> RpcResult<Option<SignatureHelp>> {
1204 let filename = params.text_document_position_params.text_document.uri.to_string();
1205
1206 let Some(current_code) = self.code_map.get(&filename) else {
1207 return Ok(None);
1208 };
1209 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1210 return Ok(None);
1211 };
1212
1213 let pos = position_to_char_index(params.text_document_position_params.position, current_code);
1214
1215 let Some(ast) = self.ast_map.get(&filename) else {
1217 return Ok(None);
1218 };
1219
1220 let Some(value) = ast.ast.get_expr_for_position(pos) else {
1221 return Ok(None);
1222 };
1223
1224 let Some(hover) = value.get_hover_value_for_position(pos, current_code) else {
1225 return Ok(None);
1226 };
1227
1228 match hover {
1229 crate::parsing::ast::types::Hover::Function { name, range: _ } => {
1230 let Some(signature) = self.stdlib_signatures.get(&name) else {
1232 return Ok(None);
1233 };
1234
1235 Ok(Some(signature.clone()))
1236 }
1237 crate::parsing::ast::types::Hover::Signature {
1238 name,
1239 parameter_index,
1240 range: _,
1241 } => {
1242 let Some(signature) = self.stdlib_signatures.get(&name) else {
1243 return Ok(None);
1244 };
1245
1246 let mut signature = signature.clone();
1247
1248 signature.active_parameter = Some(parameter_index);
1249
1250 Ok(Some(signature))
1251 }
1252 crate::parsing::ast::types::Hover::Comment { value: _, range: _ } => {
1253 return Ok(None);
1254 }
1255 }
1256 }
1257
1258 async fn inlay_hint(&self, _params: InlayHintParams) -> RpcResult<Option<Vec<InlayHint>>> {
1259 Ok(None)
1262 }
1263
1264 async fn semantic_tokens_full(&self, params: SemanticTokensParams) -> RpcResult<Option<SemanticTokensResult>> {
1265 let filename = params.text_document.uri.to_string();
1266
1267 let Some(semantic_tokens) = self.semantic_tokens_map.get(&filename) else {
1268 return Ok(None);
1269 };
1270
1271 Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
1272 result_id: None,
1273 data: semantic_tokens.clone(),
1274 })))
1275 }
1276
1277 async fn document_symbol(&self, params: DocumentSymbolParams) -> RpcResult<Option<DocumentSymbolResponse>> {
1278 let filename = params.text_document.uri.to_string();
1279
1280 let Some(symbols) = self.symbols_map.get(&filename) else {
1281 return Ok(None);
1282 };
1283
1284 Ok(Some(DocumentSymbolResponse::Nested(symbols.clone())))
1285 }
1286
1287 async fn formatting(&self, params: DocumentFormattingParams) -> RpcResult<Option<Vec<TextEdit>>> {
1288 let filename = params.text_document.uri.to_string();
1289
1290 let Some(current_code) = self.code_map.get(&filename) else {
1291 return Ok(None);
1292 };
1293 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1294 return Ok(None);
1295 };
1296
1297 let module_id = ModuleId::default();
1301 let Ok(ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
1302 return Ok(None);
1303 };
1304 let recast = ast.recast(
1306 &crate::parsing::ast::types::FormatOptions {
1307 tab_size: params.options.tab_size as usize,
1308 insert_final_newline: params.options.insert_final_newline.unwrap_or(false),
1309 use_tabs: !params.options.insert_spaces,
1310 },
1311 0,
1312 );
1313 let source_range = SourceRange::new(0, current_code.len(), module_id);
1314 let range = source_range.to_lsp_range(current_code);
1315 Ok(Some(vec![TextEdit {
1316 new_text: recast,
1317 range,
1318 }]))
1319 }
1320
1321 async fn rename(&self, params: RenameParams) -> RpcResult<Option<WorkspaceEdit>> {
1322 let filename = params.text_document_position.text_document.uri.to_string();
1323
1324 let Some(current_code) = self.code_map.get(&filename) else {
1325 return Ok(None);
1326 };
1327 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1328 return Ok(None);
1329 };
1330
1331 let module_id = ModuleId::default();
1335 let Ok(mut ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
1336 return Ok(None);
1337 };
1338
1339 let pos = position_to_char_index(params.text_document_position.position, current_code);
1341 ast.rename_symbol(¶ms.new_name, pos);
1343 let recast = ast.recast(&Default::default(), 0);
1345 let source_range = SourceRange::new(0, current_code.len() - 1, module_id);
1346 let range = source_range.to_lsp_range(current_code);
1347 Ok(Some(WorkspaceEdit {
1348 changes: Some(HashMap::from([(
1349 params.text_document_position.text_document.uri,
1350 vec![TextEdit {
1351 new_text: recast,
1352 range,
1353 }],
1354 )])),
1355 document_changes: None,
1356 change_annotations: None,
1357 }))
1358 }
1359
1360 async fn folding_range(&self, params: FoldingRangeParams) -> RpcResult<Option<Vec<FoldingRange>>> {
1361 let filename = params.text_document.uri.to_string();
1362
1363 let Some(ast) = self.ast_map.get(&filename) else {
1365 return Ok(None);
1366 };
1367
1368 let folding_ranges = ast.ast.get_lsp_folding_ranges();
1370
1371 if folding_ranges.is_empty() {
1372 return Ok(None);
1373 }
1374
1375 Ok(Some(folding_ranges))
1376 }
1377
1378 async fn code_action(&self, params: CodeActionParams) -> RpcResult<Option<CodeActionResponse>> {
1379 let actions = params
1380 .context
1381 .diagnostics
1382 .into_iter()
1383 .filter_map(|diagnostic| {
1384 let (suggestion, range) = diagnostic.data.as_ref().and_then(|data| {
1385 serde_json::from_value::<(Suggestion, tower_lsp::lsp_types::Range)>(data.clone()).ok()
1386 })?;
1387 let edit = TextEdit {
1388 range,
1389 new_text: suggestion.insert,
1390 };
1391 let changes = HashMap::from([(params.text_document.uri.clone(), vec![edit])]);
1392 Some(CodeActionOrCommand::CodeAction(CodeAction {
1393 title: suggestion.title,
1394 kind: Some(CodeActionKind::QUICKFIX),
1395 diagnostics: Some(vec![diagnostic]),
1396 edit: Some(WorkspaceEdit {
1397 changes: Some(changes),
1398 document_changes: None,
1399 change_annotations: None,
1400 }),
1401 command: None,
1402 is_preferred: Some(true),
1403 disabled: None,
1404 data: None,
1405 }))
1406 })
1407 .collect();
1408
1409 Ok(Some(actions))
1410 }
1411}
1412
1413pub fn get_completions_from_stdlib(
1415 stdlib: &crate::std::StdLib,
1416 kcl_std: &[DocData],
1417) -> Result<HashMap<String, CompletionItem>> {
1418 let mut completions = HashMap::new();
1419 let combined = stdlib.combined();
1420
1421 for internal_fn in combined.values() {
1422 completions.insert(internal_fn.name(), internal_fn.to_completion_item()?);
1423 }
1424
1425 for d in kcl_std {
1426 completions.insert(d.name().to_owned(), d.to_completion_item());
1427 }
1428
1429 let variable_kinds = VariableKind::to_completion_items();
1430 for variable_kind in variable_kinds {
1431 completions.insert(variable_kind.label.clone(), variable_kind);
1432 }
1433
1434 Ok(completions)
1435}
1436
1437pub fn get_signatures_from_stdlib(stdlib: &crate::std::StdLib, kcl_std: &[DocData]) -> HashMap<String, SignatureHelp> {
1439 let mut signatures = HashMap::new();
1440 let combined = stdlib.combined();
1441
1442 for internal_fn in combined.values() {
1443 signatures.insert(internal_fn.name(), internal_fn.to_signature_help());
1444 }
1445
1446 for d in kcl_std {
1447 if let Some(sig) = d.to_signature_help() {
1448 signatures.insert(d.name().to_owned(), sig);
1449 }
1450 }
1451
1452 signatures
1453}
1454
1455fn position_to_char_index(position: Position, code: &str) -> usize {
1457 let mut char_position = 0;
1459 for (index, line) in code.lines().enumerate() {
1460 if index == position.line as usize {
1461 char_position += position.character as usize;
1462 break;
1463 } else {
1464 char_position += line.len() + 1;
1465 }
1466 }
1467
1468 char_position
1469}