1#![allow(dead_code)]
3
4use std::{
5 collections::HashMap,
6 io::Write,
7 str::FromStr,
8 sync::{Arc, Mutex},
9};
10
11use anyhow::Result;
12#[cfg(feature = "cli")]
13use clap::Parser;
14use dashmap::DashMap;
15use sha2::Digest;
16use tokio::sync::RwLock;
17use tower_lsp::{
18 jsonrpc::Result as RpcResult,
19 lsp_types::{
20 CodeAction, CodeActionKind, CodeActionOptions, CodeActionOrCommand, CodeActionParams,
21 CodeActionProviderCapability, CodeActionResponse, ColorInformation, ColorPresentation, ColorPresentationParams,
22 ColorProviderCapability, CompletionItem, CompletionItemKind, CompletionOptions, CompletionParams,
23 CompletionResponse, CreateFilesParams, DeleteFilesParams, Diagnostic, DiagnosticOptions,
24 DiagnosticServerCapabilities, DiagnosticSeverity, DidChangeConfigurationParams, DidChangeTextDocumentParams,
25 DidChangeWatchedFilesParams, DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams,
26 DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentColorParams, DocumentDiagnosticParams,
27 DocumentDiagnosticReport, DocumentDiagnosticReportResult, DocumentFilter, DocumentFormattingParams,
28 DocumentSymbol, DocumentSymbolParams, DocumentSymbolResponse, Documentation, FoldingRange, FoldingRangeParams,
29 FoldingRangeProviderCapability, FullDocumentDiagnosticReport, Hover as LspHover, HoverContents, HoverParams,
30 HoverProviderCapability, InitializeParams, InitializeResult, InitializedParams, InlayHint, InlayHintParams,
31 InsertTextFormat, MarkupContent, MarkupKind, MessageType, OneOf, Position, PrepareRenameResponse,
32 RelatedFullDocumentDiagnosticReport, RenameFilesParams, RenameParams, SemanticToken, SemanticTokenModifier,
33 SemanticTokenType, SemanticTokens, SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions,
34 SemanticTokensParams, SemanticTokensRegistrationOptions, SemanticTokensResult,
35 SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelp, SignatureHelpOptions, SignatureHelpParams,
36 StaticRegistrationOptions, TextDocumentItem, TextDocumentPositionParams, TextDocumentRegistrationOptions,
37 TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, TextEdit, WorkDoneProgressOptions,
38 WorkspaceEdit, WorkspaceFolder, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
39 },
40 Client, LanguageServer,
41};
42
43use crate::{
44 docs::kcl_doc::ModData,
45 errors::LspSuggestion,
46 exec::KclValue,
47 execution::{cache, kcl_value::FunctionSource},
48 lsp::{
49 backend::Backend as _,
50 kcl::hover::{Hover, HoverOpts},
51 util::IntoDiagnostic,
52 },
53 parsing::{
54 ast::types::{Expr, VariableKind},
55 token::TokenStream,
56 PIPE_OPERATOR,
57 },
58 ModuleId, Program, SourceRange,
59};
60
61pub mod custom_notifications;
62mod hover;
63
64const SEMANTIC_TOKEN_TYPES: [SemanticTokenType; 10] = [
65 SemanticTokenType::NUMBER,
66 SemanticTokenType::VARIABLE,
67 SemanticTokenType::KEYWORD,
68 SemanticTokenType::TYPE,
69 SemanticTokenType::STRING,
70 SemanticTokenType::OPERATOR,
71 SemanticTokenType::COMMENT,
72 SemanticTokenType::FUNCTION,
73 SemanticTokenType::PARAMETER,
74 SemanticTokenType::PROPERTY,
75];
76
77const SEMANTIC_TOKEN_MODIFIERS: [SemanticTokenModifier; 5] = [
78 SemanticTokenModifier::DECLARATION,
79 SemanticTokenModifier::DEFINITION,
80 SemanticTokenModifier::DEFAULT_LIBRARY,
81 SemanticTokenModifier::READONLY,
82 SemanticTokenModifier::STATIC,
83];
84
85#[derive(Clone, Debug)]
87#[cfg_attr(feature = "cli", derive(Parser))]
88pub struct Server {
89 #[cfg_attr(feature = "cli", clap(long, default_value = "8080"))]
91 pub socket: i32,
92
93 #[cfg_attr(feature = "cli", clap(short, long, default_value = "false"))]
95 pub stdio: bool,
96}
97
98#[derive(Clone)]
100pub struct Backend {
101 pub client: Client,
103 pub fs: Arc<crate::fs::FileManager>,
105 pub workspace_folders: DashMap<String, WorkspaceFolder>,
107 pub stdlib_completions: HashMap<String, CompletionItem>,
109 pub stdlib_signatures: HashMap<String, SignatureHelp>,
111 pub stdlib_args: HashMap<String, HashMap<String, String>>,
113 pub(super) token_map: DashMap<String, TokenStream>,
115 pub ast_map: DashMap<String, crate::Program>,
117 pub code_map: DashMap<String, Vec<u8>>,
119 pub diagnostics_map: DashMap<String, Vec<Diagnostic>>,
121 pub symbols_map: DashMap<String, Vec<DocumentSymbol>>,
123 pub semantic_tokens_map: DashMap<String, Vec<SemanticToken>>,
125 pub zoo_client: kittycad::Client,
127 pub can_send_telemetry: bool,
129 pub executor_ctx: Arc<RwLock<Option<crate::execution::ExecutorContext>>>,
131 pub can_execute: Arc<RwLock<bool>>,
133
134 pub is_initialized: Arc<RwLock<bool>>,
135}
136
137impl Backend {
138 #[cfg(target_arch = "wasm32")]
139 pub fn new_wasm(
140 client: Client,
141 executor_ctx: Option<crate::execution::ExecutorContext>,
142 fs: crate::fs::wasm::FileSystemManager,
143 zoo_client: kittycad::Client,
144 can_send_telemetry: bool,
145 ) -> Result<Self, String> {
146 Self::with_file_manager(
147 client,
148 executor_ctx,
149 crate::fs::FileManager::new(fs),
150 zoo_client,
151 can_send_telemetry,
152 )
153 }
154
155 #[cfg(not(target_arch = "wasm32"))]
156 pub fn new(
157 client: Client,
158 executor_ctx: Option<crate::execution::ExecutorContext>,
159 zoo_client: kittycad::Client,
160 can_send_telemetry: bool,
161 ) -> Result<Self, String> {
162 Self::with_file_manager(
163 client,
164 executor_ctx,
165 crate::fs::FileManager::new(),
166 zoo_client,
167 can_send_telemetry,
168 )
169 }
170
171 fn with_file_manager(
172 client: Client,
173 executor_ctx: Option<crate::execution::ExecutorContext>,
174 fs: crate::fs::FileManager,
175 zoo_client: kittycad::Client,
176 can_send_telemetry: bool,
177 ) -> Result<Self, String> {
178 let kcl_std = crate::docs::kcl_doc::walk_prelude();
179 let stdlib_completions = get_completions_from_stdlib(&kcl_std).map_err(|e| e.to_string())?;
180 let stdlib_signatures = get_signatures_from_stdlib(&kcl_std);
181 let stdlib_args = get_arg_maps_from_stdlib(&kcl_std);
182
183 Ok(Self {
184 client,
185 fs: Arc::new(fs),
186 stdlib_completions,
187 stdlib_signatures,
188 stdlib_args,
189 zoo_client,
190 can_send_telemetry,
191 can_execute: Arc::new(RwLock::new(executor_ctx.is_some())),
192 executor_ctx: Arc::new(RwLock::new(executor_ctx)),
193 workspace_folders: Default::default(),
194 token_map: Default::default(),
195 ast_map: Default::default(),
196 code_map: Default::default(),
197 diagnostics_map: Default::default(),
198 symbols_map: Default::default(),
199 semantic_tokens_map: Default::default(),
200 is_initialized: Default::default(),
201 })
202 }
203
204 fn remove_from_ast_maps(&self, filename: &str) {
205 self.ast_map.remove(filename);
206 self.symbols_map.remove(filename);
207 }
208}
209
210#[async_trait::async_trait]
212impl crate::lsp::backend::Backend for Backend {
213 fn client(&self) -> &Client {
214 &self.client
215 }
216
217 fn fs(&self) -> &Arc<crate::fs::FileManager> {
218 &self.fs
219 }
220
221 async fn is_initialized(&self) -> bool {
222 *self.is_initialized.read().await
223 }
224
225 async fn set_is_initialized(&self, is_initialized: bool) {
226 *self.is_initialized.write().await = is_initialized;
227 }
228
229 async fn workspace_folders(&self) -> Vec<WorkspaceFolder> {
230 self.workspace_folders.iter().map(|i| i.clone()).collect()
232 }
233
234 async fn add_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
235 for folder in folders {
236 self.workspace_folders.insert(folder.name.to_string(), folder);
237 }
238 }
239
240 async fn remove_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
241 for folder in folders {
242 self.workspace_folders.remove(&folder.name);
243 }
244 }
245
246 fn code_map(&self) -> &DashMap<String, Vec<u8>> {
247 &self.code_map
248 }
249
250 async fn insert_code_map(&self, uri: String, text: Vec<u8>) {
251 self.code_map.insert(uri, text);
252 }
253
254 async fn remove_from_code_map(&self, uri: String) -> Option<Vec<u8>> {
255 self.code_map.remove(&uri).map(|x| x.1)
256 }
257
258 async fn clear_code_state(&self) {
259 self.code_map.clear();
260 self.token_map.clear();
261 self.ast_map.clear();
262 self.diagnostics_map.clear();
263 self.symbols_map.clear();
264 self.semantic_tokens_map.clear();
265 }
266
267 fn current_diagnostics_map(&self) -> &DashMap<String, Vec<Diagnostic>> {
268 &self.diagnostics_map
269 }
270
271 async fn inner_on_change(&self, params: TextDocumentItem, force: bool) {
272 if force {
273 crate::bust_cache().await;
274 }
275
276 let filename = params.uri.to_string();
277 let module_id = ModuleId::default();
281 let tokens = match crate::parsing::token::lex(¶ms.text, module_id) {
282 Ok(tokens) => tokens,
283 Err(err) => {
284 self.add_to_diagnostics(¶ms, &[err], true).await;
285 self.token_map.remove(&filename);
286 self.remove_from_ast_maps(&filename);
287 self.semantic_tokens_map.remove(&filename);
288 return;
289 }
290 };
291
292 let tokens_changed = if let Some(previous_tokens) = self.token_map.get(&filename) {
294 *previous_tokens != tokens
295 } else {
296 true
297 };
298
299 let had_diagnostics = self.has_diagnostics(params.uri.as_ref()).await;
300
301 if !tokens_changed && !force && !had_diagnostics {
303 return;
305 }
306
307 if tokens_changed {
308 self.token_map.insert(params.uri.to_string(), tokens.clone());
310 self.update_semantic_tokens(&tokens, ¶ms).await;
312 }
313
314 let (ast, errs) = match crate::parsing::parse_tokens(tokens.clone()).0 {
317 Ok(result) => result,
318 Err(err) => {
319 self.add_to_diagnostics(¶ms, &[err], true).await;
320 self.remove_from_ast_maps(&filename);
321 return;
322 }
323 };
324
325 self.add_to_diagnostics(¶ms, &errs, true).await;
326
327 if errs.iter().any(|e| e.severity == crate::errors::Severity::Fatal) {
328 self.remove_from_ast_maps(&filename);
329 return;
330 }
331
332 let Some(mut ast) = ast else {
333 self.remove_from_ast_maps(&filename);
334 return;
335 };
336
337 ast.compute_digest();
341
342 let ast = crate::Program {
344 ast,
345 original_file_contents: params.text.clone(),
346 };
347
348 let ast_changed = match self.ast_map.get(&filename) {
350 Some(old_ast) => {
351 *old_ast.ast != *ast.ast
353 }
354 None => true,
355 };
356
357 if !ast_changed && !force && !had_diagnostics {
358 return;
360 }
361
362 if ast_changed {
363 self.ast_map.insert(params.uri.to_string(), ast.clone());
364 self.symbols_map.insert(
366 params.uri.to_string(),
367 ast.ast.get_lsp_symbols(¶ms.text).unwrap_or_default(),
368 );
369
370 self.update_semantic_tokens(&tokens, ¶ms).await;
372
373 let discovered_findings = ast.lint_all().into_iter().flatten().collect::<Vec<_>>();
374 self.add_to_diagnostics(¶ms, &discovered_findings, false).await;
375 }
376
377 if self.can_execute().await || self.executor_ctx().await.is_none() {
379 self.client
382 .send_notification::<custom_notifications::AstUpdated>(ast.ast.clone())
383 .await;
384 }
385
386 if self.execute(¶ms, &ast).await.is_err() {
390 return;
391 }
392
393 self.clear_diagnostics_map(¶ms.uri, Some(DiagnosticSeverity::ERROR))
395 .await;
396 }
397}
398
399impl Backend {
400 pub async fn can_execute(&self) -> bool {
401 *self.can_execute.read().await
402 }
403
404 pub async fn executor_ctx(&self) -> tokio::sync::RwLockReadGuard<'_, Option<crate::execution::ExecutorContext>> {
405 self.executor_ctx.read().await
406 }
407
408 async fn update_semantic_tokens(&self, tokens: &TokenStream, params: &TextDocumentItem) {
409 let mut semantic_tokens = vec![];
411 let mut last_position = Position::new(0, 0);
412 for token in tokens.as_slice() {
413 let Ok(token_type) = SemanticTokenType::try_from(token.token_type) else {
414 continue;
417 };
418
419 let mut token_type_index = match self.get_semantic_token_type_index(&token_type) {
420 Some(index) => index,
421 None => {
424 self.client
425 .log_message(
426 MessageType::ERROR,
427 format!("token type `{:?}` not accounted for", token_type),
428 )
429 .await;
430 continue;
431 }
432 };
433
434 let source_range: SourceRange = token.into();
435 let position = source_range.start_to_lsp_position(¶ms.text);
436
437 let token_modifiers_bitset = if let Some(ast) = self.ast_map.get(params.uri.as_str()) {
440 let token_index = Arc::new(Mutex::new(token_type_index));
441 let modifier_index: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
442 crate::walk::walk(&ast.ast, |node: crate::walk::Node| {
443 let Ok(node_range): Result<SourceRange, _> = (&node).try_into() else {
444 return Ok(true);
445 };
446
447 if !node_range.contains(source_range.start()) {
448 return Ok(true);
449 }
450
451 let get_modifier = |modifier: Vec<SemanticTokenModifier>| -> Result<bool> {
452 let mut mods = modifier_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
453 let Some(token_modifier_index) = self.get_semantic_token_modifier_index(modifier) else {
454 return Ok(true);
455 };
456 if *mods == 0 {
457 *mods = token_modifier_index;
458 } else {
459 *mods |= token_modifier_index;
460 }
461 Ok(false)
462 };
463
464 match node {
465 crate::walk::Node::TagDeclarator(_) => {
466 return get_modifier(vec![
467 SemanticTokenModifier::DEFINITION,
468 SemanticTokenModifier::STATIC,
469 ]);
470 }
471 crate::walk::Node::VariableDeclarator(variable) => {
472 let sr: SourceRange = (&variable.id).into();
473 if sr.contains(source_range.start()) {
474 if let Expr::FunctionExpression(_) = &variable.init {
475 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
476 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::FUNCTION) {
477 Some(index) => index,
478 None => token_type_index,
479 };
480 }
481
482 return get_modifier(vec![
483 SemanticTokenModifier::DECLARATION,
484 SemanticTokenModifier::READONLY,
485 ]);
486 }
487 }
488 crate::walk::Node::Parameter(_) => {
489 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
490 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PARAMETER) {
491 Some(index) => index,
492 None => token_type_index,
493 };
494 return Ok(false);
495 }
496 crate::walk::Node::MemberExpression(member_expression) => {
497 let sr: SourceRange = (&member_expression.property).into();
498 if sr.contains(source_range.start()) {
499 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
500 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PROPERTY) {
501 Some(index) => index,
502 None => token_type_index,
503 };
504 return Ok(false);
505 }
506 }
507 crate::walk::Node::ObjectProperty(object_property) => {
508 let sr: SourceRange = (&object_property.key).into();
509 if sr.contains(source_range.start()) {
510 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
511 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PROPERTY) {
512 Some(index) => index,
513 None => token_type_index,
514 };
515 }
516 return get_modifier(vec![SemanticTokenModifier::DECLARATION]);
517 }
518 crate::walk::Node::CallExpressionKw(call_expr) => {
519 let sr: SourceRange = (&call_expr.callee).into();
520 if sr.contains(source_range.start()) {
521 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
522 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::FUNCTION) {
523 Some(index) => index,
524 None => token_type_index,
525 };
526
527 if self.stdlib_completions.contains_key(&call_expr.callee.name.name) {
528 return get_modifier(vec![SemanticTokenModifier::DEFAULT_LIBRARY]);
530 }
531
532 return Ok(false);
533 }
534 }
535 _ => {}
536 }
537 Ok(true)
538 })
539 .unwrap_or_default();
540
541 let t = if let Ok(guard) = token_index.lock() { *guard } else { 0 };
542 token_type_index = t;
543
544 let m = if let Ok(guard) = modifier_index.lock() {
545 *guard
546 } else {
547 0
548 };
549 m
550 } else {
551 0
552 };
553
554 if let Some(line) = params.text.lines().nth(position.line as usize) {
558 if line.len() == position.character as usize {
559 let semantic_token = SemanticToken {
562 delta_line: position.line - last_position.line + 1,
563 delta_start: 0,
564 length: (token.end - token.start) as u32,
565 token_type: token_type_index,
566 token_modifiers_bitset,
567 };
568
569 semantic_tokens.push(semantic_token);
570
571 last_position = Position::new(position.line + 1, 0);
572 continue;
573 }
574 }
575
576 let semantic_token = SemanticToken {
577 delta_line: position.line - last_position.line,
578 delta_start: if position.line != last_position.line {
579 position.character
580 } else {
581 position.character - last_position.character
582 },
583 length: (token.end - token.start) as u32,
584 token_type: token_type_index,
585 token_modifiers_bitset,
586 };
587
588 semantic_tokens.push(semantic_token);
589
590 last_position = position;
591 }
592 self.semantic_tokens_map.insert(params.uri.to_string(), semantic_tokens);
593 }
594
595 async fn clear_diagnostics_map(&self, uri: &url::Url, severity: Option<DiagnosticSeverity>) {
596 let Some(mut items) = self.diagnostics_map.get_mut(uri.as_str()) else {
597 return;
598 };
599
600 if let Some(severity) = severity {
602 items.retain(|x| x.severity != Some(severity));
603 } else {
604 items.clear();
605 }
606
607 if items.is_empty() {
608 #[cfg(not(target_arch = "wasm32"))]
609 {
610 self.client.publish_diagnostics(uri.clone(), items.clone(), None).await;
611 }
612
613 drop(items);
615
616 self.diagnostics_map.remove(uri.as_str());
617 } else {
618 #[cfg(not(target_arch = "wasm32"))]
621 {
622 self.client.publish_diagnostics(uri.clone(), items.clone(), None).await;
623 }
624 }
625 }
626
627 async fn add_to_diagnostics<DiagT: IntoDiagnostic + std::fmt::Debug>(
628 &self,
629 params: &TextDocumentItem,
630 diagnostics: &[DiagT],
631 clear_all_before_add: bool,
632 ) {
633 if diagnostics.is_empty() {
634 return;
635 }
636
637 if clear_all_before_add {
638 self.clear_diagnostics_map(¶ms.uri, None).await;
639 } else if diagnostics.iter().all(|x| x.severity() == DiagnosticSeverity::ERROR) {
640 self.clear_diagnostics_map(¶ms.uri, Some(DiagnosticSeverity::ERROR))
644 .await;
645 } else if diagnostics
646 .iter()
647 .all(|x| x.severity() == DiagnosticSeverity::INFORMATION)
648 {
649 self.clear_diagnostics_map(¶ms.uri, Some(DiagnosticSeverity::INFORMATION))
652 .await;
653 }
654
655 let mut items = if let Some(items) = self.diagnostics_map.get(params.uri.as_str()) {
656 items.clone()
658 } else {
659 vec![]
660 };
661
662 for diagnostic in diagnostics {
663 let lsp_d = diagnostic.to_lsp_diagnostics(¶ms.text);
664 for d in lsp_d {
666 if !items.iter().any(|x| x == &d) {
667 items.push(d);
668 }
669 }
670 }
671
672 self.diagnostics_map.insert(params.uri.to_string(), items.clone());
673
674 self.client.publish_diagnostics(params.uri.clone(), items, None).await;
675 }
676
677 async fn execute(&self, params: &TextDocumentItem, ast: &Program) -> Result<()> {
678 if !self.can_execute().await {
680 return Ok(());
681 }
682
683 let ctx = self.executor_ctx().await;
685 let Some(ref executor_ctx) = *ctx else {
686 return Ok(());
687 };
688
689 if !self.is_initialized().await {
690 return Ok(());
692 }
693
694 match executor_ctx.run_with_caching(ast.clone()).await {
695 Err(err) => {
696 self.add_to_diagnostics(params, &[err], false).await;
697
698 Err(anyhow::anyhow!("failed to execute code"))
701 }
702 Ok(_) => Ok(()),
703 }
704 }
705
706 pub fn get_semantic_token_type_index(&self, token_type: &SemanticTokenType) -> Option<u32> {
707 SEMANTIC_TOKEN_TYPES
708 .iter()
709 .position(|x| *x == *token_type)
710 .map(|y| y as u32)
711 }
712
713 pub fn get_semantic_token_modifier_index(&self, token_types: Vec<SemanticTokenModifier>) -> Option<u32> {
714 if token_types.is_empty() {
715 return None;
716 }
717
718 let mut modifier = None;
719 for token_type in token_types {
720 if let Some(index) = SEMANTIC_TOKEN_MODIFIERS
721 .iter()
722 .position(|x| *x == token_type)
723 .map(|y| y as u32)
724 {
725 modifier = match modifier {
726 Some(modifier) => Some(modifier | index),
727 None => Some(index),
728 };
729 }
730 }
731 modifier
732 }
733
734 pub async fn create_zip(&self) -> Result<Vec<u8>> {
735 let mut buf = vec![];
737 let mut zip = zip::ZipWriter::new(std::io::Cursor::new(&mut buf));
738 for code in self.code_map.iter() {
739 let entry = code.key();
740 let value = code.value();
741 let file_name = entry.replace("file://", "").to_string();
742
743 let options = zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
744 zip.start_file(file_name, options)?;
745 zip.write_all(value)?;
746 }
747 zip.finish()?;
750
751 Ok(buf)
752 }
753
754 pub async fn send_telemetry(&self) -> Result<()> {
755 let user = self
757 .zoo_client
758 .users()
759 .get_self()
760 .await
761 .map_err(|e| anyhow::anyhow!(e.to_string()))?;
762
763 let mut hasher = sha2::Sha256::new();
766 hasher.update(user.id);
768 let result = hasher.finalize();
770 let user_id_hash = format!("{:x}", result);
772
773 let workspace_folders = self.workspace_folders().await;
776 let project_names: Vec<&str> = workspace_folders.iter().map(|v| v.name.as_str()).collect::<Vec<_>>();
777 let project_name = project_names
779 .first()
780 .ok_or_else(|| anyhow::anyhow!("no project names"))?
781 .to_string();
782
783 self.zoo_client
785 .meta()
786 .create_event(
787 vec![kittycad::types::multipart::Attachment {
788 name: "attachment".to_string(),
790 filepath: Some("attachment.zip".into()),
791 content_type: Some("application/x-zip".to_string()),
792 data: self.create_zip().await?,
793 }],
794 &kittycad::types::Event {
795 attachment_uri: None,
797 created_at: chrono::Utc::now(),
798 event_type: kittycad::types::ModelingAppEventType::SuccessfulCompileBeforeClose,
799 last_compiled_at: Some(chrono::Utc::now()),
800 project_description: None,
802 project_name,
803 source_id: uuid::Uuid::from_str("70178592-dfca-47b3-bd2d-6fce2bcaee04").unwrap(),
806 type_: kittycad::types::Type::ModelingAppEvent,
807 user_id: user_id_hash,
808 },
809 )
810 .await
811 .map_err(|e| anyhow::anyhow!(e.to_string()))?;
812
813 Ok(())
814 }
815
816 pub async fn update_can_execute(
817 &self,
818 params: custom_notifications::UpdateCanExecuteParams,
819 ) -> RpcResult<custom_notifications::UpdateCanExecuteResponse> {
820 let mut can_execute = self.can_execute.write().await;
821
822 if *can_execute == params.can_execute {
823 return Ok(custom_notifications::UpdateCanExecuteResponse {});
824 }
825
826 *can_execute = params.can_execute;
827
828 Ok(custom_notifications::UpdateCanExecuteResponse {})
829 }
830
831 pub fn inner_prepare_rename(
833 &self,
834 params: &TextDocumentPositionParams,
835 new_name: &str,
836 ) -> RpcResult<Option<(String, String)>> {
837 let filename = params.text_document.uri.to_string();
838
839 let Some(current_code) = self.code_map.get(&filename) else {
840 return Ok(None);
841 };
842 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
843 return Ok(None);
844 };
845
846 let module_id = ModuleId::default();
850 let Ok(mut ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
851 return Ok(None);
852 };
853
854 let pos = position_to_char_index(params.position, current_code);
856 ast.rename_symbol(new_name, pos);
858 let recast = ast.recast(&Default::default(), 0);
860
861 Ok(Some((current_code.to_string(), recast)))
862 }
863}
864
865#[tower_lsp::async_trait]
866impl LanguageServer for Backend {
867 async fn initialize(&self, params: InitializeParams) -> RpcResult<InitializeResult> {
868 self.client
869 .log_message(MessageType::INFO, format!("initialize: {:?}", params))
870 .await;
871
872 Ok(InitializeResult {
873 capabilities: ServerCapabilities {
874 color_provider: Some(ColorProviderCapability::Simple(true)),
875 code_action_provider: Some(CodeActionProviderCapability::Options(CodeActionOptions {
876 code_action_kinds: Some(vec![CodeActionKind::QUICKFIX]),
877 resolve_provider: Some(false),
878 work_done_progress_options: WorkDoneProgressOptions::default(),
879 })),
880 completion_provider: Some(CompletionOptions {
881 resolve_provider: Some(false),
882 trigger_characters: Some(vec![".".to_string()]),
883 work_done_progress_options: Default::default(),
884 all_commit_characters: None,
885 ..Default::default()
886 }),
887 diagnostic_provider: Some(DiagnosticServerCapabilities::Options(DiagnosticOptions {
888 ..Default::default()
889 })),
890 document_formatting_provider: Some(OneOf::Left(true)),
891 folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
892 hover_provider: Some(HoverProviderCapability::Simple(true)),
893 inlay_hint_provider: Some(OneOf::Left(true)),
894 rename_provider: Some(OneOf::Left(true)),
895 semantic_tokens_provider: Some(SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(
896 SemanticTokensRegistrationOptions {
897 text_document_registration_options: {
898 TextDocumentRegistrationOptions {
899 document_selector: Some(vec![DocumentFilter {
900 language: Some("kcl".to_string()),
901 scheme: Some("file".to_string()),
902 pattern: None,
903 }]),
904 }
905 },
906 semantic_tokens_options: SemanticTokensOptions {
907 work_done_progress_options: WorkDoneProgressOptions::default(),
908 legend: SemanticTokensLegend {
909 token_types: SEMANTIC_TOKEN_TYPES.to_vec(),
910 token_modifiers: SEMANTIC_TOKEN_MODIFIERS.to_vec(),
911 },
912 range: Some(false),
913 full: Some(SemanticTokensFullOptions::Bool(true)),
914 },
915 static_registration_options: StaticRegistrationOptions::default(),
916 },
917 )),
918 signature_help_provider: Some(SignatureHelpOptions {
919 trigger_characters: None,
920 retrigger_characters: None,
921 ..Default::default()
922 }),
923 text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
924 open_close: Some(true),
925 change: Some(TextDocumentSyncKind::FULL),
926 ..Default::default()
927 })),
928 workspace: Some(WorkspaceServerCapabilities {
929 workspace_folders: Some(WorkspaceFoldersServerCapabilities {
930 supported: Some(true),
931 change_notifications: Some(OneOf::Left(true)),
932 }),
933 file_operations: None,
934 }),
935 ..Default::default()
936 },
937 ..Default::default()
938 })
939 }
940
941 async fn initialized(&self, params: InitializedParams) {
942 self.do_initialized(params).await
943 }
944
945 async fn shutdown(&self) -> RpcResult<()> {
946 self.do_shutdown().await
947 }
948
949 async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
950 self.do_did_change_workspace_folders(params).await
951 }
952
953 async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
954 self.do_did_change_configuration(params).await
955 }
956
957 async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
958 self.do_did_change_watched_files(params).await
959 }
960
961 async fn did_create_files(&self, params: CreateFilesParams) {
962 self.do_did_create_files(params).await
963 }
964
965 async fn did_rename_files(&self, params: RenameFilesParams) {
966 self.do_did_rename_files(params).await
967 }
968
969 async fn did_delete_files(&self, params: DeleteFilesParams) {
970 self.do_did_delete_files(params).await
971 }
972
973 async fn did_open(&self, params: DidOpenTextDocumentParams) {
974 self.do_did_open(params).await
975 }
976
977 async fn did_change(&self, params: DidChangeTextDocumentParams) {
978 self.do_did_change(params).await;
979 }
980
981 async fn did_save(&self, params: DidSaveTextDocumentParams) {
982 self.do_did_save(params).await
983 }
984
985 async fn did_close(&self, params: DidCloseTextDocumentParams) {
986 self.do_did_close(params).await;
987
988 if !self.can_send_telemetry {
991 return;
992 }
993
994 #[cfg(target_arch = "wasm32")]
996 {
997 let be = self.clone();
998 wasm_bindgen_futures::spawn_local(async move {
999 if let Err(err) = be.send_telemetry().await {
1000 be.client
1001 .log_message(MessageType::WARNING, format!("failed to send telemetry: {}", err))
1002 .await;
1003 }
1004 });
1005 }
1006 #[cfg(not(target_arch = "wasm32"))]
1007 if let Err(err) = self.send_telemetry().await {
1008 self.client
1009 .log_message(MessageType::WARNING, format!("failed to send telemetry: {}", err))
1010 .await;
1011 }
1012 }
1013
1014 async fn hover(&self, params: HoverParams) -> RpcResult<Option<LspHover>> {
1015 let filename = params.text_document_position_params.text_document.uri.to_string();
1016
1017 let Some(current_code) = self.code_map.get(&filename) else {
1018 return Ok(None);
1019 };
1020 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1021 return Ok(None);
1022 };
1023
1024 let pos = position_to_char_index(params.text_document_position_params.position, current_code);
1025
1026 let Some(ast) = self.ast_map.get(&filename) else {
1028 return Ok(None);
1029 };
1030
1031 let Some(hover) = ast
1032 .ast
1033 .get_hover_value_for_position(pos, current_code, &HoverOpts::default_for_hover())
1034 else {
1035 return Ok(None);
1036 };
1037
1038 match hover {
1039 Hover::Function { name, range } => {
1040 let (sig, docs) = if let Some(Some(result)) = with_cached_var(&name, |value| {
1041 match value {
1042 KclValue::Function {
1044 value: FunctionSource::User { ast, .. },
1045 ..
1046 } => {
1047 Some((ast.signature(), ""))
1049 }
1050 _ => None,
1051 }
1052 })
1053 .await
1054 {
1055 result
1056 } else {
1057 let Some(completion) = self.stdlib_completions.get(&name) else {
1059 return Ok(None);
1060 };
1061 let Some(docs) = &completion.documentation else {
1062 return Ok(None);
1063 };
1064
1065 let docs = match docs {
1066 Documentation::String(docs) => docs,
1067 Documentation::MarkupContent(MarkupContent { value, .. }) => value,
1068 };
1069
1070 let docs = if docs.len() > 320 {
1071 let end = docs.find("\n\n").or_else(|| docs.find("\n\r\n")).unwrap_or(320);
1072 &docs[..end]
1073 } else {
1074 &**docs
1075 };
1076
1077 let Some(label_details) = &completion.label_details else {
1078 return Ok(None);
1079 };
1080
1081 let sig = if let Some(detail) = &label_details.detail {
1082 detail.clone()
1083 } else {
1084 String::new()
1085 };
1086
1087 (sig, docs)
1088 };
1089
1090 Ok(Some(LspHover {
1091 contents: HoverContents::Markup(MarkupContent {
1092 kind: MarkupKind::Markdown,
1093 value: format!("```\n{}{}\n```\n\n{}", name, sig, docs),
1094 }),
1095 range: Some(range),
1096 }))
1097 }
1098 Hover::Type { name, range } => {
1099 let Some(completion) = self.stdlib_completions.get(&name) else {
1100 return Ok(None);
1101 };
1102 let Some(docs) = &completion.documentation else {
1103 return Ok(None);
1104 };
1105
1106 let docs = match docs {
1107 Documentation::String(docs) => docs,
1108 Documentation::MarkupContent(MarkupContent { value, .. }) => value,
1109 };
1110
1111 let docs = if docs.len() > 320 {
1112 let end = docs.find("\n\n").or_else(|| docs.find("\n\r\n")).unwrap_or(320);
1113 &docs[..end]
1114 } else {
1115 &**docs
1116 };
1117
1118 Ok(Some(LspHover {
1119 contents: HoverContents::Markup(MarkupContent {
1120 kind: MarkupKind::Markdown,
1121 value: format!("```\n{}\n```\n\n{}", name, docs),
1122 }),
1123 range: Some(range),
1124 }))
1125 }
1126 Hover::KwArg {
1127 name,
1128 callee_name,
1129 range,
1130 } => {
1131 let Some(arg_map) = self.stdlib_args.get(&callee_name) else {
1134 return Ok(None);
1135 };
1136
1137 let Some(tip) = arg_map.get(&name) else {
1138 return Ok(None);
1139 };
1140
1141 Ok(Some(LspHover {
1142 contents: HoverContents::Markup(MarkupContent {
1143 kind: MarkupKind::Markdown,
1144 value: tip.clone(),
1145 }),
1146 range: Some(range),
1147 }))
1148 }
1149 Hover::Variable {
1150 name,
1151 ty: Some(ty),
1152 range,
1153 } => Ok(Some(LspHover {
1154 contents: HoverContents::Markup(MarkupContent {
1155 kind: MarkupKind::Markdown,
1156 value: format!("```\n{}: {}\n```", name, ty),
1157 }),
1158 range: Some(range),
1159 })),
1160 Hover::Variable { name, ty: None, range } => Ok(with_cached_var(&name, |value| {
1161 let mut text: String = format!("```\n{}", name);
1162 if let Some(ty) = value.principal_type() {
1163 text.push_str(&format!(": {}", ty.human_friendly_type()));
1164 }
1165 if let Some(v) = value.value_str() {
1166 text.push_str(&format!(" = {}", v));
1167 }
1168 text.push_str("\n```");
1169
1170 LspHover {
1171 contents: HoverContents::Markup(MarkupContent {
1172 kind: MarkupKind::Markdown,
1173 value: text,
1174 }),
1175 range: Some(range),
1176 }
1177 })
1178 .await),
1179 Hover::Signature { .. } => Ok(None),
1180 Hover::Comment { value, range } => Ok(Some(LspHover {
1181 contents: HoverContents::Markup(MarkupContent {
1182 kind: MarkupKind::Markdown,
1183 value,
1184 }),
1185 range: Some(range),
1186 })),
1187 }
1188 }
1189
1190 async fn completion(&self, params: CompletionParams) -> RpcResult<Option<CompletionResponse>> {
1191 let mut completions = vec![CompletionItem {
1192 label: PIPE_OPERATOR.to_string(),
1193 label_details: None,
1194 kind: Some(CompletionItemKind::OPERATOR),
1195 detail: Some("A pipe operator.".to_string()),
1196 documentation: Some(Documentation::MarkupContent(MarkupContent {
1197 kind: MarkupKind::Markdown,
1198 value: "A pipe operator.".to_string(),
1199 })),
1200 deprecated: Some(false),
1201 preselect: None,
1202 sort_text: None,
1203 filter_text: None,
1204 insert_text: Some("|> ".to_string()),
1205 insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
1206 insert_text_mode: None,
1207 text_edit: None,
1208 additional_text_edits: None,
1209 command: None,
1210 commit_characters: None,
1211 data: None,
1212 tags: None,
1213 }];
1214
1215 let Some(current_code) = self
1217 .code_map
1218 .get(params.text_document_position.text_document.uri.as_ref())
1219 else {
1220 return Ok(Some(CompletionResponse::Array(completions)));
1221 };
1222 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1223 return Ok(Some(CompletionResponse::Array(completions)));
1224 };
1225
1226 if let Some(line) = current_code
1228 .lines()
1229 .nth(params.text_document_position.position.line as usize)
1230 {
1231 let char_pos = params.text_document_position.position.character as usize;
1232 if char_pos <= line.len() {
1233 let line_prefix = &line[..char_pos];
1234 let last_word = line_prefix
1236 .split(|c: char| c.is_whitespace() || c.is_ascii_punctuation())
1237 .next_back()
1238 .unwrap_or("");
1239
1240 if !last_word.is_empty() && last_word.chars().next().unwrap().is_ascii_digit() {
1242 return Ok(None);
1243 }
1244 }
1245 }
1246
1247 completions.extend(self.stdlib_completions.values().cloned());
1248
1249 let Some(ast) = self
1251 .ast_map
1252 .get(params.text_document_position.text_document.uri.as_ref())
1253 else {
1254 return Ok(Some(CompletionResponse::Array(completions)));
1255 };
1256
1257 let Some(current_code) = self
1258 .code_map
1259 .get(params.text_document_position.text_document.uri.as_ref())
1260 else {
1261 return Ok(Some(CompletionResponse::Array(completions)));
1262 };
1263 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1264 return Ok(Some(CompletionResponse::Array(completions)));
1265 };
1266
1267 let position = position_to_char_index(params.text_document_position.position, current_code);
1268 if ast.ast.in_comment(position) {
1269 return Ok(None);
1271 }
1272
1273 let Ok(variables) = ast.ast.completion_items(position) else {
1275 return Ok(Some(CompletionResponse::Array(completions)));
1276 };
1277
1278 completions.extend(variables);
1280
1281 Ok(Some(CompletionResponse::Array(completions)))
1282 }
1283
1284 async fn diagnostic(&self, params: DocumentDiagnosticParams) -> RpcResult<DocumentDiagnosticReportResult> {
1285 let filename = params.text_document.uri.to_string();
1286
1287 let Some(items) = self.diagnostics_map.get(&filename) else {
1289 return Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1291 RelatedFullDocumentDiagnosticReport {
1292 related_documents: None,
1293 full_document_diagnostic_report: FullDocumentDiagnosticReport {
1294 result_id: None,
1295 items: vec![],
1296 },
1297 },
1298 )));
1299 };
1300
1301 Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1302 RelatedFullDocumentDiagnosticReport {
1303 related_documents: None,
1304 full_document_diagnostic_report: FullDocumentDiagnosticReport {
1305 result_id: None,
1306 items: items.clone(),
1307 },
1308 },
1309 )))
1310 }
1311
1312 async fn signature_help(&self, params: SignatureHelpParams) -> RpcResult<Option<SignatureHelp>> {
1313 let filename = params.text_document_position_params.text_document.uri.to_string();
1314
1315 let Some(current_code) = self.code_map.get(&filename) else {
1316 return Ok(None);
1317 };
1318 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1319 return Ok(None);
1320 };
1321
1322 let pos = position_to_char_index(params.text_document_position_params.position, current_code);
1323
1324 let Some(ch) = current_code.chars().nth(pos) else {
1326 return Ok(None);
1327 };
1328
1329 let check_char = |ch: char| {
1330 if ch == '(' {
1334 let next_space = if ch != ' ' {
1338 if let Some(next_space) = current_code[pos..].find(' ') {
1339 pos + next_space
1340 } else if let Some(next_space) = current_code[pos..].find('(') {
1341 pos + next_space
1342 } else {
1343 pos
1344 }
1345 } else {
1346 pos
1347 };
1348 let p2 = std::cmp::max(pos, next_space);
1349
1350 let last_word = current_code[..p2].split_whitespace().last()?;
1351
1352 return self.stdlib_signatures.get(last_word);
1354 } else if ch == ',' {
1355 let last_paren = current_code[..pos].rfind('(')?;
1360 let last_word = current_code[..last_paren].split_whitespace().last()?;
1362 return self.stdlib_signatures.get(last_word);
1364 }
1365
1366 None
1367 };
1368
1369 if let Some(signature) = check_char(ch) {
1370 return Ok(Some(signature.clone()));
1371 }
1372
1373 if let Some(context) = params.context {
1375 if let Some(character) = context.trigger_character {
1376 for character in character.chars() {
1377 if character == '(' || character == ',' {
1379 if let Some(signature) = check_char(character) {
1380 return Ok(Some(signature.clone()));
1381 }
1382 }
1383 }
1384 }
1385 }
1386
1387 let Some(ast) = self.ast_map.get(&filename) else {
1389 return Ok(None);
1390 };
1391
1392 let Some(value) = ast.ast.get_expr_for_position(pos) else {
1393 return Ok(None);
1394 };
1395
1396 let Some(hover) =
1397 value.get_hover_value_for_position(pos, current_code, &HoverOpts::default_for_signature_help())
1398 else {
1399 return Ok(None);
1400 };
1401
1402 match hover {
1403 Hover::Function { name, range: _ } => {
1404 let Some(signature) = self.stdlib_signatures.get(&name) else {
1406 return Ok(None);
1407 };
1408
1409 Ok(Some(signature.clone()))
1410 }
1411 Hover::Signature {
1412 name,
1413 parameter_index,
1414 range: _,
1415 } => {
1416 let Some(signature) = self.stdlib_signatures.get(&name) else {
1417 return Ok(None);
1418 };
1419
1420 let mut signature = signature.clone();
1421
1422 signature.active_parameter = Some(parameter_index);
1423
1424 Ok(Some(signature))
1425 }
1426 _ => {
1427 return Ok(None);
1428 }
1429 }
1430 }
1431
1432 async fn inlay_hint(&self, _params: InlayHintParams) -> RpcResult<Option<Vec<InlayHint>>> {
1433 Ok(None)
1436 }
1437
1438 async fn semantic_tokens_full(&self, params: SemanticTokensParams) -> RpcResult<Option<SemanticTokensResult>> {
1439 let filename = params.text_document.uri.to_string();
1440
1441 let Some(semantic_tokens) = self.semantic_tokens_map.get(&filename) else {
1442 return Ok(None);
1443 };
1444
1445 Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
1446 result_id: None,
1447 data: semantic_tokens.clone(),
1448 })))
1449 }
1450
1451 async fn document_symbol(&self, params: DocumentSymbolParams) -> RpcResult<Option<DocumentSymbolResponse>> {
1452 let filename = params.text_document.uri.to_string();
1453
1454 let Some(symbols) = self.symbols_map.get(&filename) else {
1455 return Ok(None);
1456 };
1457
1458 Ok(Some(DocumentSymbolResponse::Nested(symbols.clone())))
1459 }
1460
1461 async fn formatting(&self, params: DocumentFormattingParams) -> RpcResult<Option<Vec<TextEdit>>> {
1462 let filename = params.text_document.uri.to_string();
1463
1464 let Some(current_code) = self.code_map.get(&filename) else {
1465 return Ok(None);
1466 };
1467 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1468 return Ok(None);
1469 };
1470
1471 let module_id = ModuleId::default();
1475 let Ok(ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
1476 return Ok(None);
1477 };
1478 let recast = ast.recast(
1480 &crate::parsing::ast::types::FormatOptions {
1481 tab_size: params.options.tab_size as usize,
1482 insert_final_newline: params.options.insert_final_newline.unwrap_or(false),
1483 use_tabs: !params.options.insert_spaces,
1484 },
1485 0,
1486 );
1487 let source_range = SourceRange::new(0, current_code.len(), module_id);
1488 let range = source_range.to_lsp_range(current_code);
1489 Ok(Some(vec![TextEdit {
1490 new_text: recast,
1491 range,
1492 }]))
1493 }
1494
1495 async fn rename(&self, params: RenameParams) -> RpcResult<Option<WorkspaceEdit>> {
1496 let Some((current_code, new_code)) =
1497 self.inner_prepare_rename(¶ms.text_document_position, ¶ms.new_name)?
1498 else {
1499 return Ok(None);
1500 };
1501
1502 let source_range = SourceRange::new(0, current_code.len(), ModuleId::default());
1503 let range = source_range.to_lsp_range(¤t_code);
1504 Ok(Some(WorkspaceEdit {
1505 changes: Some(HashMap::from([(
1506 params.text_document_position.text_document.uri,
1507 vec![TextEdit {
1508 new_text: new_code,
1509 range,
1510 }],
1511 )])),
1512 document_changes: None,
1513 change_annotations: None,
1514 }))
1515 }
1516
1517 async fn prepare_rename(&self, params: TextDocumentPositionParams) -> RpcResult<Option<PrepareRenameResponse>> {
1518 if self
1519 .inner_prepare_rename(¶ms, "someNameNoOneInTheirRightMindWouldEverUseForTesting")?
1520 .is_none()
1521 {
1522 return Ok(None);
1523 }
1524
1525 Ok(Some(PrepareRenameResponse::DefaultBehavior { default_behavior: true }))
1527 }
1528
1529 async fn folding_range(&self, params: FoldingRangeParams) -> RpcResult<Option<Vec<FoldingRange>>> {
1530 let filename = params.text_document.uri.to_string();
1531
1532 let Some(ast) = self.ast_map.get(&filename) else {
1534 return Ok(None);
1535 };
1536
1537 let folding_ranges = ast.ast.get_lsp_folding_ranges();
1539
1540 if folding_ranges.is_empty() {
1541 return Ok(None);
1542 }
1543
1544 Ok(Some(folding_ranges))
1545 }
1546
1547 async fn code_action(&self, params: CodeActionParams) -> RpcResult<Option<CodeActionResponse>> {
1548 let actions = params
1549 .context
1550 .diagnostics
1551 .into_iter()
1552 .filter_map(|diagnostic| {
1553 let (suggestion, range) = diagnostic
1554 .data
1555 .as_ref()
1556 .and_then(|data| serde_json::from_value::<LspSuggestion>(data.clone()).ok())?;
1557 let edit = TextEdit {
1558 range,
1559 new_text: suggestion.insert,
1560 };
1561 let changes = HashMap::from([(params.text_document.uri.clone(), vec![edit])]);
1562
1563 Some(CodeActionOrCommand::CodeAction(CodeAction {
1566 title: suggestion.title,
1567 kind: Some(CodeActionKind::QUICKFIX),
1568 diagnostics: Some(vec![diagnostic]),
1569 edit: Some(WorkspaceEdit {
1570 changes: Some(changes),
1571 document_changes: None,
1572 change_annotations: None,
1573 }),
1574 command: None,
1575 is_preferred: Some(true),
1576 disabled: None,
1577 data: None,
1578 }))
1579 })
1580 .collect();
1581
1582 Ok(Some(actions))
1583 }
1584
1585 async fn document_color(&self, params: DocumentColorParams) -> RpcResult<Vec<ColorInformation>> {
1586 let filename = params.text_document.uri.to_string();
1587
1588 let Some(current_code) = self.code_map.get(&filename) else {
1589 return Ok(vec![]);
1590 };
1591 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1592 return Ok(vec![]);
1593 };
1594
1595 let Some(ast) = self.ast_map.get(&filename) else {
1597 return Ok(vec![]);
1598 };
1599
1600 let Ok(colors) = ast.ast.document_color(current_code) else {
1602 return Ok(vec![]);
1603 };
1604
1605 Ok(colors)
1606 }
1607
1608 async fn color_presentation(&self, params: ColorPresentationParams) -> RpcResult<Vec<ColorPresentation>> {
1609 let filename = params.text_document.uri.to_string();
1610
1611 let Some(current_code) = self.code_map.get(&filename) else {
1612 return Ok(vec![]);
1613 };
1614 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1615 return Ok(vec![]);
1616 };
1617
1618 let Some(ast) = self.ast_map.get(&filename) else {
1620 return Ok(vec![]);
1621 };
1622
1623 let pos_start = position_to_char_index(params.range.start, current_code);
1624 let pos_end = position_to_char_index(params.range.end, current_code);
1625
1626 let Ok(Some(presentation)) = ast.ast.color_presentation(¶ms.color, pos_start, pos_end) else {
1628 return Ok(vec![]);
1629 };
1630
1631 Ok(vec![presentation])
1632 }
1633}
1634
1635pub fn get_completions_from_stdlib(kcl_std: &ModData) -> Result<HashMap<String, CompletionItem>> {
1637 let mut completions = HashMap::new();
1638
1639 for d in kcl_std.all_docs() {
1640 if let Some(ci) = d.to_completion_item() {
1641 completions.insert(d.name().to_owned(), ci);
1642 }
1643 }
1644
1645 let variable_kinds = VariableKind::to_completion_items();
1646 for variable_kind in variable_kinds {
1647 completions.insert(variable_kind.label.clone(), variable_kind);
1648 }
1649
1650 Ok(completions)
1651}
1652
1653pub fn get_signatures_from_stdlib(kcl_std: &ModData) -> HashMap<String, SignatureHelp> {
1655 let mut signatures = HashMap::new();
1656
1657 for d in kcl_std.all_docs() {
1658 if let Some(sig) = d.to_signature_help() {
1659 signatures.insert(d.name().to_owned(), sig);
1660 }
1661 }
1662
1663 signatures
1664}
1665
1666pub fn get_arg_maps_from_stdlib(kcl_std: &ModData) -> HashMap<String, HashMap<String, String>> {
1668 let mut result = HashMap::new();
1669
1670 for d in kcl_std.all_docs() {
1671 let crate::docs::kcl_doc::DocData::Fn(f) = d else {
1672 continue;
1673 };
1674 let arg_map: HashMap<String, String> = f
1675 .args
1676 .iter()
1677 .map(|data| {
1678 let mut tip = "```\n".to_owned();
1679 tip.push_str(&data.to_string());
1680 tip.push_str("\n```");
1681 if let Some(docs) = &data.docs {
1682 tip.push_str("\n\n");
1683 tip.push_str(docs);
1684 }
1685 (data.name.clone(), tip)
1686 })
1687 .collect();
1688 if !arg_map.is_empty() {
1689 result.insert(f.name.clone(), arg_map);
1690 }
1691 }
1692
1693 result
1694}
1695
1696fn position_to_char_index(position: Position, code: &str) -> usize {
1698 let mut char_position = 0;
1700 for (index, line) in code.lines().enumerate() {
1701 if index == position.line as usize {
1702 char_position += position.character as usize;
1703 break;
1704 } else {
1705 char_position += line.len() + 1;
1706 }
1707 }
1708
1709 std::cmp::min(char_position, code.len() - 1)
1710}
1711
1712async fn with_cached_var<T>(name: &str, f: impl Fn(&KclValue) -> T) -> Option<T> {
1713 let mem = cache::read_old_memory().await?;
1714 let value = mem.0.get(name, SourceRange::default()).ok()?;
1715
1716 Some(f(value))
1717}
1718
1719#[cfg(test)]
1720mod tests {
1721 use pretty_assertions::assert_eq;
1722
1723 use super::*;
1724
1725 #[test]
1726 fn test_position_to_char_index_first_line() {
1727 let code = r#"def foo():
1728return 42"#;
1729 let position = Position::new(0, 3);
1730 let index = position_to_char_index(position, code);
1731 assert_eq!(index, 3);
1732 }
1733
1734 #[test]
1735 fn test_position_to_char_index() {
1736 let code = r#"def foo():
1737return 42"#;
1738 let position = Position::new(1, 4);
1739 let index = position_to_char_index(position, code);
1740 assert_eq!(index, 15);
1741 }
1742
1743 #[test]
1744 fn test_position_to_char_index_with_newline() {
1745 let code = r#"def foo():
1746
1747return 42"#;
1748 let position = Position::new(2, 0);
1749 let index = position_to_char_index(position, code);
1750 assert_eq!(index, 12);
1751 }
1752
1753 #[test]
1754 fn test_position_to_char_at_end() {
1755 let code = r#"def foo():
1756return 42"#;
1757
1758 let position = Position::new(1, 8);
1759 let index = position_to_char_index(position, code);
1760 assert_eq!(index, 19);
1761 }
1762}