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 Client, LanguageServer,
19 jsonrpc::Result as RpcResult,
20 lsp_types::{
21 CodeAction, CodeActionKind, CodeActionOptions, CodeActionOrCommand, CodeActionParams,
22 CodeActionProviderCapability, CodeActionResponse, ColorInformation, ColorPresentation, ColorPresentationParams,
23 ColorProviderCapability, CompletionItem, CompletionItemKind, CompletionOptions, CompletionParams,
24 CompletionResponse, CreateFilesParams, DeleteFilesParams, Diagnostic, DiagnosticOptions,
25 DiagnosticServerCapabilities, DiagnosticSeverity, DidChangeConfigurationParams, DidChangeTextDocumentParams,
26 DidChangeWatchedFilesParams, DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams,
27 DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentColorParams, DocumentDiagnosticParams,
28 DocumentDiagnosticReport, DocumentDiagnosticReportResult, DocumentFilter, DocumentFormattingParams,
29 DocumentSymbol, DocumentSymbolParams, DocumentSymbolResponse, Documentation, FoldingRange, FoldingRangeParams,
30 FoldingRangeProviderCapability, FullDocumentDiagnosticReport, Hover as LspHover, HoverContents, HoverParams,
31 HoverProviderCapability, InitializeParams, InitializeResult, InitializedParams, InlayHint, InlayHintParams,
32 InsertTextFormat, MarkupContent, MarkupKind, MessageType, OneOf, Position, PrepareRenameResponse,
33 RelatedFullDocumentDiagnosticReport, RenameFilesParams, RenameParams, SemanticToken, SemanticTokenModifier,
34 SemanticTokenType, SemanticTokens, SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions,
35 SemanticTokensParams, SemanticTokensRegistrationOptions, SemanticTokensResult,
36 SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelp, SignatureHelpOptions, SignatureHelpParams,
37 StaticRegistrationOptions, TextDocumentItem, TextDocumentPositionParams, TextDocumentRegistrationOptions,
38 TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, TextEdit, WorkDoneProgressOptions,
39 WorkspaceEdit, WorkspaceFolder, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
40 },
41};
42
43use crate::{
44 ModuleId, Program, SourceRange,
45 docs::kcl_doc::ModData,
46 errors::LspSuggestion,
47 exec::KclValue,
48 execution::{cache, kcl_value::FunctionSource},
49 lsp::{
50 backend::Backend as _,
51 kcl::hover::{Hover, HoverOpts},
52 util::IntoDiagnostic,
53 },
54 parsing::{
55 PIPE_OPERATOR,
56 ast::types::{Expr, VariableKind},
57 token::TokenStream,
58 },
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 = match self.token_map.get(&filename) {
294 Some(previous_tokens) => *previous_tokens != tokens,
295 _ => true,
296 };
297
298 let had_diagnostics = self.has_diagnostics(params.uri.as_ref()).await;
299
300 if !tokens_changed && !force && !had_diagnostics {
302 return;
304 }
305
306 if tokens_changed {
307 self.token_map.insert(params.uri.to_string(), tokens.clone());
309 self.update_semantic_tokens(&tokens, ¶ms).await;
311 }
312
313 let (ast, errs) = match crate::parsing::parse_tokens(tokens.clone()).0 {
316 Ok(result) => result,
317 Err(err) => {
318 self.add_to_diagnostics(¶ms, &[err], true).await;
319 self.remove_from_ast_maps(&filename);
320 return;
321 }
322 };
323
324 self.add_to_diagnostics(¶ms, &errs, true).await;
325
326 if errs.iter().any(|e| e.severity == crate::errors::Severity::Fatal) {
327 self.remove_from_ast_maps(&filename);
328 return;
329 }
330
331 let Some(mut ast) = ast else {
332 self.remove_from_ast_maps(&filename);
333 return;
334 };
335
336 ast.compute_digest();
340
341 let ast = crate::Program {
343 ast,
344 original_file_contents: params.text.clone(),
345 };
346
347 let ast_changed = match self.ast_map.get(&filename) {
349 Some(old_ast) => {
350 *old_ast.ast != *ast.ast
352 }
353 None => true,
354 };
355
356 if !ast_changed && !force && !had_diagnostics {
357 return;
359 }
360
361 if ast_changed {
362 self.ast_map.insert(params.uri.to_string(), ast.clone());
363 self.symbols_map.insert(
365 params.uri.to_string(),
366 ast.ast.get_lsp_symbols(¶ms.text).unwrap_or_default(),
367 );
368
369 self.update_semantic_tokens(&tokens, ¶ms).await;
371
372 let discovered_findings = ast.lint_all().into_iter().flatten().collect::<Vec<_>>();
373 self.add_to_diagnostics(¶ms, &discovered_findings, false).await;
374 }
375
376 if self.can_execute().await || self.executor_ctx().await.is_none() {
378 self.client
381 .send_notification::<custom_notifications::AstUpdated>(ast.ast.clone())
382 .await;
383 }
384
385 if self.execute(¶ms, &ast).await.is_err() {
389 return;
390 }
391
392 self.clear_diagnostics_map(¶ms.uri, Some(DiagnosticSeverity::ERROR))
394 .await;
395 }
396}
397
398impl Backend {
399 pub async fn can_execute(&self) -> bool {
400 *self.can_execute.read().await
401 }
402
403 pub async fn executor_ctx(&self) -> tokio::sync::RwLockReadGuard<'_, Option<crate::execution::ExecutorContext>> {
404 self.executor_ctx.read().await
405 }
406
407 async fn update_semantic_tokens(&self, tokens: &TokenStream, params: &TextDocumentItem) {
408 let mut semantic_tokens = vec![];
410 let mut last_position = Position::new(0, 0);
411 for token in tokens.as_slice() {
412 let Ok(token_type) = SemanticTokenType::try_from(token.token_type) else {
413 continue;
416 };
417
418 let mut token_type_index = match self.get_semantic_token_type_index(&token_type) {
419 Some(index) => index,
420 None => {
423 self.client
424 .log_message(
425 MessageType::ERROR,
426 format!("token type `{token_type:?}` not accounted for"),
427 )
428 .await;
429 continue;
430 }
431 };
432
433 let source_range: SourceRange = token.into();
434 let position = source_range.start_to_lsp_position(¶ms.text);
435
436 let token_modifiers_bitset = match self.ast_map.get(params.uri.as_str()) {
439 Some(ast) => {
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 = match token_index.lock() {
542 Ok(guard) => *guard,
543 _ => 0,
544 };
545 token_type_index = t;
546
547 match modifier_index.lock() {
548 Ok(guard) => *guard,
549 _ => 0,
550 }
551 }
552 _ => 0,
553 };
554
555 if let Some(line) = params.text.lines().nth(position.line as usize) {
559 if line.len() == position.character as usize {
560 let semantic_token = SemanticToken {
563 delta_line: position.line - last_position.line + 1,
564 delta_start: 0,
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::new(position.line + 1, 0);
573 continue;
574 }
575 }
576
577 let semantic_token = SemanticToken {
578 delta_line: position.line - last_position.line,
579 delta_start: if position.line != last_position.line {
580 position.character
581 } else {
582 position.character - last_position.character
583 },
584 length: (token.end - token.start) as u32,
585 token_type: token_type_index,
586 token_modifiers_bitset,
587 };
588
589 semantic_tokens.push(semantic_token);
590
591 last_position = position;
592 }
593 self.semantic_tokens_map.insert(params.uri.to_string(), semantic_tokens);
594 }
595
596 async fn clear_diagnostics_map(&self, uri: &url::Url, severity: Option<DiagnosticSeverity>) {
597 let Some(mut items) = self.diagnostics_map.get_mut(uri.as_str()) else {
598 return;
599 };
600
601 if let Some(severity) = severity {
603 items.retain(|x| x.severity != Some(severity));
604 } else {
605 items.clear();
606 }
607
608 if items.is_empty() {
609 #[cfg(not(target_arch = "wasm32"))]
610 {
611 self.client.publish_diagnostics(uri.clone(), items.clone(), None).await;
612 }
613
614 drop(items);
616
617 self.diagnostics_map.remove(uri.as_str());
618 } else {
619 #[cfg(not(target_arch = "wasm32"))]
622 {
623 self.client.publish_diagnostics(uri.clone(), items.clone(), None).await;
624 }
625 }
626 }
627
628 async fn add_to_diagnostics<DiagT: IntoDiagnostic + std::fmt::Debug>(
629 &self,
630 params: &TextDocumentItem,
631 diagnostics: &[DiagT],
632 clear_all_before_add: bool,
633 ) {
634 if diagnostics.is_empty() {
635 return;
636 }
637
638 if clear_all_before_add {
639 self.clear_diagnostics_map(¶ms.uri, None).await;
640 } else if diagnostics.iter().all(|x| x.severity() == DiagnosticSeverity::ERROR) {
641 self.clear_diagnostics_map(¶ms.uri, Some(DiagnosticSeverity::ERROR))
645 .await;
646 } else if diagnostics
647 .iter()
648 .all(|x| x.severity() == DiagnosticSeverity::INFORMATION)
649 {
650 self.clear_diagnostics_map(¶ms.uri, Some(DiagnosticSeverity::INFORMATION))
653 .await;
654 }
655
656 let mut items = match self.diagnostics_map.get(params.uri.as_str()) {
657 Some(items) => {
658 items.clone()
660 }
661 _ => {
662 vec![]
663 }
664 };
665
666 for diagnostic in diagnostics {
667 let lsp_d = diagnostic.to_lsp_diagnostics(¶ms.text);
668 for d in lsp_d {
670 if !items.iter().any(|x| x == &d) {
671 items.push(d);
672 }
673 }
674 }
675
676 self.diagnostics_map.insert(params.uri.to_string(), items.clone());
677
678 self.client.publish_diagnostics(params.uri.clone(), items, None).await;
679 }
680
681 async fn execute(&self, params: &TextDocumentItem, ast: &Program) -> Result<()> {
682 if !self.can_execute().await {
684 return Ok(());
685 }
686
687 let ctx = self.executor_ctx().await;
689 let Some(ref executor_ctx) = *ctx else {
690 return Ok(());
691 };
692
693 if !self.is_initialized().await {
694 return Ok(());
696 }
697
698 match executor_ctx.run_with_caching(ast.clone()).await {
699 Err(err) => {
700 self.add_to_diagnostics(params, &[err], false).await;
701
702 Err(anyhow::anyhow!("failed to execute code"))
705 }
706 Ok(_) => Ok(()),
707 }
708 }
709
710 pub fn get_semantic_token_type_index(&self, token_type: &SemanticTokenType) -> Option<u32> {
711 SEMANTIC_TOKEN_TYPES
712 .iter()
713 .position(|x| *x == *token_type)
714 .map(|y| y as u32)
715 }
716
717 pub fn get_semantic_token_modifier_index(&self, token_types: Vec<SemanticTokenModifier>) -> Option<u32> {
718 if token_types.is_empty() {
719 return None;
720 }
721
722 let mut modifier = None;
723 for token_type in token_types {
724 if let Some(index) = SEMANTIC_TOKEN_MODIFIERS
725 .iter()
726 .position(|x| *x == token_type)
727 .map(|y| y as u32)
728 {
729 modifier = match modifier {
730 Some(modifier) => Some(modifier | index),
731 None => Some(index),
732 };
733 }
734 }
735 modifier
736 }
737
738 pub async fn create_zip(&self) -> Result<Vec<u8>> {
739 let mut buf = vec![];
741 let mut zip = zip::ZipWriter::new(std::io::Cursor::new(&mut buf));
742 for code in self.code_map.iter() {
743 let entry = code.key();
744 let value = code.value();
745 let file_name = entry.replace("file://", "").to_string();
746
747 let options = zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
748 zip.start_file(file_name, options)?;
749 zip.write_all(value)?;
750 }
751 zip.finish()?;
754
755 Ok(buf)
756 }
757
758 pub async fn send_telemetry(&self) -> Result<()> {
759 let user = self
761 .zoo_client
762 .users()
763 .get_self()
764 .await
765 .map_err(|e| anyhow::anyhow!(e.to_string()))?;
766
767 let mut hasher = sha2::Sha256::new();
770 hasher.update(user.id);
772 let result = hasher.finalize();
774 let user_id_hash = format!("{result:x}");
776
777 let workspace_folders = self.workspace_folders().await;
780 let project_names: Vec<&str> = workspace_folders.iter().map(|v| v.name.as_str()).collect::<Vec<_>>();
781 let project_name = project_names
783 .first()
784 .ok_or_else(|| anyhow::anyhow!("no project names"))?
785 .to_string();
786
787 self.zoo_client
789 .meta()
790 .create_event(
791 vec![kittycad::types::multipart::Attachment {
792 name: "attachment".to_string(),
794 filepath: Some("attachment.zip".into()),
795 content_type: Some("application/x-zip".to_string()),
796 data: self.create_zip().await?,
797 }],
798 &kittycad::types::Event {
799 attachment_uri: None,
801 created_at: chrono::Utc::now(),
802 event_type: kittycad::types::ModelingAppEventType::SuccessfulCompileBeforeClose,
803 last_compiled_at: Some(chrono::Utc::now()),
804 project_description: None,
806 project_name,
807 source_id: uuid::Uuid::from_str("70178592-dfca-47b3-bd2d-6fce2bcaee04").unwrap(),
810 type_: kittycad::types::Type::ModelingAppEvent,
811 user_id: user_id_hash,
812 },
813 )
814 .await
815 .map_err(|e| anyhow::anyhow!(e.to_string()))?;
816
817 Ok(())
818 }
819
820 pub async fn update_can_execute(
821 &self,
822 params: custom_notifications::UpdateCanExecuteParams,
823 ) -> RpcResult<custom_notifications::UpdateCanExecuteResponse> {
824 let mut can_execute = self.can_execute.write().await;
825
826 if *can_execute == params.can_execute {
827 return Ok(custom_notifications::UpdateCanExecuteResponse {});
828 }
829
830 *can_execute = params.can_execute;
831
832 Ok(custom_notifications::UpdateCanExecuteResponse {})
833 }
834
835 pub fn inner_prepare_rename(
837 &self,
838 params: &TextDocumentPositionParams,
839 new_name: &str,
840 ) -> RpcResult<Option<(String, String)>> {
841 let filename = params.text_document.uri.to_string();
842
843 let Some(current_code) = self.code_map.get(&filename) else {
844 return Ok(None);
845 };
846 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
847 return Ok(None);
848 };
849
850 let module_id = ModuleId::default();
854 let Ok(mut ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
855 return Ok(None);
856 };
857
858 let pos = position_to_char_index(params.position, current_code);
860 ast.rename_symbol(new_name, pos);
862 let recast = ast.recast_top(&Default::default(), 0);
864
865 Ok(Some((current_code.to_string(), recast)))
866 }
867}
868
869#[tower_lsp::async_trait]
870impl LanguageServer for Backend {
871 async fn initialize(&self, params: InitializeParams) -> RpcResult<InitializeResult> {
872 self.client
873 .log_message(MessageType::INFO, format!("initialize: {params:?}"))
874 .await;
875
876 Ok(InitializeResult {
877 capabilities: ServerCapabilities {
878 color_provider: Some(ColorProviderCapability::Simple(true)),
879 code_action_provider: Some(CodeActionProviderCapability::Options(CodeActionOptions {
880 code_action_kinds: Some(vec![CodeActionKind::QUICKFIX]),
881 resolve_provider: Some(false),
882 work_done_progress_options: WorkDoneProgressOptions::default(),
883 })),
884 completion_provider: Some(CompletionOptions {
885 resolve_provider: Some(false),
886 trigger_characters: Some(vec![".".to_string()]),
887 work_done_progress_options: Default::default(),
888 all_commit_characters: None,
889 ..Default::default()
890 }),
891 diagnostic_provider: Some(DiagnosticServerCapabilities::Options(DiagnosticOptions {
892 ..Default::default()
893 })),
894 document_formatting_provider: Some(OneOf::Left(true)),
895 folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
896 hover_provider: Some(HoverProviderCapability::Simple(true)),
897 inlay_hint_provider: Some(OneOf::Left(true)),
898 rename_provider: Some(OneOf::Left(true)),
899 semantic_tokens_provider: Some(SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(
900 SemanticTokensRegistrationOptions {
901 text_document_registration_options: {
902 TextDocumentRegistrationOptions {
903 document_selector: Some(vec![DocumentFilter {
904 language: Some("kcl".to_string()),
905 scheme: Some("file".to_string()),
906 pattern: None,
907 }]),
908 }
909 },
910 semantic_tokens_options: SemanticTokensOptions {
911 work_done_progress_options: WorkDoneProgressOptions::default(),
912 legend: SemanticTokensLegend {
913 token_types: SEMANTIC_TOKEN_TYPES.to_vec(),
914 token_modifiers: SEMANTIC_TOKEN_MODIFIERS.to_vec(),
915 },
916 range: Some(false),
917 full: Some(SemanticTokensFullOptions::Bool(true)),
918 },
919 static_registration_options: StaticRegistrationOptions::default(),
920 },
921 )),
922 signature_help_provider: Some(SignatureHelpOptions {
923 trigger_characters: None,
924 retrigger_characters: None,
925 ..Default::default()
926 }),
927 text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
928 open_close: Some(true),
929 change: Some(TextDocumentSyncKind::FULL),
930 ..Default::default()
931 })),
932 workspace: Some(WorkspaceServerCapabilities {
933 workspace_folders: Some(WorkspaceFoldersServerCapabilities {
934 supported: Some(true),
935 change_notifications: Some(OneOf::Left(true)),
936 }),
937 file_operations: None,
938 }),
939 ..Default::default()
940 },
941 ..Default::default()
942 })
943 }
944
945 async fn initialized(&self, params: InitializedParams) {
946 self.do_initialized(params).await
947 }
948
949 async fn shutdown(&self) -> RpcResult<()> {
950 self.do_shutdown().await
951 }
952
953 async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
954 self.do_did_change_workspace_folders(params).await
955 }
956
957 async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
958 self.do_did_change_configuration(params).await
959 }
960
961 async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
962 self.do_did_change_watched_files(params).await
963 }
964
965 async fn did_create_files(&self, params: CreateFilesParams) {
966 self.do_did_create_files(params).await
967 }
968
969 async fn did_rename_files(&self, params: RenameFilesParams) {
970 self.do_did_rename_files(params).await
971 }
972
973 async fn did_delete_files(&self, params: DeleteFilesParams) {
974 self.do_did_delete_files(params).await
975 }
976
977 async fn did_open(&self, params: DidOpenTextDocumentParams) {
978 self.do_did_open(params).await
979 }
980
981 async fn did_change(&self, params: DidChangeTextDocumentParams) {
982 self.do_did_change(params).await;
983 }
984
985 async fn did_save(&self, params: DidSaveTextDocumentParams) {
986 self.do_did_save(params).await
987 }
988
989 async fn did_close(&self, params: DidCloseTextDocumentParams) {
990 self.do_did_close(params).await;
991
992 if !self.can_send_telemetry {
995 return;
996 }
997
998 #[cfg(target_arch = "wasm32")]
1000 {
1001 let be = self.clone();
1002 wasm_bindgen_futures::spawn_local(async move {
1003 if let Err(err) = be.send_telemetry().await {
1004 be.client
1005 .log_message(MessageType::WARNING, format!("failed to send telemetry: {}", err))
1006 .await;
1007 }
1008 });
1009 }
1010 #[cfg(not(target_arch = "wasm32"))]
1011 if let Err(err) = self.send_telemetry().await {
1012 self.client
1013 .log_message(MessageType::WARNING, format!("failed to send telemetry: {err}"))
1014 .await;
1015 }
1016 }
1017
1018 async fn hover(&self, params: HoverParams) -> RpcResult<Option<LspHover>> {
1019 let filename = params.text_document_position_params.text_document.uri.to_string();
1020
1021 let Some(current_code) = self.code_map.get(&filename) else {
1022 return Ok(None);
1023 };
1024 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1025 return Ok(None);
1026 };
1027
1028 let pos = position_to_char_index(params.text_document_position_params.position, current_code);
1029
1030 let Some(ast) = self.ast_map.get(&filename) else {
1032 return Ok(None);
1033 };
1034
1035 let Some(hover) = ast
1036 .ast
1037 .get_hover_value_for_position(pos, current_code, &HoverOpts::default_for_hover())
1038 else {
1039 return Ok(None);
1040 };
1041
1042 match hover {
1043 Hover::Function { name, range } => {
1044 let (sig, docs) = if let Some(Some(result)) = with_cached_var(&name, |value| {
1045 match value {
1046 KclValue::Function {
1048 value: FunctionSource::User { ast, .. },
1049 ..
1050 } => {
1051 Some((ast.signature(), ""))
1053 }
1054 _ => None,
1055 }
1056 })
1057 .await
1058 {
1059 result
1060 } else {
1061 let Some(completion) = self.stdlib_completions.get(&name) else {
1063 return Ok(None);
1064 };
1065 let Some(docs) = &completion.documentation else {
1066 return Ok(None);
1067 };
1068
1069 let docs = match docs {
1070 Documentation::String(docs) => docs,
1071 Documentation::MarkupContent(MarkupContent { value, .. }) => value,
1072 };
1073
1074 let docs = if docs.len() > 320 {
1075 let end = docs.find("\n\n").or_else(|| docs.find("\n\r\n")).unwrap_or(320);
1076 &docs[..end]
1077 } else {
1078 &**docs
1079 };
1080
1081 let Some(label_details) = &completion.label_details else {
1082 return Ok(None);
1083 };
1084
1085 let sig = if let Some(detail) = &label_details.detail {
1086 detail.clone()
1087 } else {
1088 String::new()
1089 };
1090
1091 (sig, docs)
1092 };
1093
1094 Ok(Some(LspHover {
1095 contents: HoverContents::Markup(MarkupContent {
1096 kind: MarkupKind::Markdown,
1097 value: format!("```\n{name}{sig}\n```\n\n{docs}"),
1098 }),
1099 range: Some(range),
1100 }))
1101 }
1102 Hover::Type { name, range } => {
1103 let Some(completion) = self.stdlib_completions.get(&name) else {
1104 return Ok(None);
1105 };
1106 let Some(docs) = &completion.documentation else {
1107 return Ok(None);
1108 };
1109
1110 let docs = match docs {
1111 Documentation::String(docs) => docs,
1112 Documentation::MarkupContent(MarkupContent { value, .. }) => value,
1113 };
1114
1115 let docs = if docs.len() > 320 {
1116 let end = docs.find("\n\n").or_else(|| docs.find("\n\r\n")).unwrap_or(320);
1117 &docs[..end]
1118 } else {
1119 &**docs
1120 };
1121
1122 Ok(Some(LspHover {
1123 contents: HoverContents::Markup(MarkupContent {
1124 kind: MarkupKind::Markdown,
1125 value: format!("```\n{name}\n```\n\n{docs}"),
1126 }),
1127 range: Some(range),
1128 }))
1129 }
1130 Hover::KwArg {
1131 name,
1132 callee_name,
1133 range,
1134 } => {
1135 let Some(arg_map) = self.stdlib_args.get(&callee_name) else {
1138 return Ok(None);
1139 };
1140
1141 let Some(tip) = arg_map.get(&name) else {
1142 return Ok(None);
1143 };
1144
1145 Ok(Some(LspHover {
1146 contents: HoverContents::Markup(MarkupContent {
1147 kind: MarkupKind::Markdown,
1148 value: tip.clone(),
1149 }),
1150 range: Some(range),
1151 }))
1152 }
1153 Hover::Variable {
1154 name,
1155 ty: Some(ty),
1156 range,
1157 } => Ok(Some(LspHover {
1158 contents: HoverContents::Markup(MarkupContent {
1159 kind: MarkupKind::Markdown,
1160 value: format!("```\n{name}: {ty}\n```"),
1161 }),
1162 range: Some(range),
1163 })),
1164 Hover::Variable { name, ty: None, range } => Ok(with_cached_var(&name, |value| {
1165 let mut text: String = format!("```\n{name}");
1166 if let Some(ty) = value.principal_type() {
1167 text.push_str(&format!(": {}", ty.human_friendly_type()));
1168 }
1169 if let Some(v) = value.value_str() {
1170 text.push_str(&format!(" = {v}"));
1171 }
1172 text.push_str("\n```");
1173
1174 LspHover {
1175 contents: HoverContents::Markup(MarkupContent {
1176 kind: MarkupKind::Markdown,
1177 value: text,
1178 }),
1179 range: Some(range),
1180 }
1181 })
1182 .await),
1183 Hover::Signature { .. } => Ok(None),
1184 Hover::Comment { value, range } => Ok(Some(LspHover {
1185 contents: HoverContents::Markup(MarkupContent {
1186 kind: MarkupKind::Markdown,
1187 value,
1188 }),
1189 range: Some(range),
1190 })),
1191 }
1192 }
1193
1194 async fn completion(&self, params: CompletionParams) -> RpcResult<Option<CompletionResponse>> {
1195 let mut completions = vec![CompletionItem {
1196 label: PIPE_OPERATOR.to_string(),
1197 label_details: None,
1198 kind: Some(CompletionItemKind::OPERATOR),
1199 detail: Some("A pipe operator.".to_string()),
1200 documentation: Some(Documentation::MarkupContent(MarkupContent {
1201 kind: MarkupKind::Markdown,
1202 value: "A pipe operator.".to_string(),
1203 })),
1204 deprecated: Some(false),
1205 preselect: None,
1206 sort_text: None,
1207 filter_text: None,
1208 insert_text: Some("|> ".to_string()),
1209 insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
1210 insert_text_mode: None,
1211 text_edit: None,
1212 additional_text_edits: None,
1213 command: None,
1214 commit_characters: None,
1215 data: None,
1216 tags: None,
1217 }];
1218
1219 let Some(current_code) = self
1221 .code_map
1222 .get(params.text_document_position.text_document.uri.as_ref())
1223 else {
1224 return Ok(Some(CompletionResponse::Array(completions)));
1225 };
1226 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1227 return Ok(Some(CompletionResponse::Array(completions)));
1228 };
1229
1230 if let Some(line) = current_code
1232 .lines()
1233 .nth(params.text_document_position.position.line as usize)
1234 {
1235 let char_pos = params.text_document_position.position.character as usize;
1236 if char_pos <= line.len() {
1237 let line_prefix = &line[..char_pos];
1238 let last_word = line_prefix
1240 .split(|c: char| c.is_whitespace() || c.is_ascii_punctuation())
1241 .next_back()
1242 .unwrap_or("");
1243
1244 if !last_word.is_empty() && last_word.chars().next().unwrap().is_ascii_digit() {
1246 return Ok(None);
1247 }
1248 }
1249 }
1250
1251 completions.extend(self.stdlib_completions.values().cloned());
1252
1253 let Some(ast) = self
1255 .ast_map
1256 .get(params.text_document_position.text_document.uri.as_ref())
1257 else {
1258 return Ok(Some(CompletionResponse::Array(completions)));
1259 };
1260
1261 let Some(current_code) = self
1262 .code_map
1263 .get(params.text_document_position.text_document.uri.as_ref())
1264 else {
1265 return Ok(Some(CompletionResponse::Array(completions)));
1266 };
1267 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1268 return Ok(Some(CompletionResponse::Array(completions)));
1269 };
1270
1271 let position = position_to_char_index(params.text_document_position.position, current_code);
1272 if ast.ast.in_comment(position) {
1273 return Ok(None);
1275 }
1276
1277 let Ok(variables) = ast.ast.completion_items(position) else {
1279 return Ok(Some(CompletionResponse::Array(completions)));
1280 };
1281
1282 completions.extend(variables);
1284
1285 Ok(Some(CompletionResponse::Array(completions)))
1286 }
1287
1288 async fn diagnostic(&self, params: DocumentDiagnosticParams) -> RpcResult<DocumentDiagnosticReportResult> {
1289 let filename = params.text_document.uri.to_string();
1290
1291 let Some(items) = self.diagnostics_map.get(&filename) else {
1293 return Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1295 RelatedFullDocumentDiagnosticReport {
1296 related_documents: None,
1297 full_document_diagnostic_report: FullDocumentDiagnosticReport {
1298 result_id: None,
1299 items: vec![],
1300 },
1301 },
1302 )));
1303 };
1304
1305 Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1306 RelatedFullDocumentDiagnosticReport {
1307 related_documents: None,
1308 full_document_diagnostic_report: FullDocumentDiagnosticReport {
1309 result_id: None,
1310 items: items.clone(),
1311 },
1312 },
1313 )))
1314 }
1315
1316 async fn signature_help(&self, params: SignatureHelpParams) -> RpcResult<Option<SignatureHelp>> {
1317 let filename = params.text_document_position_params.text_document.uri.to_string();
1318
1319 let Some(current_code) = self.code_map.get(&filename) else {
1320 return Ok(None);
1321 };
1322 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1323 return Ok(None);
1324 };
1325
1326 let pos = position_to_char_index(params.text_document_position_params.position, current_code);
1327
1328 let Some(ch) = current_code.chars().nth(pos) else {
1330 return Ok(None);
1331 };
1332
1333 let check_char = |ch: char| {
1334 if ch == '(' {
1338 let next_space = if ch != ' ' {
1342 if let Some(next_space) = current_code[pos..].find(' ') {
1343 pos + next_space
1344 } else if let Some(next_space) = current_code[pos..].find('(') {
1345 pos + next_space
1346 } else {
1347 pos
1348 }
1349 } else {
1350 pos
1351 };
1352 let p2 = std::cmp::max(pos, next_space);
1353
1354 let last_word = current_code[..p2].split_whitespace().last()?;
1355
1356 return self.stdlib_signatures.get(last_word);
1358 } else if ch == ',' {
1359 let last_paren = current_code[..pos].rfind('(')?;
1364 let last_word = current_code[..last_paren].split_whitespace().last()?;
1366 return self.stdlib_signatures.get(last_word);
1368 }
1369
1370 None
1371 };
1372
1373 if let Some(signature) = check_char(ch) {
1374 return Ok(Some(signature.clone()));
1375 }
1376
1377 if let Some(context) = params.context {
1379 if let Some(character) = context.trigger_character {
1380 for character in character.chars() {
1381 if character == '(' || character == ',' {
1383 if let Some(signature) = check_char(character) {
1384 return Ok(Some(signature.clone()));
1385 }
1386 }
1387 }
1388 }
1389 }
1390
1391 let Some(ast) = self.ast_map.get(&filename) else {
1393 return Ok(None);
1394 };
1395
1396 let Some(value) = ast.ast.get_expr_for_position(pos) else {
1397 return Ok(None);
1398 };
1399
1400 let Some(hover) =
1401 value.get_hover_value_for_position(pos, current_code, &HoverOpts::default_for_signature_help())
1402 else {
1403 return Ok(None);
1404 };
1405
1406 match hover {
1407 Hover::Function { name, range: _ } => {
1408 let Some(signature) = self.stdlib_signatures.get(&name) else {
1410 return Ok(None);
1411 };
1412
1413 Ok(Some(signature.clone()))
1414 }
1415 Hover::Signature {
1416 name,
1417 parameter_index,
1418 range: _,
1419 } => {
1420 let Some(signature) = self.stdlib_signatures.get(&name) else {
1421 return Ok(None);
1422 };
1423
1424 let mut signature = signature.clone();
1425
1426 signature.active_parameter = Some(parameter_index);
1427
1428 Ok(Some(signature))
1429 }
1430 _ => {
1431 return Ok(None);
1432 }
1433 }
1434 }
1435
1436 async fn inlay_hint(&self, _params: InlayHintParams) -> RpcResult<Option<Vec<InlayHint>>> {
1437 Ok(None)
1440 }
1441
1442 async fn semantic_tokens_full(&self, params: SemanticTokensParams) -> RpcResult<Option<SemanticTokensResult>> {
1443 let filename = params.text_document.uri.to_string();
1444
1445 let Some(semantic_tokens) = self.semantic_tokens_map.get(&filename) else {
1446 return Ok(None);
1447 };
1448
1449 Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
1450 result_id: None,
1451 data: semantic_tokens.clone(),
1452 })))
1453 }
1454
1455 async fn document_symbol(&self, params: DocumentSymbolParams) -> RpcResult<Option<DocumentSymbolResponse>> {
1456 let filename = params.text_document.uri.to_string();
1457
1458 let Some(symbols) = self.symbols_map.get(&filename) else {
1459 return Ok(None);
1460 };
1461
1462 Ok(Some(DocumentSymbolResponse::Nested(symbols.clone())))
1463 }
1464
1465 async fn formatting(&self, params: DocumentFormattingParams) -> RpcResult<Option<Vec<TextEdit>>> {
1466 let filename = params.text_document.uri.to_string();
1467
1468 let Some(current_code) = self.code_map.get(&filename) else {
1469 return Ok(None);
1470 };
1471 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1472 return Ok(None);
1473 };
1474
1475 let module_id = ModuleId::default();
1479 let Ok(ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
1480 return Ok(None);
1481 };
1482 let recast = ast.recast_top(
1484 &crate::parsing::ast::types::FormatOptions {
1485 tab_size: params.options.tab_size as usize,
1486 insert_final_newline: params.options.insert_final_newline.unwrap_or(false),
1487 use_tabs: !params.options.insert_spaces,
1488 },
1489 0,
1490 );
1491 let source_range = SourceRange::new(0, current_code.len(), module_id);
1492 let range = source_range.to_lsp_range(current_code);
1493 Ok(Some(vec![TextEdit {
1494 new_text: recast,
1495 range,
1496 }]))
1497 }
1498
1499 async fn rename(&self, params: RenameParams) -> RpcResult<Option<WorkspaceEdit>> {
1500 let Some((current_code, new_code)) =
1501 self.inner_prepare_rename(¶ms.text_document_position, ¶ms.new_name)?
1502 else {
1503 return Ok(None);
1504 };
1505
1506 let source_range = SourceRange::new(0, current_code.len(), ModuleId::default());
1507 let range = source_range.to_lsp_range(¤t_code);
1508 Ok(Some(WorkspaceEdit {
1509 changes: Some(HashMap::from([(
1510 params.text_document_position.text_document.uri,
1511 vec![TextEdit {
1512 new_text: new_code,
1513 range,
1514 }],
1515 )])),
1516 document_changes: None,
1517 change_annotations: None,
1518 }))
1519 }
1520
1521 async fn prepare_rename(&self, params: TextDocumentPositionParams) -> RpcResult<Option<PrepareRenameResponse>> {
1522 if self
1523 .inner_prepare_rename(¶ms, "someNameNoOneInTheirRightMindWouldEverUseForTesting")?
1524 .is_none()
1525 {
1526 return Ok(None);
1527 }
1528
1529 Ok(Some(PrepareRenameResponse::DefaultBehavior { default_behavior: true }))
1531 }
1532
1533 async fn folding_range(&self, params: FoldingRangeParams) -> RpcResult<Option<Vec<FoldingRange>>> {
1534 let filename = params.text_document.uri.to_string();
1535
1536 let Some(ast) = self.ast_map.get(&filename) else {
1538 return Ok(None);
1539 };
1540
1541 let folding_ranges = ast.ast.get_lsp_folding_ranges();
1543
1544 if folding_ranges.is_empty() {
1545 return Ok(None);
1546 }
1547
1548 Ok(Some(folding_ranges))
1549 }
1550
1551 async fn code_action(&self, params: CodeActionParams) -> RpcResult<Option<CodeActionResponse>> {
1552 let actions = params
1553 .context
1554 .diagnostics
1555 .into_iter()
1556 .filter_map(|diagnostic| {
1557 let (suggestion, range) = diagnostic
1558 .data
1559 .as_ref()
1560 .and_then(|data| serde_json::from_value::<LspSuggestion>(data.clone()).ok())?;
1561 let edit = TextEdit {
1562 range,
1563 new_text: suggestion.insert,
1564 };
1565 let changes = HashMap::from([(params.text_document.uri.clone(), vec![edit])]);
1566
1567 Some(CodeActionOrCommand::CodeAction(CodeAction {
1570 title: suggestion.title,
1571 kind: Some(CodeActionKind::QUICKFIX),
1572 diagnostics: Some(vec![diagnostic]),
1573 edit: Some(WorkspaceEdit {
1574 changes: Some(changes),
1575 document_changes: None,
1576 change_annotations: None,
1577 }),
1578 command: None,
1579 is_preferred: Some(true),
1580 disabled: None,
1581 data: None,
1582 }))
1583 })
1584 .collect();
1585
1586 Ok(Some(actions))
1587 }
1588
1589 async fn document_color(&self, params: DocumentColorParams) -> RpcResult<Vec<ColorInformation>> {
1590 let filename = params.text_document.uri.to_string();
1591
1592 let Some(current_code) = self.code_map.get(&filename) else {
1593 return Ok(vec![]);
1594 };
1595 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1596 return Ok(vec![]);
1597 };
1598
1599 let Some(ast) = self.ast_map.get(&filename) else {
1601 return Ok(vec![]);
1602 };
1603
1604 let Ok(colors) = ast.ast.document_color(current_code) else {
1606 return Ok(vec![]);
1607 };
1608
1609 Ok(colors)
1610 }
1611
1612 async fn color_presentation(&self, params: ColorPresentationParams) -> RpcResult<Vec<ColorPresentation>> {
1613 let filename = params.text_document.uri.to_string();
1614
1615 let Some(current_code) = self.code_map.get(&filename) else {
1616 return Ok(vec![]);
1617 };
1618 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1619 return Ok(vec![]);
1620 };
1621
1622 let Some(ast) = self.ast_map.get(&filename) else {
1624 return Ok(vec![]);
1625 };
1626
1627 let pos_start = position_to_char_index(params.range.start, current_code);
1628 let pos_end = position_to_char_index(params.range.end, current_code);
1629
1630 let Ok(Some(presentation)) = ast.ast.color_presentation(¶ms.color, pos_start, pos_end) else {
1632 return Ok(vec![]);
1633 };
1634
1635 Ok(vec![presentation])
1636 }
1637}
1638
1639pub fn get_completions_from_stdlib(kcl_std: &ModData) -> Result<HashMap<String, CompletionItem>> {
1641 let mut completions = HashMap::new();
1642
1643 for d in kcl_std.all_docs() {
1644 if let Some(ci) = d.to_completion_item() {
1645 completions.insert(d.name().to_owned(), ci);
1646 }
1647 }
1648
1649 let variable_kinds = VariableKind::to_completion_items();
1650 for variable_kind in variable_kinds {
1651 completions.insert(variable_kind.label.clone(), variable_kind);
1652 }
1653
1654 Ok(completions)
1655}
1656
1657pub fn get_signatures_from_stdlib(kcl_std: &ModData) -> HashMap<String, SignatureHelp> {
1659 let mut signatures = HashMap::new();
1660
1661 for d in kcl_std.all_docs() {
1662 if let Some(sig) = d.to_signature_help() {
1663 signatures.insert(d.name().to_owned(), sig);
1664 }
1665 }
1666
1667 signatures
1668}
1669
1670pub fn get_arg_maps_from_stdlib(kcl_std: &ModData) -> HashMap<String, HashMap<String, String>> {
1672 let mut result = HashMap::new();
1673
1674 for d in kcl_std.all_docs() {
1675 let crate::docs::kcl_doc::DocData::Fn(f) = d else {
1676 continue;
1677 };
1678 let arg_map: HashMap<String, String> = f
1679 .args
1680 .iter()
1681 .map(|data| {
1682 let mut tip = "```\n".to_owned();
1683 tip.push_str(&data.to_string());
1684 tip.push_str("\n```");
1685 if let Some(docs) = &data.docs {
1686 tip.push_str("\n\n");
1687 tip.push_str(docs);
1688 }
1689 (data.name.clone(), tip)
1690 })
1691 .collect();
1692 if !arg_map.is_empty() {
1693 result.insert(f.name.clone(), arg_map);
1694 }
1695 }
1696
1697 result
1698}
1699
1700fn position_to_char_index(position: Position, code: &str) -> usize {
1702 let mut char_position = 0;
1704 for (index, line) in code.lines().enumerate() {
1705 if index == position.line as usize {
1706 char_position += position.character as usize;
1707 break;
1708 } else {
1709 char_position += line.len() + 1;
1710 }
1711 }
1712
1713 std::cmp::min(char_position, code.len() - 1)
1714}
1715
1716async fn with_cached_var<T>(name: &str, f: impl Fn(&KclValue) -> T) -> Option<T> {
1717 let mem = cache::read_old_memory().await?;
1718 let value = mem.0.get(name, SourceRange::default()).ok()?;
1719
1720 Some(f(value))
1721}
1722
1723#[cfg(test)]
1724mod tests {
1725 use pretty_assertions::assert_eq;
1726
1727 use super::*;
1728
1729 #[test]
1730 fn test_position_to_char_index_first_line() {
1731 let code = r#"def foo():
1732return 42"#;
1733 let position = Position::new(0, 3);
1734 let index = position_to_char_index(position, code);
1735 assert_eq!(index, 3);
1736 }
1737
1738 #[test]
1739 fn test_position_to_char_index() {
1740 let code = r#"def foo():
1741return 42"#;
1742 let position = Position::new(1, 4);
1743 let index = position_to_char_index(position, code);
1744 assert_eq!(index, 15);
1745 }
1746
1747 #[test]
1748 fn test_position_to_char_index_with_newline() {
1749 let code = r#"def foo():
1750
1751return 42"#;
1752 let position = Position::new(2, 0);
1753 let index = position_to_char_index(position, code);
1754 assert_eq!(index, 12);
1755 }
1756
1757 #[test]
1758 fn test_position_to_char_at_end() {
1759 let code = r#"def foo():
1760return 42"#;
1761
1762 let position = Position::new(1, 8);
1763 let index = position_to_char_index(position, code);
1764 assert_eq!(index, 19);
1765 }
1766}