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 stdlib = crate::std::StdLib::new();
179 let kcl_std = crate::docs::kcl_doc::walk_prelude();
180 let stdlib_completions = get_completions_from_stdlib(&stdlib, &kcl_std).map_err(|e| e.to_string())?;
181 let stdlib_signatures = get_signatures_from_stdlib(&stdlib, &kcl_std);
182 let stdlib_args = get_arg_maps_from_stdlib(&stdlib, &kcl_std);
183
184 Ok(Self {
185 client,
186 fs: Arc::new(fs),
187 stdlib_completions,
188 stdlib_signatures,
189 stdlib_args,
190 zoo_client,
191 can_send_telemetry,
192 can_execute: Arc::new(RwLock::new(executor_ctx.is_some())),
193 executor_ctx: Arc::new(RwLock::new(executor_ctx)),
194 workspace_folders: Default::default(),
195 token_map: Default::default(),
196 ast_map: Default::default(),
197 code_map: Default::default(),
198 diagnostics_map: Default::default(),
199 symbols_map: Default::default(),
200 semantic_tokens_map: Default::default(),
201 is_initialized: Default::default(),
202 })
203 }
204
205 fn remove_from_ast_maps(&self, filename: &str) {
206 self.ast_map.remove(filename);
207 self.symbols_map.remove(filename);
208 }
209}
210
211#[async_trait::async_trait]
213impl crate::lsp::backend::Backend for Backend {
214 fn client(&self) -> &Client {
215 &self.client
216 }
217
218 fn fs(&self) -> &Arc<crate::fs::FileManager> {
219 &self.fs
220 }
221
222 async fn is_initialized(&self) -> bool {
223 *self.is_initialized.read().await
224 }
225
226 async fn set_is_initialized(&self, is_initialized: bool) {
227 *self.is_initialized.write().await = is_initialized;
228 }
229
230 async fn workspace_folders(&self) -> Vec<WorkspaceFolder> {
231 self.workspace_folders.iter().map(|i| i.clone()).collect()
233 }
234
235 async fn add_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
236 for folder in folders {
237 self.workspace_folders.insert(folder.name.to_string(), folder);
238 }
239 }
240
241 async fn remove_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
242 for folder in folders {
243 self.workspace_folders.remove(&folder.name);
244 }
245 }
246
247 fn code_map(&self) -> &DashMap<String, Vec<u8>> {
248 &self.code_map
249 }
250
251 async fn insert_code_map(&self, uri: String, text: Vec<u8>) {
252 self.code_map.insert(uri, text);
253 }
254
255 async fn remove_from_code_map(&self, uri: String) -> Option<Vec<u8>> {
256 self.code_map.remove(&uri).map(|x| x.1)
257 }
258
259 async fn clear_code_state(&self) {
260 self.code_map.clear();
261 self.token_map.clear();
262 self.ast_map.clear();
263 self.diagnostics_map.clear();
264 self.symbols_map.clear();
265 self.semantic_tokens_map.clear();
266 }
267
268 fn current_diagnostics_map(&self) -> &DashMap<String, Vec<Diagnostic>> {
269 &self.diagnostics_map
270 }
271
272 async fn inner_on_change(&self, params: TextDocumentItem, force: bool) {
273 if force {
274 crate::bust_cache().await;
275 }
276
277 let filename = params.uri.to_string();
278 let module_id = ModuleId::default();
282 let tokens = match crate::parsing::token::lex(¶ms.text, module_id) {
283 Ok(tokens) => tokens,
284 Err(err) => {
285 self.add_to_diagnostics(¶ms, &[err], true).await;
286 self.token_map.remove(&filename);
287 self.remove_from_ast_maps(&filename);
288 self.semantic_tokens_map.remove(&filename);
289 return;
290 }
291 };
292
293 let tokens_changed = if let Some(previous_tokens) = self.token_map.get(&filename) {
295 *previous_tokens != tokens
296 } else {
297 true
298 };
299
300 let had_diagnostics = self.has_diagnostics(params.uri.as_ref()).await;
301
302 if !tokens_changed && !force && !had_diagnostics {
304 return;
306 }
307
308 if tokens_changed {
309 self.token_map.insert(params.uri.to_string(), tokens.clone());
311 self.update_semantic_tokens(&tokens, ¶ms).await;
313 }
314
315 let (ast, errs) = match crate::parsing::parse_tokens(tokens.clone()).0 {
318 Ok(result) => result,
319 Err(err) => {
320 self.add_to_diagnostics(¶ms, &[err], true).await;
321 self.remove_from_ast_maps(&filename);
322 return;
323 }
324 };
325
326 self.add_to_diagnostics(¶ms, &errs, true).await;
327
328 if errs.iter().any(|e| e.severity == crate::errors::Severity::Fatal) {
329 self.remove_from_ast_maps(&filename);
330 return;
331 }
332
333 let Some(mut ast) = ast else {
334 self.remove_from_ast_maps(&filename);
335 return;
336 };
337
338 ast.compute_digest();
342
343 let ast = crate::Program {
345 ast,
346 original_file_contents: params.text.clone(),
347 };
348
349 let ast_changed = match self.ast_map.get(&filename) {
351 Some(old_ast) => {
352 *old_ast.ast != *ast.ast
354 }
355 None => true,
356 };
357
358 if !ast_changed && !force && !had_diagnostics {
359 return;
361 }
362
363 if ast_changed {
364 self.ast_map.insert(params.uri.to_string(), ast.clone());
365 self.symbols_map.insert(
367 params.uri.to_string(),
368 ast.ast.get_lsp_symbols(¶ms.text).unwrap_or_default(),
369 );
370
371 self.update_semantic_tokens(&tokens, ¶ms).await;
373
374 let discovered_findings = ast.lint_all().into_iter().flatten().collect::<Vec<_>>();
375 self.add_to_diagnostics(¶ms, &discovered_findings, false).await;
376 }
377
378 if self.can_execute().await || self.executor_ctx().await.is_none() {
380 self.client
383 .send_notification::<custom_notifications::AstUpdated>(ast.ast.clone())
384 .await;
385 }
386
387 if self.execute(¶ms, &ast).await.is_err() {
391 return;
392 }
393
394 self.clear_diagnostics_map(¶ms.uri, Some(DiagnosticSeverity::ERROR))
396 .await;
397 }
398}
399
400impl Backend {
401 pub async fn can_execute(&self) -> bool {
402 *self.can_execute.read().await
403 }
404
405 pub async fn executor_ctx(&self) -> tokio::sync::RwLockReadGuard<'_, Option<crate::execution::ExecutorContext>> {
406 self.executor_ctx.read().await
407 }
408
409 async fn update_semantic_tokens(&self, tokens: &TokenStream, params: &TextDocumentItem) {
410 let mut semantic_tokens = vec![];
412 let mut last_position = Position::new(0, 0);
413 for token in tokens.as_slice() {
414 let Ok(token_type) = SemanticTokenType::try_from(token.token_type) else {
415 continue;
418 };
419
420 let mut token_type_index = match self.get_semantic_token_type_index(&token_type) {
421 Some(index) => index,
422 None => {
425 self.client
426 .log_message(
427 MessageType::ERROR,
428 format!("token type `{:?}` not accounted for", token_type),
429 )
430 .await;
431 continue;
432 }
433 };
434
435 let source_range: SourceRange = token.into();
436 let position = source_range.start_to_lsp_position(¶ms.text);
437
438 let token_modifiers_bitset = if let Some(ast) = self.ast_map.get(params.uri.as_str()) {
441 let token_index = Arc::new(Mutex::new(token_type_index));
442 let modifier_index: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
443 crate::walk::walk(&ast.ast, |node: crate::walk::Node| {
444 let Ok(node_range): Result<SourceRange, _> = (&node).try_into() else {
445 return Ok(true);
446 };
447
448 if !node_range.contains(source_range.start()) {
449 return Ok(true);
450 }
451
452 let get_modifier = |modifier: Vec<SemanticTokenModifier>| -> Result<bool> {
453 let mut mods = modifier_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
454 let Some(token_modifier_index) = self.get_semantic_token_modifier_index(modifier) else {
455 return Ok(true);
456 };
457 if *mods == 0 {
458 *mods = token_modifier_index;
459 } else {
460 *mods |= token_modifier_index;
461 }
462 Ok(false)
463 };
464
465 match node {
466 crate::walk::Node::TagDeclarator(_) => {
467 return get_modifier(vec![
468 SemanticTokenModifier::DEFINITION,
469 SemanticTokenModifier::STATIC,
470 ]);
471 }
472 crate::walk::Node::VariableDeclarator(variable) => {
473 let sr: SourceRange = (&variable.id).into();
474 if sr.contains(source_range.start()) {
475 if let Expr::FunctionExpression(_) = &variable.init {
476 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
477 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::FUNCTION) {
478 Some(index) => index,
479 None => token_type_index,
480 };
481 }
482
483 return get_modifier(vec![
484 SemanticTokenModifier::DECLARATION,
485 SemanticTokenModifier::READONLY,
486 ]);
487 }
488 }
489 crate::walk::Node::Parameter(_) => {
490 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
491 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PARAMETER) {
492 Some(index) => index,
493 None => token_type_index,
494 };
495 return Ok(false);
496 }
497 crate::walk::Node::MemberExpression(member_expression) => {
498 let sr: SourceRange = (&member_expression.property).into();
499 if sr.contains(source_range.start()) {
500 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
501 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PROPERTY) {
502 Some(index) => index,
503 None => token_type_index,
504 };
505 return Ok(false);
506 }
507 }
508 crate::walk::Node::ObjectProperty(object_property) => {
509 let sr: SourceRange = (&object_property.key).into();
510 if sr.contains(source_range.start()) {
511 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
512 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PROPERTY) {
513 Some(index) => index,
514 None => token_type_index,
515 };
516 }
517 return get_modifier(vec![SemanticTokenModifier::DECLARATION]);
518 }
519 crate::walk::Node::CallExpressionKw(call_expr) => {
520 let sr: SourceRange = (&call_expr.callee).into();
521 if sr.contains(source_range.start()) {
522 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
523 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::FUNCTION) {
524 Some(index) => index,
525 None => token_type_index,
526 };
527
528 if self.stdlib_completions.contains_key(&call_expr.callee.name.name) {
529 return get_modifier(vec![SemanticTokenModifier::DEFAULT_LIBRARY]);
531 }
532
533 return Ok(false);
534 }
535 }
536 _ => {}
537 }
538 Ok(true)
539 })
540 .unwrap_or_default();
541
542 let t = if let Ok(guard) = token_index.lock() { *guard } else { 0 };
543 token_type_index = t;
544
545 let m = if let Ok(guard) = modifier_index.lock() {
546 *guard
547 } else {
548 0
549 };
550 m
551 } else {
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 = if let Some(items) = self.diagnostics_map.get(params.uri.as_str()) {
657 items.clone()
659 } else {
660 vec![]
661 };
662
663 for diagnostic in diagnostics {
664 let lsp_d = diagnostic.to_lsp_diagnostics(¶ms.text);
665 for d in lsp_d {
667 if !items.iter().any(|x| x == &d) {
668 items.push(d);
669 }
670 }
671 }
672
673 self.diagnostics_map.insert(params.uri.to_string(), items.clone());
674
675 self.client.publish_diagnostics(params.uri.clone(), items, None).await;
676 }
677
678 async fn execute(&self, params: &TextDocumentItem, ast: &Program) -> Result<()> {
679 if !self.can_execute().await {
681 return Ok(());
682 }
683
684 let ctx = self.executor_ctx().await;
686 let Some(ref executor_ctx) = *ctx else {
687 return Ok(());
688 };
689
690 if !self.is_initialized().await {
691 return Ok(());
693 }
694
695 match executor_ctx.run_with_caching(ast.clone()).await {
696 Err(err) => {
697 self.add_to_diagnostics(params, &[err], false).await;
698
699 Err(anyhow::anyhow!("failed to execute code"))
702 }
703 Ok(_) => Ok(()),
704 }
705 }
706
707 pub fn get_semantic_token_type_index(&self, token_type: &SemanticTokenType) -> Option<u32> {
708 SEMANTIC_TOKEN_TYPES
709 .iter()
710 .position(|x| *x == *token_type)
711 .map(|y| y as u32)
712 }
713
714 pub fn get_semantic_token_modifier_index(&self, token_types: Vec<SemanticTokenModifier>) -> Option<u32> {
715 if token_types.is_empty() {
716 return None;
717 }
718
719 let mut modifier = None;
720 for token_type in token_types {
721 if let Some(index) = SEMANTIC_TOKEN_MODIFIERS
722 .iter()
723 .position(|x| *x == token_type)
724 .map(|y| y as u32)
725 {
726 modifier = match modifier {
727 Some(modifier) => Some(modifier | index),
728 None => Some(index),
729 };
730 }
731 }
732 modifier
733 }
734
735 pub async fn create_zip(&self) -> Result<Vec<u8>> {
736 let mut buf = vec![];
738 let mut zip = zip::ZipWriter::new(std::io::Cursor::new(&mut buf));
739 for code in self.code_map.iter() {
740 let entry = code.key();
741 let value = code.value();
742 let file_name = entry.replace("file://", "").to_string();
743
744 let options = zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
745 zip.start_file(file_name, options)?;
746 zip.write_all(value)?;
747 }
748 zip.finish()?;
751
752 Ok(buf)
753 }
754
755 pub async fn send_telemetry(&self) -> Result<()> {
756 let user = self
758 .zoo_client
759 .users()
760 .get_self()
761 .await
762 .map_err(|e| anyhow::anyhow!(e.to_string()))?;
763
764 let mut hasher = sha2::Sha256::new();
767 hasher.update(user.id);
769 let result = hasher.finalize();
771 let user_id_hash = format!("{:x}", result);
773
774 let workspace_folders = self.workspace_folders().await;
777 let project_names: Vec<&str> = workspace_folders.iter().map(|v| v.name.as_str()).collect::<Vec<_>>();
778 let project_name = project_names
780 .first()
781 .ok_or_else(|| anyhow::anyhow!("no project names"))?
782 .to_string();
783
784 self.zoo_client
786 .meta()
787 .create_event(
788 vec![kittycad::types::multipart::Attachment {
789 name: "attachment".to_string(),
791 filepath: Some("attachment.zip".into()),
792 content_type: Some("application/x-zip".to_string()),
793 data: self.create_zip().await?,
794 }],
795 &kittycad::types::Event {
796 attachment_uri: None,
798 created_at: chrono::Utc::now(),
799 event_type: kittycad::types::ModelingAppEventType::SuccessfulCompileBeforeClose,
800 last_compiled_at: Some(chrono::Utc::now()),
801 project_description: None,
803 project_name,
804 source_id: uuid::Uuid::from_str("70178592-dfca-47b3-bd2d-6fce2bcaee04").unwrap(),
807 type_: kittycad::types::Type::ModelingAppEvent,
808 user_id: user_id_hash,
809 },
810 )
811 .await
812 .map_err(|e| anyhow::anyhow!(e.to_string()))?;
813
814 Ok(())
815 }
816
817 pub async fn update_can_execute(
818 &self,
819 params: custom_notifications::UpdateCanExecuteParams,
820 ) -> RpcResult<custom_notifications::UpdateCanExecuteResponse> {
821 let mut can_execute = self.can_execute.write().await;
822
823 if *can_execute == params.can_execute {
824 return Ok(custom_notifications::UpdateCanExecuteResponse {});
825 }
826
827 *can_execute = params.can_execute;
828
829 Ok(custom_notifications::UpdateCanExecuteResponse {})
830 }
831
832 pub fn inner_prepare_rename(
834 &self,
835 params: &TextDocumentPositionParams,
836 new_name: &str,
837 ) -> RpcResult<Option<(String, String)>> {
838 let filename = params.text_document.uri.to_string();
839
840 let Some(current_code) = self.code_map.get(&filename) else {
841 return Ok(None);
842 };
843 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
844 return Ok(None);
845 };
846
847 let module_id = ModuleId::default();
851 let Ok(mut ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
852 return Ok(None);
853 };
854
855 let pos = position_to_char_index(params.position, current_code);
857 ast.rename_symbol(new_name, pos);
859 let recast = ast.recast(&Default::default(), 0);
861
862 Ok(Some((current_code.to_string(), recast)))
863 }
864}
865
866#[tower_lsp::async_trait]
867impl LanguageServer for Backend {
868 async fn initialize(&self, params: InitializeParams) -> RpcResult<InitializeResult> {
869 self.client
870 .log_message(MessageType::INFO, format!("initialize: {:?}", params))
871 .await;
872
873 Ok(InitializeResult {
874 capabilities: ServerCapabilities {
875 color_provider: Some(ColorProviderCapability::Simple(true)),
876 code_action_provider: Some(CodeActionProviderCapability::Options(CodeActionOptions {
877 code_action_kinds: Some(vec![CodeActionKind::QUICKFIX]),
878 resolve_provider: Some(false),
879 work_done_progress_options: WorkDoneProgressOptions::default(),
880 })),
881 completion_provider: Some(CompletionOptions {
882 resolve_provider: Some(false),
883 trigger_characters: Some(vec![".".to_string()]),
884 work_done_progress_options: Default::default(),
885 all_commit_characters: None,
886 ..Default::default()
887 }),
888 diagnostic_provider: Some(DiagnosticServerCapabilities::Options(DiagnosticOptions {
889 ..Default::default()
890 })),
891 document_formatting_provider: Some(OneOf::Left(true)),
892 folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
893 hover_provider: Some(HoverProviderCapability::Simple(true)),
894 inlay_hint_provider: Some(OneOf::Left(true)),
895 rename_provider: Some(OneOf::Left(true)),
896 semantic_tokens_provider: Some(SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(
897 SemanticTokensRegistrationOptions {
898 text_document_registration_options: {
899 TextDocumentRegistrationOptions {
900 document_selector: Some(vec![DocumentFilter {
901 language: Some("kcl".to_string()),
902 scheme: Some("file".to_string()),
903 pattern: None,
904 }]),
905 }
906 },
907 semantic_tokens_options: SemanticTokensOptions {
908 work_done_progress_options: WorkDoneProgressOptions::default(),
909 legend: SemanticTokensLegend {
910 token_types: SEMANTIC_TOKEN_TYPES.to_vec(),
911 token_modifiers: SEMANTIC_TOKEN_MODIFIERS.to_vec(),
912 },
913 range: Some(false),
914 full: Some(SemanticTokensFullOptions::Bool(true)),
915 },
916 static_registration_options: StaticRegistrationOptions::default(),
917 },
918 )),
919 signature_help_provider: Some(SignatureHelpOptions {
920 trigger_characters: None,
921 retrigger_characters: None,
922 ..Default::default()
923 }),
924 text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
925 open_close: Some(true),
926 change: Some(TextDocumentSyncKind::FULL),
927 ..Default::default()
928 })),
929 workspace: Some(WorkspaceServerCapabilities {
930 workspace_folders: Some(WorkspaceFoldersServerCapabilities {
931 supported: Some(true),
932 change_notifications: Some(OneOf::Left(true)),
933 }),
934 file_operations: None,
935 }),
936 ..Default::default()
937 },
938 ..Default::default()
939 })
940 }
941
942 async fn initialized(&self, params: InitializedParams) {
943 self.do_initialized(params).await
944 }
945
946 async fn shutdown(&self) -> RpcResult<()> {
947 self.do_shutdown().await
948 }
949
950 async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
951 self.do_did_change_workspace_folders(params).await
952 }
953
954 async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
955 self.do_did_change_configuration(params).await
956 }
957
958 async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
959 self.do_did_change_watched_files(params).await
960 }
961
962 async fn did_create_files(&self, params: CreateFilesParams) {
963 self.do_did_create_files(params).await
964 }
965
966 async fn did_rename_files(&self, params: RenameFilesParams) {
967 self.do_did_rename_files(params).await
968 }
969
970 async fn did_delete_files(&self, params: DeleteFilesParams) {
971 self.do_did_delete_files(params).await
972 }
973
974 async fn did_open(&self, params: DidOpenTextDocumentParams) {
975 self.do_did_open(params).await
976 }
977
978 async fn did_change(&self, params: DidChangeTextDocumentParams) {
979 self.do_did_change(params).await;
980 }
981
982 async fn did_save(&self, params: DidSaveTextDocumentParams) {
983 self.do_did_save(params).await
984 }
985
986 async fn did_close(&self, params: DidCloseTextDocumentParams) {
987 self.do_did_close(params).await;
988
989 if !self.can_send_telemetry {
992 return;
993 }
994
995 #[cfg(target_arch = "wasm32")]
997 {
998 let be = self.clone();
999 wasm_bindgen_futures::spawn_local(async move {
1000 if let Err(err) = be.send_telemetry().await {
1001 be.client
1002 .log_message(MessageType::WARNING, format!("failed to send telemetry: {}", err))
1003 .await;
1004 }
1005 });
1006 }
1007 #[cfg(not(target_arch = "wasm32"))]
1008 if let Err(err) = self.send_telemetry().await {
1009 self.client
1010 .log_message(MessageType::WARNING, format!("failed to send telemetry: {}", err))
1011 .await;
1012 }
1013 }
1014
1015 async fn hover(&self, params: HoverParams) -> RpcResult<Option<LspHover>> {
1016 let filename = params.text_document_position_params.text_document.uri.to_string();
1017
1018 let Some(current_code) = self.code_map.get(&filename) else {
1019 return Ok(None);
1020 };
1021 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1022 return Ok(None);
1023 };
1024
1025 let pos = position_to_char_index(params.text_document_position_params.position, current_code);
1026
1027 let Some(ast) = self.ast_map.get(&filename) else {
1029 return Ok(None);
1030 };
1031
1032 let Some(hover) = ast
1033 .ast
1034 .get_hover_value_for_position(pos, current_code, &HoverOpts::default_for_hover())
1035 else {
1036 return Ok(None);
1037 };
1038
1039 match hover {
1040 Hover::Function { name, range } => {
1041 let (sig, docs) = if let Some(Some(result)) = with_cached_var(&name, |value| {
1042 match value {
1043 KclValue::Function {
1045 value: FunctionSource::User { ast, .. },
1046 ..
1047 } => {
1048 Some((ast.signature(), ""))
1050 }
1051 _ => None,
1052 }
1053 })
1054 .await
1055 {
1056 result
1057 } else {
1058 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 let Some(label_details) = &completion.label_details else {
1079 return Ok(None);
1080 };
1081
1082 let sig = if let Some(detail) = &label_details.detail {
1083 detail.clone()
1084 } else {
1085 String::new()
1086 };
1087
1088 (sig, docs)
1089 };
1090
1091 Ok(Some(LspHover {
1092 contents: HoverContents::Markup(MarkupContent {
1093 kind: MarkupKind::Markdown,
1094 value: format!("```\n{}{}\n```\n\n{}", name, sig, docs),
1095 }),
1096 range: Some(range),
1097 }))
1098 }
1099 Hover::Type { name, range } => {
1100 let Some(completion) = self.stdlib_completions.get(&name) else {
1101 return Ok(None);
1102 };
1103 let Some(docs) = &completion.documentation else {
1104 return Ok(None);
1105 };
1106
1107 let docs = match docs {
1108 Documentation::String(docs) => docs,
1109 Documentation::MarkupContent(MarkupContent { value, .. }) => value,
1110 };
1111
1112 let docs = if docs.len() > 320 {
1113 let end = docs.find("\n\n").or_else(|| docs.find("\n\r\n")).unwrap_or(320);
1114 &docs[..end]
1115 } else {
1116 &**docs
1117 };
1118
1119 Ok(Some(LspHover {
1120 contents: HoverContents::Markup(MarkupContent {
1121 kind: MarkupKind::Markdown,
1122 value: format!("```\n{}\n```\n\n{}", name, docs),
1123 }),
1124 range: Some(range),
1125 }))
1126 }
1127 Hover::KwArg {
1128 name,
1129 callee_name,
1130 range,
1131 } => {
1132 let Some(arg_map) = self.stdlib_args.get(&callee_name) else {
1135 return Ok(None);
1136 };
1137
1138 let Some(tip) = arg_map.get(&name) else {
1139 return Ok(None);
1140 };
1141
1142 Ok(Some(LspHover {
1143 contents: HoverContents::Markup(MarkupContent {
1144 kind: MarkupKind::Markdown,
1145 value: tip.clone(),
1146 }),
1147 range: Some(range),
1148 }))
1149 }
1150 Hover::Variable {
1151 name,
1152 ty: Some(ty),
1153 range,
1154 } => Ok(Some(LspHover {
1155 contents: HoverContents::Markup(MarkupContent {
1156 kind: MarkupKind::Markdown,
1157 value: format!("```\n{}: {}\n```", name, ty),
1158 }),
1159 range: Some(range),
1160 })),
1161 Hover::Variable { name, ty: None, range } => Ok(with_cached_var(&name, |value| {
1162 let mut text: String = format!("```\n{}", name);
1163 if let Some(ty) = value.principal_type() {
1164 text.push_str(&format!(": {}", ty.human_friendly_type()));
1165 }
1166 if let Some(v) = value.value_str() {
1167 text.push_str(&format!(" = {}", v));
1168 }
1169 text.push_str("\n```");
1170
1171 LspHover {
1172 contents: HoverContents::Markup(MarkupContent {
1173 kind: MarkupKind::Markdown,
1174 value: text,
1175 }),
1176 range: Some(range),
1177 }
1178 })
1179 .await),
1180 Hover::Signature { .. } => Ok(None),
1181 Hover::Comment { value, range } => Ok(Some(LspHover {
1182 contents: HoverContents::Markup(MarkupContent {
1183 kind: MarkupKind::Markdown,
1184 value,
1185 }),
1186 range: Some(range),
1187 })),
1188 }
1189 }
1190
1191 async fn completion(&self, params: CompletionParams) -> RpcResult<Option<CompletionResponse>> {
1192 let mut completions = vec![CompletionItem {
1194 label: PIPE_OPERATOR.to_string(),
1195 label_details: None,
1196 kind: Some(CompletionItemKind::OPERATOR),
1197 detail: Some("A pipe operator.".to_string()),
1198 documentation: Some(Documentation::MarkupContent(MarkupContent {
1199 kind: MarkupKind::Markdown,
1200 value: "A pipe operator.".to_string(),
1201 })),
1202 deprecated: Some(false),
1203 preselect: None,
1204 sort_text: None,
1205 filter_text: None,
1206 insert_text: Some("|> ".to_string()),
1207 insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
1208 insert_text_mode: None,
1209 text_edit: None,
1210 additional_text_edits: None,
1211 command: None,
1212 commit_characters: None,
1213 data: None,
1214 tags: None,
1215 }];
1216
1217 let Some(current_code) = self
1219 .code_map
1220 .get(params.text_document_position.text_document.uri.as_ref())
1221 else {
1222 return Ok(Some(CompletionResponse::Array(completions)));
1223 };
1224 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1225 return Ok(Some(CompletionResponse::Array(completions)));
1226 };
1227
1228 if let Some(line) = current_code
1230 .lines()
1231 .nth(params.text_document_position.position.line as usize)
1232 {
1233 let char_pos = params.text_document_position.position.character as usize;
1234 if char_pos <= line.len() {
1235 let line_prefix = &line[..char_pos];
1236 let last_word = line_prefix
1238 .split(|c: char| c.is_whitespace() || c.is_ascii_punctuation())
1239 .next_back()
1240 .unwrap_or("");
1241
1242 if !last_word.is_empty() && last_word.chars().next().unwrap().is_ascii_digit() {
1244 return Ok(None);
1245 }
1246 }
1247 }
1248
1249 completions.extend(self.stdlib_completions.values().cloned());
1250
1251 let Some(ast) = self
1253 .ast_map
1254 .get(params.text_document_position.text_document.uri.as_ref())
1255 else {
1256 return Ok(Some(CompletionResponse::Array(completions)));
1257 };
1258
1259 let Some(current_code) = self
1260 .code_map
1261 .get(params.text_document_position.text_document.uri.as_ref())
1262 else {
1263 return Ok(Some(CompletionResponse::Array(completions)));
1264 };
1265 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1266 return Ok(Some(CompletionResponse::Array(completions)));
1267 };
1268
1269 let position = position_to_char_index(params.text_document_position.position, current_code);
1270 if ast.ast.in_comment(position) {
1271 return Ok(None);
1273 }
1274
1275 let Ok(variables) = ast.ast.completion_items(position) else {
1277 return Ok(Some(CompletionResponse::Array(completions)));
1278 };
1279
1280 completions.extend(variables);
1282
1283 Ok(Some(CompletionResponse::Array(completions)))
1284 }
1285
1286 async fn diagnostic(&self, params: DocumentDiagnosticParams) -> RpcResult<DocumentDiagnosticReportResult> {
1287 let filename = params.text_document.uri.to_string();
1288
1289 let Some(items) = self.diagnostics_map.get(&filename) else {
1291 return Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1293 RelatedFullDocumentDiagnosticReport {
1294 related_documents: None,
1295 full_document_diagnostic_report: FullDocumentDiagnosticReport {
1296 result_id: None,
1297 items: vec![],
1298 },
1299 },
1300 )));
1301 };
1302
1303 Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1304 RelatedFullDocumentDiagnosticReport {
1305 related_documents: None,
1306 full_document_diagnostic_report: FullDocumentDiagnosticReport {
1307 result_id: None,
1308 items: items.clone(),
1309 },
1310 },
1311 )))
1312 }
1313
1314 async fn signature_help(&self, params: SignatureHelpParams) -> RpcResult<Option<SignatureHelp>> {
1315 let filename = params.text_document_position_params.text_document.uri.to_string();
1316
1317 let Some(current_code) = self.code_map.get(&filename) else {
1318 return Ok(None);
1319 };
1320 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1321 return Ok(None);
1322 };
1323
1324 let pos = position_to_char_index(params.text_document_position_params.position, current_code);
1325
1326 let Some(ch) = current_code.chars().nth(pos) else {
1328 return Ok(None);
1329 };
1330
1331 let check_char = |ch: char| {
1332 if ch == '(' {
1336 let next_space = if ch != ' ' {
1340 if let Some(next_space) = current_code[pos..].find(' ') {
1341 pos + next_space
1342 } else if let Some(next_space) = current_code[pos..].find('(') {
1343 pos + next_space
1344 } else {
1345 pos
1346 }
1347 } else {
1348 pos
1349 };
1350 let p2 = std::cmp::max(pos, next_space);
1351
1352 let last_word = current_code[..p2].split_whitespace().last()?;
1353
1354 return self.stdlib_signatures.get(last_word);
1356 } else if ch == ',' {
1357 let last_paren = current_code[..pos].rfind('(')?;
1362 let last_word = current_code[..last_paren].split_whitespace().last()?;
1364 return self.stdlib_signatures.get(last_word);
1366 }
1367
1368 None
1369 };
1370
1371 if let Some(signature) = check_char(ch) {
1372 return Ok(Some(signature.clone()));
1373 }
1374
1375 if let Some(context) = params.context {
1377 if let Some(character) = context.trigger_character {
1378 for character in character.chars() {
1379 if character == '(' || character == ',' {
1381 if let Some(signature) = check_char(character) {
1382 return Ok(Some(signature.clone()));
1383 }
1384 }
1385 }
1386 }
1387 }
1388
1389 let Some(ast) = self.ast_map.get(&filename) else {
1391 return Ok(None);
1392 };
1393
1394 let Some(value) = ast.ast.get_expr_for_position(pos) else {
1395 return Ok(None);
1396 };
1397
1398 let Some(hover) =
1399 value.get_hover_value_for_position(pos, current_code, &HoverOpts::default_for_signature_help())
1400 else {
1401 return Ok(None);
1402 };
1403
1404 match hover {
1405 Hover::Function { name, range: _ } => {
1406 let Some(signature) = self.stdlib_signatures.get(&name) else {
1408 return Ok(None);
1409 };
1410
1411 Ok(Some(signature.clone()))
1412 }
1413 Hover::Signature {
1414 name,
1415 parameter_index,
1416 range: _,
1417 } => {
1418 let Some(signature) = self.stdlib_signatures.get(&name) else {
1419 return Ok(None);
1420 };
1421
1422 let mut signature = signature.clone();
1423
1424 signature.active_parameter = Some(parameter_index);
1425
1426 Ok(Some(signature))
1427 }
1428 _ => {
1429 return Ok(None);
1430 }
1431 }
1432 }
1433
1434 async fn inlay_hint(&self, _params: InlayHintParams) -> RpcResult<Option<Vec<InlayHint>>> {
1435 Ok(None)
1438 }
1439
1440 async fn semantic_tokens_full(&self, params: SemanticTokensParams) -> RpcResult<Option<SemanticTokensResult>> {
1441 let filename = params.text_document.uri.to_string();
1442
1443 let Some(semantic_tokens) = self.semantic_tokens_map.get(&filename) else {
1444 return Ok(None);
1445 };
1446
1447 Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
1448 result_id: None,
1449 data: semantic_tokens.clone(),
1450 })))
1451 }
1452
1453 async fn document_symbol(&self, params: DocumentSymbolParams) -> RpcResult<Option<DocumentSymbolResponse>> {
1454 let filename = params.text_document.uri.to_string();
1455
1456 let Some(symbols) = self.symbols_map.get(&filename) else {
1457 return Ok(None);
1458 };
1459
1460 Ok(Some(DocumentSymbolResponse::Nested(symbols.clone())))
1461 }
1462
1463 async fn formatting(&self, params: DocumentFormattingParams) -> RpcResult<Option<Vec<TextEdit>>> {
1464 let filename = params.text_document.uri.to_string();
1465
1466 let Some(current_code) = self.code_map.get(&filename) else {
1467 return Ok(None);
1468 };
1469 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1470 return Ok(None);
1471 };
1472
1473 let module_id = ModuleId::default();
1477 let Ok(ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
1478 return Ok(None);
1479 };
1480 let recast = ast.recast(
1482 &crate::parsing::ast::types::FormatOptions {
1483 tab_size: params.options.tab_size as usize,
1484 insert_final_newline: params.options.insert_final_newline.unwrap_or(false),
1485 use_tabs: !params.options.insert_spaces,
1486 },
1487 0,
1488 );
1489 let source_range = SourceRange::new(0, current_code.len(), module_id);
1490 let range = source_range.to_lsp_range(current_code);
1491 Ok(Some(vec![TextEdit {
1492 new_text: recast,
1493 range,
1494 }]))
1495 }
1496
1497 async fn rename(&self, params: RenameParams) -> RpcResult<Option<WorkspaceEdit>> {
1498 let Some((current_code, new_code)) =
1499 self.inner_prepare_rename(¶ms.text_document_position, ¶ms.new_name)?
1500 else {
1501 return Ok(None);
1502 };
1503
1504 let source_range = SourceRange::new(0, current_code.len(), ModuleId::default());
1505 let range = source_range.to_lsp_range(¤t_code);
1506 Ok(Some(WorkspaceEdit {
1507 changes: Some(HashMap::from([(
1508 params.text_document_position.text_document.uri,
1509 vec![TextEdit {
1510 new_text: new_code,
1511 range,
1512 }],
1513 )])),
1514 document_changes: None,
1515 change_annotations: None,
1516 }))
1517 }
1518
1519 async fn prepare_rename(&self, params: TextDocumentPositionParams) -> RpcResult<Option<PrepareRenameResponse>> {
1520 if self
1521 .inner_prepare_rename(¶ms, "someNameNoOneInTheirRightMindWouldEverUseForTesting")?
1522 .is_none()
1523 {
1524 return Ok(None);
1525 }
1526
1527 Ok(Some(PrepareRenameResponse::DefaultBehavior { default_behavior: true }))
1529 }
1530
1531 async fn folding_range(&self, params: FoldingRangeParams) -> RpcResult<Option<Vec<FoldingRange>>> {
1532 let filename = params.text_document.uri.to_string();
1533
1534 let Some(ast) = self.ast_map.get(&filename) else {
1536 return Ok(None);
1537 };
1538
1539 let folding_ranges = ast.ast.get_lsp_folding_ranges();
1541
1542 if folding_ranges.is_empty() {
1543 return Ok(None);
1544 }
1545
1546 Ok(Some(folding_ranges))
1547 }
1548
1549 async fn code_action(&self, params: CodeActionParams) -> RpcResult<Option<CodeActionResponse>> {
1550 let actions = params
1551 .context
1552 .diagnostics
1553 .into_iter()
1554 .filter_map(|diagnostic| {
1555 let (suggestion, range) = diagnostic
1556 .data
1557 .as_ref()
1558 .and_then(|data| serde_json::from_value::<LspSuggestion>(data.clone()).ok())?;
1559 let edit = TextEdit {
1560 range,
1561 new_text: suggestion.insert,
1562 };
1563 let changes = HashMap::from([(params.text_document.uri.clone(), vec![edit])]);
1564
1565 Some(CodeActionOrCommand::CodeAction(CodeAction {
1568 title: suggestion.title,
1569 kind: Some(CodeActionKind::QUICKFIX),
1570 diagnostics: Some(vec![diagnostic]),
1571 edit: Some(WorkspaceEdit {
1572 changes: Some(changes),
1573 document_changes: None,
1574 change_annotations: None,
1575 }),
1576 command: None,
1577 is_preferred: Some(true),
1578 disabled: None,
1579 data: None,
1580 }))
1581 })
1582 .collect();
1583
1584 Ok(Some(actions))
1585 }
1586
1587 async fn document_color(&self, params: DocumentColorParams) -> RpcResult<Vec<ColorInformation>> {
1588 let filename = params.text_document.uri.to_string();
1589
1590 let Some(current_code) = self.code_map.get(&filename) else {
1591 return Ok(vec![]);
1592 };
1593 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1594 return Ok(vec![]);
1595 };
1596
1597 let Some(ast) = self.ast_map.get(&filename) else {
1599 return Ok(vec![]);
1600 };
1601
1602 let Ok(colors) = ast.ast.document_color(current_code) else {
1604 return Ok(vec![]);
1605 };
1606
1607 Ok(colors)
1608 }
1609
1610 async fn color_presentation(&self, params: ColorPresentationParams) -> RpcResult<Vec<ColorPresentation>> {
1611 let filename = params.text_document.uri.to_string();
1612
1613 let Some(current_code) = self.code_map.get(&filename) else {
1614 return Ok(vec![]);
1615 };
1616 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1617 return Ok(vec![]);
1618 };
1619
1620 let Some(ast) = self.ast_map.get(&filename) else {
1622 return Ok(vec![]);
1623 };
1624
1625 let pos_start = position_to_char_index(params.range.start, current_code);
1626 let pos_end = position_to_char_index(params.range.end, current_code);
1627
1628 let Ok(Some(presentation)) = ast.ast.color_presentation(¶ms.color, pos_start, pos_end) else {
1630 return Ok(vec![]);
1631 };
1632
1633 Ok(vec![presentation])
1634 }
1635}
1636
1637pub fn get_completions_from_stdlib(
1639 stdlib: &crate::std::StdLib,
1640 kcl_std: &ModData,
1641) -> Result<HashMap<String, CompletionItem>> {
1642 let mut completions = HashMap::new();
1643 let combined = stdlib.combined();
1644
1645 for internal_fn in combined.values() {
1646 completions.insert(internal_fn.name(), internal_fn.to_completion_item()?);
1647 }
1648
1649 for d in kcl_std.all_docs() {
1650 if let Some(ci) = d.to_completion_item() {
1651 completions.insert(d.name().to_owned(), ci);
1652 }
1653 }
1654
1655 let variable_kinds = VariableKind::to_completion_items();
1656 for variable_kind in variable_kinds {
1657 completions.insert(variable_kind.label.clone(), variable_kind);
1658 }
1659
1660 Ok(completions)
1661}
1662
1663pub fn get_signatures_from_stdlib(stdlib: &crate::std::StdLib, kcl_std: &ModData) -> HashMap<String, SignatureHelp> {
1665 let mut signatures = HashMap::new();
1666 let combined = stdlib.combined();
1667
1668 for internal_fn in combined.values() {
1669 signatures.insert(internal_fn.name(), internal_fn.to_signature_help());
1670 }
1671
1672 for d in kcl_std.all_docs() {
1673 if let Some(sig) = d.to_signature_help() {
1674 signatures.insert(d.name().to_owned(), sig);
1675 }
1676 }
1677
1678 signatures
1679}
1680
1681pub fn get_arg_maps_from_stdlib(
1683 stdlib: &crate::std::StdLib,
1684 kcl_std: &ModData,
1685) -> HashMap<String, HashMap<String, String>> {
1686 let mut result = HashMap::new();
1687 let combined = stdlib.combined();
1688
1689 for internal_fn in combined.values() {
1690 let arg_map: HashMap<String, String> = internal_fn
1691 .args(false)
1692 .into_iter()
1693 .map(|data| {
1694 let mut tip = "```\n".to_owned();
1695 tip.push_str(&data.name.clone());
1696 if !data.required {
1697 tip.push('?');
1698 }
1699 if !data.type_.is_empty() {
1700 tip.push_str(": ");
1701 tip.push_str(&data.type_);
1702 }
1703 tip.push_str("\n```");
1704 if !data.description.is_empty() {
1705 tip.push_str("\n\n");
1706 tip.push_str(&data.description);
1707 }
1708 (data.name, tip)
1709 })
1710 .collect();
1711 if !arg_map.is_empty() {
1712 result.insert(internal_fn.name(), arg_map);
1713 }
1714 }
1715
1716 for _d in kcl_std.all_docs() {
1717 }
1719
1720 result
1721}
1722
1723fn position_to_char_index(position: Position, code: &str) -> usize {
1725 let mut char_position = 0;
1727 for (index, line) in code.lines().enumerate() {
1728 if index == position.line as usize {
1729 char_position += position.character as usize;
1730 break;
1731 } else {
1732 char_position += line.len() + 1;
1733 }
1734 }
1735
1736 std::cmp::min(char_position, code.len() - 1)
1737}
1738
1739async fn with_cached_var<T>(name: &str, f: impl Fn(&KclValue) -> T) -> Option<T> {
1740 let mem = cache::read_old_memory().await?;
1741 let value = mem.0.get(name, SourceRange::default()).ok()?;
1742
1743 Some(f(value))
1744}
1745
1746#[cfg(test)]
1747mod tests {
1748 use pretty_assertions::assert_eq;
1749
1750 use super::*;
1751
1752 #[test]
1753 fn test_position_to_char_index_first_line() {
1754 let code = r#"def foo():
1755return 42"#;
1756 let position = Position::new(0, 3);
1757 let index = position_to_char_index(position, code);
1758 assert_eq!(index, 3);
1759 }
1760
1761 #[test]
1762 fn test_position_to_char_index() {
1763 let code = r#"def foo():
1764return 42"#;
1765 let position = Position::new(1, 4);
1766 let index = position_to_char_index(position, code);
1767 assert_eq!(index, 15);
1768 }
1769
1770 #[test]
1771 fn test_position_to_char_index_with_newline() {
1772 let code = r#"def foo():
1773
1774return 42"#;
1775 let position = Position::new(2, 0);
1776 let index = position_to_char_index(position, code);
1777 assert_eq!(index, 12);
1778 }
1779
1780 #[test]
1781 fn test_position_to_char_at_end() {
1782 let code = r#"def foo():
1783return 42"#;
1784
1785 let position = Position::new(1, 8);
1786 let index = position_to_char_index(position, code);
1787 assert_eq!(index, 19);
1788 }
1789}