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, CodeActionOrCommand, CodeActionParams, CodeActionResponse, CompletionItem,
21 CompletionItemKind, CompletionOptions, CompletionParams, CompletionResponse, CreateFilesParams,
22 DeleteFilesParams, Diagnostic, DiagnosticOptions, DiagnosticServerCapabilities, DiagnosticSeverity,
23 DidChangeConfigurationParams, DidChangeTextDocumentParams, DidChangeWatchedFilesParams,
24 DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
25 DidSaveTextDocumentParams, DocumentDiagnosticParams, DocumentDiagnosticReport, DocumentDiagnosticReportResult,
26 DocumentFilter, DocumentFormattingParams, DocumentSymbol, DocumentSymbolParams, DocumentSymbolResponse,
27 Documentation, FoldingRange, FoldingRangeParams, FoldingRangeProviderCapability, FullDocumentDiagnosticReport,
28 Hover as LspHover, HoverContents, HoverParams, HoverProviderCapability, InitializeParams, InitializeResult,
29 InitializedParams, InlayHint, InlayHintParams, InsertTextFormat, MarkupContent, MarkupKind, MessageType, OneOf,
30 Position, RelatedFullDocumentDiagnosticReport, RenameFilesParams, RenameParams, SemanticToken,
31 SemanticTokenModifier, SemanticTokenType, SemanticTokens, SemanticTokensFullOptions, SemanticTokensLegend,
32 SemanticTokensOptions, SemanticTokensParams, SemanticTokensRegistrationOptions, SemanticTokensResult,
33 SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelp, SignatureHelpOptions, SignatureHelpParams,
34 StaticRegistrationOptions, TextDocumentItem, TextDocumentRegistrationOptions, TextDocumentSyncCapability,
35 TextDocumentSyncKind, TextDocumentSyncOptions, TextEdit, WorkDoneProgressOptions, WorkspaceEdit,
36 WorkspaceFolder, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
37 },
38 Client, LanguageServer,
39};
40
41use crate::{
42 docs::kcl_doc::DocData,
43 errors::Suggestion,
44 exec::KclValue,
45 execution::{cache, kcl_value::FunctionSource},
46 lsp::{
47 backend::Backend as _,
48 kcl::hover::{Hover, HoverOpts},
49 util::IntoDiagnostic,
50 },
51 parsing::{
52 ast::types::{Expr, VariableKind},
53 token::TokenStream,
54 PIPE_OPERATOR,
55 },
56 ModuleId, Program, SourceRange,
57};
58
59pub mod custom_notifications;
60mod hover;
61
62const SEMANTIC_TOKEN_TYPES: [SemanticTokenType; 10] = [
63 SemanticTokenType::NUMBER,
64 SemanticTokenType::VARIABLE,
65 SemanticTokenType::KEYWORD,
66 SemanticTokenType::TYPE,
67 SemanticTokenType::STRING,
68 SemanticTokenType::OPERATOR,
69 SemanticTokenType::COMMENT,
70 SemanticTokenType::FUNCTION,
71 SemanticTokenType::PARAMETER,
72 SemanticTokenType::PROPERTY,
73];
74
75const SEMANTIC_TOKEN_MODIFIERS: [SemanticTokenModifier; 5] = [
76 SemanticTokenModifier::DECLARATION,
77 SemanticTokenModifier::DEFINITION,
78 SemanticTokenModifier::DEFAULT_LIBRARY,
79 SemanticTokenModifier::READONLY,
80 SemanticTokenModifier::STATIC,
81];
82
83#[derive(Clone, Debug)]
85#[cfg_attr(feature = "cli", derive(Parser))]
86pub struct Server {
87 #[cfg_attr(feature = "cli", clap(long, default_value = "8080"))]
89 pub socket: i32,
90
91 #[cfg_attr(feature = "cli", clap(short, long, default_value = "false"))]
93 pub stdio: bool,
94}
95
96#[derive(Clone)]
98pub struct Backend {
99 pub client: Client,
101 pub fs: Arc<crate::fs::FileManager>,
103 pub workspace_folders: DashMap<String, WorkspaceFolder>,
105 pub stdlib_completions: HashMap<String, CompletionItem>,
107 pub stdlib_signatures: HashMap<String, SignatureHelp>,
109 pub stdlib_args: HashMap<String, HashMap<String, String>>,
111 pub(super) token_map: DashMap<String, TokenStream>,
113 pub ast_map: DashMap<String, crate::Program>,
115 pub code_map: DashMap<String, Vec<u8>>,
117 pub diagnostics_map: DashMap<String, Vec<Diagnostic>>,
119 pub symbols_map: DashMap<String, Vec<DocumentSymbol>>,
121 pub semantic_tokens_map: DashMap<String, Vec<SemanticToken>>,
123 pub zoo_client: kittycad::Client,
125 pub can_send_telemetry: bool,
127 pub executor_ctx: Arc<RwLock<Option<crate::execution::ExecutorContext>>>,
129 pub can_execute: Arc<RwLock<bool>>,
131
132 pub is_initialized: Arc<RwLock<bool>>,
133}
134
135impl Backend {
136 #[cfg(target_arch = "wasm32")]
137 pub fn new_wasm(
138 client: Client,
139 executor_ctx: Option<crate::execution::ExecutorContext>,
140 fs: crate::fs::wasm::FileSystemManager,
141 zoo_client: kittycad::Client,
142 can_send_telemetry: bool,
143 ) -> Result<Self, String> {
144 Self::with_file_manager(
145 client,
146 executor_ctx,
147 crate::fs::FileManager::new(fs),
148 zoo_client,
149 can_send_telemetry,
150 )
151 }
152
153 #[cfg(not(target_arch = "wasm32"))]
154 pub fn new(
155 client: Client,
156 executor_ctx: Option<crate::execution::ExecutorContext>,
157 zoo_client: kittycad::Client,
158 can_send_telemetry: bool,
159 ) -> Result<Self, String> {
160 Self::with_file_manager(
161 client,
162 executor_ctx,
163 crate::fs::FileManager::new(),
164 zoo_client,
165 can_send_telemetry,
166 )
167 }
168
169 fn with_file_manager(
170 client: Client,
171 executor_ctx: Option<crate::execution::ExecutorContext>,
172 fs: crate::fs::FileManager,
173 zoo_client: kittycad::Client,
174 can_send_telemetry: bool,
175 ) -> Result<Self, String> {
176 let stdlib = crate::std::StdLib::new();
177 let kcl_std = crate::docs::kcl_doc::walk_prelude();
178 let stdlib_completions = get_completions_from_stdlib(&stdlib, &kcl_std).map_err(|e| e.to_string())?;
179 let stdlib_signatures = get_signatures_from_stdlib(&stdlib, &kcl_std);
180 let stdlib_args = get_arg_maps_from_stdlib(&stdlib, &kcl_std);
181
182 Ok(Self {
183 client,
184 fs: Arc::new(fs),
185 stdlib_completions,
186 stdlib_signatures,
187 stdlib_args,
188 zoo_client,
189 can_send_telemetry,
190 can_execute: Arc::new(RwLock::new(executor_ctx.is_some())),
191 executor_ctx: Arc::new(RwLock::new(executor_ctx)),
192 workspace_folders: Default::default(),
193 token_map: Default::default(),
194 ast_map: Default::default(),
195 code_map: Default::default(),
196 diagnostics_map: Default::default(),
197 symbols_map: Default::default(),
198 semantic_tokens_map: Default::default(),
199 is_initialized: Default::default(),
200 })
201 }
202
203 fn remove_from_ast_maps(&self, filename: &str) {
204 self.ast_map.remove(filename);
205 self.symbols_map.remove(filename);
206 }
207}
208
209#[async_trait::async_trait]
211impl crate::lsp::backend::Backend for Backend {
212 fn client(&self) -> &Client {
213 &self.client
214 }
215
216 fn fs(&self) -> &Arc<crate::fs::FileManager> {
217 &self.fs
218 }
219
220 async fn is_initialized(&self) -> bool {
221 *self.is_initialized.read().await
222 }
223
224 async fn set_is_initialized(&self, is_initialized: bool) {
225 *self.is_initialized.write().await = is_initialized;
226 }
227
228 async fn workspace_folders(&self) -> Vec<WorkspaceFolder> {
229 self.workspace_folders.iter().map(|i| i.clone()).collect()
231 }
232
233 async fn add_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
234 for folder in folders {
235 self.workspace_folders.insert(folder.name.to_string(), folder);
236 }
237 }
238
239 async fn remove_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
240 for folder in folders {
241 self.workspace_folders.remove(&folder.name);
242 }
243 }
244
245 fn code_map(&self) -> &DashMap<String, Vec<u8>> {
246 &self.code_map
247 }
248
249 async fn insert_code_map(&self, uri: String, text: Vec<u8>) {
250 self.code_map.insert(uri, text);
251 }
252
253 async fn remove_from_code_map(&self, uri: String) -> Option<Vec<u8>> {
254 self.code_map.remove(&uri).map(|x| x.1)
255 }
256
257 async fn clear_code_state(&self) {
258 self.code_map.clear();
259 self.token_map.clear();
260 self.ast_map.clear();
261 self.diagnostics_map.clear();
262 self.symbols_map.clear();
263 self.semantic_tokens_map.clear();
264 }
265
266 fn current_diagnostics_map(&self) -> &DashMap<String, Vec<Diagnostic>> {
267 &self.diagnostics_map
268 }
269
270 async fn inner_on_change(&self, params: TextDocumentItem, force: bool) {
271 if force {
272 crate::bust_cache().await;
273 }
274
275 let filename = params.uri.to_string();
276 let module_id = ModuleId::default();
280 let tokens = match crate::parsing::token::lex(¶ms.text, module_id) {
281 Ok(tokens) => tokens,
282 Err(err) => {
283 self.add_to_diagnostics(¶ms, &[err], true).await;
284 self.token_map.remove(&filename);
285 self.remove_from_ast_maps(&filename);
286 self.semantic_tokens_map.remove(&filename);
287 return;
288 }
289 };
290
291 let tokens_changed = if let Some(previous_tokens) = self.token_map.get(&filename) {
293 *previous_tokens != tokens
294 } else {
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 `{:?}` not accounted for", token_type),
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 = if let Some(ast) = self.ast_map.get(params.uri.as_str()) {
439 let token_index = Arc::new(Mutex::new(token_type_index));
440 let modifier_index: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
441 crate::walk::walk(&ast.ast, |node: crate::walk::Node| {
442 let Ok(node_range): Result<SourceRange, _> = (&node).try_into() else {
443 return Ok(true);
444 };
445
446 if !node_range.contains(source_range.start()) {
447 return Ok(true);
448 }
449
450 let get_modifier = |modifier: Vec<SemanticTokenModifier>| -> Result<bool> {
451 let mut mods = modifier_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
452 let Some(token_modifier_index) = self.get_semantic_token_modifier_index(modifier) else {
453 return Ok(true);
454 };
455 if *mods == 0 {
456 *mods = token_modifier_index;
457 } else {
458 *mods |= token_modifier_index;
459 }
460 Ok(false)
461 };
462
463 match node {
464 crate::walk::Node::TagDeclarator(_) => {
465 return get_modifier(vec![
466 SemanticTokenModifier::DEFINITION,
467 SemanticTokenModifier::STATIC,
468 ]);
469 }
470 crate::walk::Node::VariableDeclarator(variable) => {
471 let sr: SourceRange = (&variable.id).into();
472 if sr.contains(source_range.start()) {
473 if let Expr::FunctionExpression(_) = &variable.init {
474 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
475 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::FUNCTION) {
476 Some(index) => index,
477 None => token_type_index,
478 };
479 }
480
481 return get_modifier(vec![
482 SemanticTokenModifier::DECLARATION,
483 SemanticTokenModifier::READONLY,
484 ]);
485 }
486 }
487 crate::walk::Node::Parameter(_) => {
488 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
489 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PARAMETER) {
490 Some(index) => index,
491 None => token_type_index,
492 };
493 return Ok(false);
494 }
495 crate::walk::Node::MemberExpression(member_expression) => {
496 let sr: SourceRange = (&member_expression.property).into();
497 if sr.contains(source_range.start()) {
498 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
499 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PROPERTY) {
500 Some(index) => index,
501 None => token_type_index,
502 };
503 return Ok(false);
504 }
505 }
506 crate::walk::Node::ObjectProperty(object_property) => {
507 let sr: SourceRange = (&object_property.key).into();
508 if sr.contains(source_range.start()) {
509 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
510 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PROPERTY) {
511 Some(index) => index,
512 None => token_type_index,
513 };
514 }
515 return get_modifier(vec![SemanticTokenModifier::DECLARATION]);
516 }
517 crate::walk::Node::CallExpression(call_expr) => {
518 let sr: SourceRange = (&call_expr.callee).into();
519 if sr.contains(source_range.start()) {
520 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
521 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::FUNCTION) {
522 Some(index) => index,
523 None => token_type_index,
524 };
525
526 if self.stdlib_completions.contains_key(&call_expr.callee.name.name) {
527 return get_modifier(vec![SemanticTokenModifier::DEFAULT_LIBRARY]);
529 }
530
531 return Ok(false);
532 }
533 }
534 _ => {}
535 }
536 Ok(true)
537 })
538 .unwrap_or_default();
539
540 let t = if let Ok(guard) = token_index.lock() { *guard } else { 0 };
541 token_type_index = t;
542
543 let m = if let Ok(guard) = modifier_index.lock() {
544 *guard
545 } else {
546 0
547 };
548 m
549 } else {
550 0
551 };
552
553 if let Some(line) = params.text.lines().nth(position.line as usize) {
557 if line.len() == position.character as usize {
558 let semantic_token = SemanticToken {
561 delta_line: position.line - last_position.line + 1,
562 delta_start: 0,
563 length: (token.end - token.start) as u32,
564 token_type: token_type_index,
565 token_modifiers_bitset,
566 };
567
568 semantic_tokens.push(semantic_token);
569
570 last_position = Position::new(position.line + 1, 0);
571 continue;
572 }
573 }
574
575 let semantic_token = SemanticToken {
576 delta_line: position.line - last_position.line,
577 delta_start: if position.line != last_position.line {
578 position.character
579 } else {
580 position.character - last_position.character
581 },
582 length: (token.end - token.start) as u32,
583 token_type: token_type_index,
584 token_modifiers_bitset,
585 };
586
587 semantic_tokens.push(semantic_token);
588
589 last_position = position;
590 }
591 self.semantic_tokens_map.insert(params.uri.to_string(), semantic_tokens);
592 }
593
594 async fn clear_diagnostics_map(&self, uri: &url::Url, severity: Option<DiagnosticSeverity>) {
595 let Some(mut items) = self.diagnostics_map.get_mut(uri.as_str()) else {
596 return;
597 };
598
599 if let Some(severity) = severity {
601 items.retain(|x| x.severity != Some(severity));
602 } else {
603 items.clear();
604 }
605
606 if items.is_empty() {
607 #[cfg(not(target_arch = "wasm32"))]
608 {
609 self.client.publish_diagnostics(uri.clone(), items.clone(), None).await;
610 }
611
612 drop(items);
614
615 self.diagnostics_map.remove(uri.as_str());
616 } else {
617 #[cfg(not(target_arch = "wasm32"))]
620 {
621 self.client.publish_diagnostics(uri.clone(), items.clone(), None).await;
622 }
623 }
624 }
625
626 async fn add_to_diagnostics<DiagT: IntoDiagnostic + std::fmt::Debug>(
627 &self,
628 params: &TextDocumentItem,
629 diagnostics: &[DiagT],
630 clear_all_before_add: bool,
631 ) {
632 if diagnostics.is_empty() {
633 return;
634 }
635
636 if clear_all_before_add {
637 self.clear_diagnostics_map(¶ms.uri, None).await;
638 } else if diagnostics.iter().all(|x| x.severity() == DiagnosticSeverity::ERROR) {
639 self.clear_diagnostics_map(¶ms.uri, Some(DiagnosticSeverity::ERROR))
643 .await;
644 } else if diagnostics
645 .iter()
646 .all(|x| x.severity() == DiagnosticSeverity::INFORMATION)
647 {
648 self.clear_diagnostics_map(¶ms.uri, Some(DiagnosticSeverity::INFORMATION))
651 .await;
652 }
653
654 let mut items = if let Some(items) = self.diagnostics_map.get(params.uri.as_str()) {
655 items.clone()
657 } else {
658 vec![]
659 };
660
661 for diagnostic in diagnostics {
662 let lsp_d = diagnostic.to_lsp_diagnostics(¶ms.text);
663 for d in lsp_d {
665 if !items.iter().any(|x| x == &d) {
666 items.push(d);
667 }
668 }
669 }
670
671 self.diagnostics_map.insert(params.uri.to_string(), items.clone());
672
673 self.client.publish_diagnostics(params.uri.clone(), items, None).await;
674 }
675
676 async fn execute(&self, params: &TextDocumentItem, ast: &Program) -> Result<()> {
677 if !self.can_execute().await {
679 return Ok(());
680 }
681
682 let ctx = self.executor_ctx().await;
684 let Some(ref executor_ctx) = *ctx else {
685 return Ok(());
686 };
687
688 if !self.is_initialized().await {
689 return Ok(());
691 }
692
693 match executor_ctx.run_with_caching(ast.clone()).await {
694 Err(err) => {
695 self.add_to_diagnostics(params, &[err], false).await;
696
697 Err(anyhow::anyhow!("failed to execute code"))
700 }
701 Ok(_) => Ok(()),
702 }
703 }
704
705 pub fn get_semantic_token_type_index(&self, token_type: &SemanticTokenType) -> Option<u32> {
706 SEMANTIC_TOKEN_TYPES
707 .iter()
708 .position(|x| *x == *token_type)
709 .map(|y| y as u32)
710 }
711
712 pub fn get_semantic_token_modifier_index(&self, token_types: Vec<SemanticTokenModifier>) -> Option<u32> {
713 if token_types.is_empty() {
714 return None;
715 }
716
717 let mut modifier = None;
718 for token_type in token_types {
719 if let Some(index) = SEMANTIC_TOKEN_MODIFIERS
720 .iter()
721 .position(|x| *x == token_type)
722 .map(|y| y as u32)
723 {
724 modifier = match modifier {
725 Some(modifier) => Some(modifier | index),
726 None => Some(index),
727 };
728 }
729 }
730 modifier
731 }
732
733 pub async fn create_zip(&self) -> Result<Vec<u8>> {
734 let mut buf = vec![];
736 let mut zip = zip::ZipWriter::new(std::io::Cursor::new(&mut buf));
737 for code in self.code_map.iter() {
738 let entry = code.key();
739 let value = code.value();
740 let file_name = entry.replace("file://", "").to_string();
741
742 let options = zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
743 zip.start_file(file_name, options)?;
744 zip.write_all(value)?;
745 }
746 zip.finish()?;
749
750 Ok(buf)
751 }
752
753 pub async fn send_telemetry(&self) -> Result<()> {
754 let user = self
756 .zoo_client
757 .users()
758 .get_self()
759 .await
760 .map_err(|e| anyhow::anyhow!(e.to_string()))?;
761
762 let mut hasher = sha2::Sha256::new();
765 hasher.update(user.id);
767 let result = hasher.finalize();
769 let user_id_hash = format!("{:x}", result);
771
772 let workspace_folders = self.workspace_folders().await;
775 let project_names: Vec<&str> = workspace_folders.iter().map(|v| v.name.as_str()).collect::<Vec<_>>();
776 let project_name = project_names
778 .first()
779 .ok_or_else(|| anyhow::anyhow!("no project names"))?
780 .to_string();
781
782 self.zoo_client
784 .meta()
785 .create_event(
786 vec![kittycad::types::multipart::Attachment {
787 name: "attachment".to_string(),
789 filepath: Some("attachment.zip".into()),
790 content_type: Some("application/x-zip".to_string()),
791 data: self.create_zip().await?,
792 }],
793 &kittycad::types::Event {
794 attachment_uri: None,
796 created_at: chrono::Utc::now(),
797 event_type: kittycad::types::ModelingAppEventType::SuccessfulCompileBeforeClose,
798 last_compiled_at: Some(chrono::Utc::now()),
799 project_description: None,
801 project_name,
802 source_id: uuid::Uuid::from_str("70178592-dfca-47b3-bd2d-6fce2bcaee04").unwrap(),
805 type_: kittycad::types::Type::ModelingAppEvent,
806 user_id: user_id_hash,
807 },
808 )
809 .await
810 .map_err(|e| anyhow::anyhow!(e.to_string()))?;
811
812 Ok(())
813 }
814
815 pub async fn update_can_execute(
816 &self,
817 params: custom_notifications::UpdateCanExecuteParams,
818 ) -> RpcResult<custom_notifications::UpdateCanExecuteResponse> {
819 let mut can_execute = self.can_execute.write().await;
820
821 if *can_execute == params.can_execute {
822 return Ok(custom_notifications::UpdateCanExecuteResponse {});
823 }
824
825 *can_execute = params.can_execute;
826
827 Ok(custom_notifications::UpdateCanExecuteResponse {})
828 }
829}
830
831#[tower_lsp::async_trait]
832impl LanguageServer for Backend {
833 async fn initialize(&self, params: InitializeParams) -> RpcResult<InitializeResult> {
834 self.client
835 .log_message(MessageType::INFO, format!("initialize: {:?}", params))
836 .await;
837
838 Ok(InitializeResult {
839 capabilities: ServerCapabilities {
840 completion_provider: Some(CompletionOptions {
841 resolve_provider: Some(false),
842 trigger_characters: Some(vec![".".to_string()]),
843 work_done_progress_options: Default::default(),
844 all_commit_characters: None,
845 ..Default::default()
846 }),
847 diagnostic_provider: Some(DiagnosticServerCapabilities::Options(DiagnosticOptions {
848 ..Default::default()
849 })),
850 document_formatting_provider: Some(OneOf::Left(true)),
851 folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
852 hover_provider: Some(HoverProviderCapability::Simple(true)),
853 inlay_hint_provider: Some(OneOf::Left(true)),
854 rename_provider: Some(OneOf::Left(true)),
855 semantic_tokens_provider: Some(SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(
856 SemanticTokensRegistrationOptions {
857 text_document_registration_options: {
858 TextDocumentRegistrationOptions {
859 document_selector: Some(vec![DocumentFilter {
860 language: Some("kcl".to_string()),
861 scheme: Some("file".to_string()),
862 pattern: None,
863 }]),
864 }
865 },
866 semantic_tokens_options: SemanticTokensOptions {
867 work_done_progress_options: WorkDoneProgressOptions::default(),
868 legend: SemanticTokensLegend {
869 token_types: SEMANTIC_TOKEN_TYPES.to_vec(),
870 token_modifiers: SEMANTIC_TOKEN_MODIFIERS.to_vec(),
871 },
872 range: Some(false),
873 full: Some(SemanticTokensFullOptions::Bool(true)),
874 },
875 static_registration_options: StaticRegistrationOptions::default(),
876 },
877 )),
878 signature_help_provider: Some(SignatureHelpOptions {
879 trigger_characters: None,
880 retrigger_characters: None,
881 ..Default::default()
882 }),
883 text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
884 open_close: Some(true),
885 change: Some(TextDocumentSyncKind::FULL),
886 ..Default::default()
887 })),
888 workspace: Some(WorkspaceServerCapabilities {
889 workspace_folders: Some(WorkspaceFoldersServerCapabilities {
890 supported: Some(true),
891 change_notifications: Some(OneOf::Left(true)),
892 }),
893 file_operations: None,
894 }),
895 ..Default::default()
896 },
897 ..Default::default()
898 })
899 }
900
901 async fn initialized(&self, params: InitializedParams) {
902 self.do_initialized(params).await
903 }
904
905 async fn shutdown(&self) -> RpcResult<()> {
906 self.do_shutdown().await
907 }
908
909 async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
910 self.do_did_change_workspace_folders(params).await
911 }
912
913 async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
914 self.do_did_change_configuration(params).await
915 }
916
917 async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
918 self.do_did_change_watched_files(params).await
919 }
920
921 async fn did_create_files(&self, params: CreateFilesParams) {
922 self.do_did_create_files(params).await
923 }
924
925 async fn did_rename_files(&self, params: RenameFilesParams) {
926 self.do_did_rename_files(params).await
927 }
928
929 async fn did_delete_files(&self, params: DeleteFilesParams) {
930 self.do_did_delete_files(params).await
931 }
932
933 async fn did_open(&self, params: DidOpenTextDocumentParams) {
934 self.do_did_open(params).await
935 }
936
937 async fn did_change(&self, params: DidChangeTextDocumentParams) {
938 self.do_did_change(params).await;
939 }
940
941 async fn did_save(&self, params: DidSaveTextDocumentParams) {
942 self.do_did_save(params).await
943 }
944
945 async fn did_close(&self, params: DidCloseTextDocumentParams) {
946 self.do_did_close(params).await;
947
948 if !self.can_send_telemetry {
951 return;
952 }
953
954 #[cfg(target_arch = "wasm32")]
956 {
957 let be = self.clone();
958 wasm_bindgen_futures::spawn_local(async move {
959 if let Err(err) = be.send_telemetry().await {
960 be.client
961 .log_message(MessageType::WARNING, format!("failed to send telemetry: {}", err))
962 .await;
963 }
964 });
965 }
966 #[cfg(not(target_arch = "wasm32"))]
967 if let Err(err) = self.send_telemetry().await {
968 self.client
969 .log_message(MessageType::WARNING, format!("failed to send telemetry: {}", err))
970 .await;
971 }
972 }
973
974 async fn hover(&self, params: HoverParams) -> RpcResult<Option<LspHover>> {
975 let filename = params.text_document_position_params.text_document.uri.to_string();
976
977 let Some(current_code) = self.code_map.get(&filename) else {
978 return Ok(None);
979 };
980 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
981 return Ok(None);
982 };
983
984 let pos = position_to_char_index(params.text_document_position_params.position, current_code);
985
986 let Some(ast) = self.ast_map.get(&filename) else {
988 return Ok(None);
989 };
990
991 let Some(hover) = ast
992 .ast
993 .get_hover_value_for_position(pos, current_code, &HoverOpts::default_for_hover())
994 else {
995 return Ok(None);
996 };
997
998 match hover {
999 Hover::Function { name, range } => {
1000 let (sig, docs) = if let Some(Some(result)) = with_cached_var(&name, |value| {
1001 match value {
1002 KclValue::Function {
1004 value: FunctionSource::User { ast, .. },
1005 ..
1006 } => {
1007 Some((ast.signature(), ""))
1009 }
1010 _ => None,
1011 }
1012 })
1013 .await
1014 {
1015 result
1016 } else {
1017 let Some(completion) = self.stdlib_completions.get(&name) else {
1019 return Ok(None);
1020 };
1021 let Some(docs) = &completion.documentation else {
1022 return Ok(None);
1023 };
1024
1025 let docs = match docs {
1026 Documentation::String(docs) => docs,
1027 Documentation::MarkupContent(MarkupContent { value, .. }) => value,
1028 };
1029
1030 let docs = if docs.len() > 320 {
1031 let end = docs.find("\n\n").or_else(|| docs.find("\n\r\n")).unwrap_or(320);
1032 &docs[..end]
1033 } else {
1034 &**docs
1035 };
1036
1037 let Some(label_details) = &completion.label_details else {
1038 return Ok(None);
1039 };
1040
1041 let sig = if let Some(detail) = &label_details.detail {
1042 detail.clone()
1043 } else {
1044 String::new()
1045 };
1046
1047 (sig, docs)
1048 };
1049
1050 Ok(Some(LspHover {
1051 contents: HoverContents::Markup(MarkupContent {
1052 kind: MarkupKind::Markdown,
1053 value: format!("```\n{}{}\n```\n\n{}", name, sig, docs),
1054 }),
1055 range: Some(range),
1056 }))
1057 }
1058 Hover::Type { name, range } => {
1059 let Some(completion) = self.stdlib_completions.get(&name) else {
1060 return Ok(None);
1061 };
1062 let Some(docs) = &completion.documentation else {
1063 return Ok(None);
1064 };
1065
1066 let docs = match docs {
1067 Documentation::String(docs) => docs,
1068 Documentation::MarkupContent(MarkupContent { value, .. }) => value,
1069 };
1070
1071 let docs = if docs.len() > 320 {
1072 let end = docs.find("\n\n").or_else(|| docs.find("\n\r\n")).unwrap_or(320);
1073 &docs[..end]
1074 } else {
1075 &**docs
1076 };
1077
1078 Ok(Some(LspHover {
1079 contents: HoverContents::Markup(MarkupContent {
1080 kind: MarkupKind::Markdown,
1081 value: format!("```\n{}\n```\n\n{}", name, docs),
1082 }),
1083 range: Some(range),
1084 }))
1085 }
1086 Hover::KwArg {
1087 name,
1088 callee_name,
1089 range,
1090 } => {
1091 let Some(arg_map) = self.stdlib_args.get(&callee_name) else {
1094 return Ok(None);
1095 };
1096
1097 let Some(tip) = arg_map.get(&name) else {
1098 return Ok(None);
1099 };
1100
1101 Ok(Some(LspHover {
1102 contents: HoverContents::Markup(MarkupContent {
1103 kind: MarkupKind::Markdown,
1104 value: tip.clone(),
1105 }),
1106 range: Some(range),
1107 }))
1108 }
1109 Hover::Variable {
1110 name,
1111 ty: Some(ty),
1112 range,
1113 } => Ok(Some(LspHover {
1114 contents: HoverContents::Markup(MarkupContent {
1115 kind: MarkupKind::Markdown,
1116 value: format!("```\n{}: {}\n```", name, ty),
1117 }),
1118 range: Some(range),
1119 })),
1120 Hover::Variable { name, ty: None, range } => Ok(with_cached_var(&name, |value| {
1121 let mut text: String = format!("```\n{}", name);
1122 if let Some(ty) = value.principal_type() {
1123 text.push_str(&format!(": {}", ty.human_friendly_type()));
1124 }
1125 if let Some(v) = value.value_str() {
1126 text.push_str(&format!(" = {}", v));
1127 }
1128 text.push_str("\n```");
1129
1130 LspHover {
1131 contents: HoverContents::Markup(MarkupContent {
1132 kind: MarkupKind::Markdown,
1133 value: text,
1134 }),
1135 range: Some(range),
1136 }
1137 })
1138 .await),
1139 Hover::Signature { .. } => Ok(None),
1140 Hover::Comment { value, range } => Ok(Some(LspHover {
1141 contents: HoverContents::Markup(MarkupContent {
1142 kind: MarkupKind::Markdown,
1143 value,
1144 }),
1145 range: Some(range),
1146 })),
1147 }
1148 }
1149
1150 async fn completion(&self, params: CompletionParams) -> RpcResult<Option<CompletionResponse>> {
1151 let mut completions = vec![CompletionItem {
1152 label: PIPE_OPERATOR.to_string(),
1153 label_details: None,
1154 kind: Some(CompletionItemKind::OPERATOR),
1155 detail: Some("A pipe operator.".to_string()),
1156 documentation: Some(Documentation::MarkupContent(MarkupContent {
1157 kind: MarkupKind::Markdown,
1158 value: "A pipe operator.".to_string(),
1159 })),
1160 deprecated: Some(false),
1161 preselect: None,
1162 sort_text: None,
1163 filter_text: None,
1164 insert_text: Some("|> ".to_string()),
1165 insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
1166 insert_text_mode: None,
1167 text_edit: None,
1168 additional_text_edits: None,
1169 command: None,
1170 commit_characters: None,
1171 data: None,
1172 tags: None,
1173 }];
1174
1175 let Some(current_code) = self
1177 .code_map
1178 .get(params.text_document_position.text_document.uri.as_ref())
1179 else {
1180 return Ok(Some(CompletionResponse::Array(completions)));
1181 };
1182 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1183 return Ok(Some(CompletionResponse::Array(completions)));
1184 };
1185
1186 if let Some(line) = current_code
1188 .lines()
1189 .nth(params.text_document_position.position.line as usize)
1190 {
1191 let char_pos = params.text_document_position.position.character as usize;
1192 if char_pos <= line.len() {
1193 let line_prefix = &line[..char_pos];
1194 let last_word = line_prefix
1196 .split(|c: char| c.is_whitespace() || c.is_ascii_punctuation())
1197 .next_back()
1198 .unwrap_or("");
1199
1200 if !last_word.is_empty() && last_word.chars().next().unwrap().is_ascii_digit() {
1202 return Ok(None);
1203 }
1204 }
1205 }
1206
1207 completions.extend(self.stdlib_completions.values().cloned());
1208
1209 let Some(ast) = self
1211 .ast_map
1212 .get(params.text_document_position.text_document.uri.as_ref())
1213 else {
1214 return Ok(Some(CompletionResponse::Array(completions)));
1215 };
1216
1217 let Some(current_code) = self
1218 .code_map
1219 .get(params.text_document_position.text_document.uri.as_ref())
1220 else {
1221 return Ok(Some(CompletionResponse::Array(completions)));
1222 };
1223 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1224 return Ok(Some(CompletionResponse::Array(completions)));
1225 };
1226
1227 let position = position_to_char_index(params.text_document_position.position, current_code);
1228 if ast.ast.in_comment(position) {
1229 return Ok(None);
1231 }
1232
1233 let Ok(variables) = ast.ast.completion_items(position) else {
1235 return Ok(Some(CompletionResponse::Array(completions)));
1236 };
1237
1238 completions.extend(variables);
1240
1241 Ok(Some(CompletionResponse::Array(completions)))
1242 }
1243
1244 async fn diagnostic(&self, params: DocumentDiagnosticParams) -> RpcResult<DocumentDiagnosticReportResult> {
1245 let filename = params.text_document.uri.to_string();
1246
1247 let Some(items) = self.diagnostics_map.get(&filename) else {
1249 return Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1251 RelatedFullDocumentDiagnosticReport {
1252 related_documents: None,
1253 full_document_diagnostic_report: FullDocumentDiagnosticReport {
1254 result_id: None,
1255 items: vec![],
1256 },
1257 },
1258 )));
1259 };
1260
1261 Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1262 RelatedFullDocumentDiagnosticReport {
1263 related_documents: None,
1264 full_document_diagnostic_report: FullDocumentDiagnosticReport {
1265 result_id: None,
1266 items: items.clone(),
1267 },
1268 },
1269 )))
1270 }
1271
1272 async fn signature_help(&self, params: SignatureHelpParams) -> RpcResult<Option<SignatureHelp>> {
1273 let filename = params.text_document_position_params.text_document.uri.to_string();
1274
1275 let Some(current_code) = self.code_map.get(&filename) else {
1276 return Ok(None);
1277 };
1278 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1279 return Ok(None);
1280 };
1281
1282 let pos = position_to_char_index(params.text_document_position_params.position, current_code);
1283
1284 let Some(ast) = self.ast_map.get(&filename) else {
1286 return Ok(None);
1287 };
1288
1289 let Some(value) = ast.ast.get_expr_for_position(pos) else {
1290 return Ok(None);
1291 };
1292
1293 let Some(hover) =
1294 value.get_hover_value_for_position(pos, current_code, &HoverOpts::default_for_signature_help())
1295 else {
1296 return Ok(None);
1297 };
1298
1299 match hover {
1300 Hover::Function { name, range: _ } => {
1301 let Some(signature) = self.stdlib_signatures.get(&name) else {
1303 return Ok(None);
1304 };
1305
1306 Ok(Some(signature.clone()))
1307 }
1308 Hover::Signature {
1309 name,
1310 parameter_index,
1311 range: _,
1312 } => {
1313 let Some(signature) = self.stdlib_signatures.get(&name) else {
1314 return Ok(None);
1315 };
1316
1317 let mut signature = signature.clone();
1318
1319 signature.active_parameter = Some(parameter_index);
1320
1321 Ok(Some(signature))
1322 }
1323 _ => {
1324 return Ok(None);
1325 }
1326 }
1327 }
1328
1329 async fn inlay_hint(&self, _params: InlayHintParams) -> RpcResult<Option<Vec<InlayHint>>> {
1330 Ok(None)
1333 }
1334
1335 async fn semantic_tokens_full(&self, params: SemanticTokensParams) -> RpcResult<Option<SemanticTokensResult>> {
1336 let filename = params.text_document.uri.to_string();
1337
1338 let Some(semantic_tokens) = self.semantic_tokens_map.get(&filename) else {
1339 return Ok(None);
1340 };
1341
1342 Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
1343 result_id: None,
1344 data: semantic_tokens.clone(),
1345 })))
1346 }
1347
1348 async fn document_symbol(&self, params: DocumentSymbolParams) -> RpcResult<Option<DocumentSymbolResponse>> {
1349 let filename = params.text_document.uri.to_string();
1350
1351 let Some(symbols) = self.symbols_map.get(&filename) else {
1352 return Ok(None);
1353 };
1354
1355 Ok(Some(DocumentSymbolResponse::Nested(symbols.clone())))
1356 }
1357
1358 async fn formatting(&self, params: DocumentFormattingParams) -> RpcResult<Option<Vec<TextEdit>>> {
1359 let filename = params.text_document.uri.to_string();
1360
1361 let Some(current_code) = self.code_map.get(&filename) else {
1362 return Ok(None);
1363 };
1364 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1365 return Ok(None);
1366 };
1367
1368 let module_id = ModuleId::default();
1372 let Ok(ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
1373 return Ok(None);
1374 };
1375 let recast = ast.recast(
1377 &crate::parsing::ast::types::FormatOptions {
1378 tab_size: params.options.tab_size as usize,
1379 insert_final_newline: params.options.insert_final_newline.unwrap_or(false),
1380 use_tabs: !params.options.insert_spaces,
1381 },
1382 0,
1383 );
1384 let source_range = SourceRange::new(0, current_code.len(), module_id);
1385 let range = source_range.to_lsp_range(current_code);
1386 Ok(Some(vec![TextEdit {
1387 new_text: recast,
1388 range,
1389 }]))
1390 }
1391
1392 async fn rename(&self, params: RenameParams) -> RpcResult<Option<WorkspaceEdit>> {
1393 let filename = params.text_document_position.text_document.uri.to_string();
1394
1395 let Some(current_code) = self.code_map.get(&filename) else {
1396 return Ok(None);
1397 };
1398 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1399 return Ok(None);
1400 };
1401
1402 let module_id = ModuleId::default();
1406 let Ok(mut ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
1407 return Ok(None);
1408 };
1409
1410 let pos = position_to_char_index(params.text_document_position.position, current_code);
1412 ast.rename_symbol(¶ms.new_name, pos);
1414 let recast = ast.recast(&Default::default(), 0);
1416 let source_range = SourceRange::new(0, current_code.len() - 1, module_id);
1417 let range = source_range.to_lsp_range(current_code);
1418 Ok(Some(WorkspaceEdit {
1419 changes: Some(HashMap::from([(
1420 params.text_document_position.text_document.uri,
1421 vec![TextEdit {
1422 new_text: recast,
1423 range,
1424 }],
1425 )])),
1426 document_changes: None,
1427 change_annotations: None,
1428 }))
1429 }
1430
1431 async fn folding_range(&self, params: FoldingRangeParams) -> RpcResult<Option<Vec<FoldingRange>>> {
1432 let filename = params.text_document.uri.to_string();
1433
1434 let Some(ast) = self.ast_map.get(&filename) else {
1436 return Ok(None);
1437 };
1438
1439 let folding_ranges = ast.ast.get_lsp_folding_ranges();
1441
1442 if folding_ranges.is_empty() {
1443 return Ok(None);
1444 }
1445
1446 Ok(Some(folding_ranges))
1447 }
1448
1449 async fn code_action(&self, params: CodeActionParams) -> RpcResult<Option<CodeActionResponse>> {
1450 let actions = params
1451 .context
1452 .diagnostics
1453 .into_iter()
1454 .filter_map(|diagnostic| {
1455 let (suggestion, range) = diagnostic.data.as_ref().and_then(|data| {
1456 serde_json::from_value::<(Suggestion, tower_lsp::lsp_types::Range)>(data.clone()).ok()
1457 })?;
1458 let edit = TextEdit {
1459 range,
1460 new_text: suggestion.insert,
1461 };
1462 let changes = HashMap::from([(params.text_document.uri.clone(), vec![edit])]);
1463 Some(CodeActionOrCommand::CodeAction(CodeAction {
1464 title: suggestion.title,
1465 kind: Some(CodeActionKind::QUICKFIX),
1466 diagnostics: Some(vec![diagnostic]),
1467 edit: Some(WorkspaceEdit {
1468 changes: Some(changes),
1469 document_changes: None,
1470 change_annotations: None,
1471 }),
1472 command: None,
1473 is_preferred: Some(true),
1474 disabled: None,
1475 data: None,
1476 }))
1477 })
1478 .collect();
1479
1480 Ok(Some(actions))
1481 }
1482}
1483
1484pub fn get_completions_from_stdlib(
1486 stdlib: &crate::std::StdLib,
1487 kcl_std: &[DocData],
1488) -> Result<HashMap<String, CompletionItem>> {
1489 let mut completions = HashMap::new();
1490 let combined = stdlib.combined();
1491
1492 for internal_fn in combined.values() {
1493 completions.insert(internal_fn.name(), internal_fn.to_completion_item()?);
1494 }
1495
1496 for d in kcl_std {
1497 completions.insert(d.name().to_owned(), d.to_completion_item());
1498 }
1499
1500 let variable_kinds = VariableKind::to_completion_items();
1501 for variable_kind in variable_kinds {
1502 completions.insert(variable_kind.label.clone(), variable_kind);
1503 }
1504
1505 Ok(completions)
1506}
1507
1508pub fn get_signatures_from_stdlib(stdlib: &crate::std::StdLib, kcl_std: &[DocData]) -> HashMap<String, SignatureHelp> {
1510 let mut signatures = HashMap::new();
1511 let combined = stdlib.combined();
1512
1513 for internal_fn in combined.values() {
1514 signatures.insert(internal_fn.name(), internal_fn.to_signature_help());
1515 }
1516
1517 for d in kcl_std {
1518 if let Some(sig) = d.to_signature_help() {
1519 signatures.insert(d.name().to_owned(), sig);
1520 }
1521 }
1522
1523 signatures
1524}
1525
1526pub fn get_arg_maps_from_stdlib(
1528 stdlib: &crate::std::StdLib,
1529 kcl_std: &[DocData],
1530) -> HashMap<String, HashMap<String, String>> {
1531 let mut result = HashMap::new();
1532 let combined = stdlib.combined();
1533
1534 for internal_fn in combined.values() {
1535 if internal_fn.keyword_arguments() {
1536 let arg_map: HashMap<String, String> = internal_fn
1537 .args(false)
1538 .into_iter()
1539 .map(|data| {
1540 let mut tip = "```\n".to_owned();
1541 tip.push_str(&data.name.clone());
1542 if !data.required {
1543 tip.push('?');
1544 }
1545 if !data.type_.is_empty() {
1546 tip.push_str(": ");
1547 tip.push_str(&data.type_);
1548 }
1549 tip.push_str("\n```");
1550 if !data.description.is_empty() {
1551 tip.push_str("\n\n");
1552 tip.push_str(&data.description);
1553 }
1554 (data.name, tip)
1555 })
1556 .collect();
1557 if !arg_map.is_empty() {
1558 result.insert(internal_fn.name(), arg_map);
1559 }
1560 }
1561 }
1562
1563 for _d in kcl_std {
1564 }
1566
1567 result
1568}
1569
1570fn position_to_char_index(position: Position, code: &str) -> usize {
1572 let mut char_position = 0;
1574 for (index, line) in code.lines().enumerate() {
1575 if index == position.line as usize {
1576 char_position += position.character as usize;
1577 break;
1578 } else {
1579 char_position += line.len() + 1;
1580 }
1581 }
1582
1583 char_position
1584}
1585
1586async fn with_cached_var<T>(name: &str, f: impl Fn(&KclValue) -> T) -> Option<T> {
1587 let mem = cache::read_old_memory().await?;
1588 let value = mem.0.get(name, SourceRange::default()).ok()?;
1589
1590 Some(f(value))
1591}