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 {
1193 label: PIPE_OPERATOR.to_string(),
1194 label_details: None,
1195 kind: Some(CompletionItemKind::OPERATOR),
1196 detail: Some("A pipe operator.".to_string()),
1197 documentation: Some(Documentation::MarkupContent(MarkupContent {
1198 kind: MarkupKind::Markdown,
1199 value: "A pipe operator.".to_string(),
1200 })),
1201 deprecated: Some(false),
1202 preselect: None,
1203 sort_text: None,
1204 filter_text: None,
1205 insert_text: Some("|> ".to_string()),
1206 insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
1207 insert_text_mode: None,
1208 text_edit: None,
1209 additional_text_edits: None,
1210 command: None,
1211 commit_characters: None,
1212 data: None,
1213 tags: None,
1214 }];
1215
1216 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 if let Some(line) = current_code
1229 .lines()
1230 .nth(params.text_document_position.position.line as usize)
1231 {
1232 let char_pos = params.text_document_position.position.character as usize;
1233 if char_pos <= line.len() {
1234 let line_prefix = &line[..char_pos];
1235 let last_word = line_prefix
1237 .split(|c: char| c.is_whitespace() || c.is_ascii_punctuation())
1238 .next_back()
1239 .unwrap_or("");
1240
1241 if !last_word.is_empty() && last_word.chars().next().unwrap().is_ascii_digit() {
1243 return Ok(None);
1244 }
1245 }
1246 }
1247
1248 completions.extend(self.stdlib_completions.values().cloned());
1249
1250 let Some(ast) = self
1252 .ast_map
1253 .get(params.text_document_position.text_document.uri.as_ref())
1254 else {
1255 return Ok(Some(CompletionResponse::Array(completions)));
1256 };
1257
1258 let Some(current_code) = self
1259 .code_map
1260 .get(params.text_document_position.text_document.uri.as_ref())
1261 else {
1262 return Ok(Some(CompletionResponse::Array(completions)));
1263 };
1264 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1265 return Ok(Some(CompletionResponse::Array(completions)));
1266 };
1267
1268 let position = position_to_char_index(params.text_document_position.position, current_code);
1269 if ast.ast.in_comment(position) {
1270 return Ok(None);
1272 }
1273
1274 let Ok(variables) = ast.ast.completion_items(position) else {
1276 return Ok(Some(CompletionResponse::Array(completions)));
1277 };
1278
1279 completions.extend(variables);
1281
1282 Ok(Some(CompletionResponse::Array(completions)))
1283 }
1284
1285 async fn diagnostic(&self, params: DocumentDiagnosticParams) -> RpcResult<DocumentDiagnosticReportResult> {
1286 let filename = params.text_document.uri.to_string();
1287
1288 let Some(items) = self.diagnostics_map.get(&filename) else {
1290 return Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1292 RelatedFullDocumentDiagnosticReport {
1293 related_documents: None,
1294 full_document_diagnostic_report: FullDocumentDiagnosticReport {
1295 result_id: None,
1296 items: vec![],
1297 },
1298 },
1299 )));
1300 };
1301
1302 Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1303 RelatedFullDocumentDiagnosticReport {
1304 related_documents: None,
1305 full_document_diagnostic_report: FullDocumentDiagnosticReport {
1306 result_id: None,
1307 items: items.clone(),
1308 },
1309 },
1310 )))
1311 }
1312
1313 async fn signature_help(&self, params: SignatureHelpParams) -> RpcResult<Option<SignatureHelp>> {
1314 let filename = params.text_document_position_params.text_document.uri.to_string();
1315
1316 let Some(current_code) = self.code_map.get(&filename) else {
1317 return Ok(None);
1318 };
1319 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1320 return Ok(None);
1321 };
1322
1323 let pos = position_to_char_index(params.text_document_position_params.position, current_code);
1324
1325 let Some(ch) = current_code.chars().nth(pos) else {
1327 return Ok(None);
1328 };
1329
1330 let check_char = |ch: char| {
1331 if ch == '(' {
1335 let next_space = if ch != ' ' {
1339 if let Some(next_space) = current_code[pos..].find(' ') {
1340 pos + next_space
1341 } else if let Some(next_space) = current_code[pos..].find('(') {
1342 pos + next_space
1343 } else {
1344 pos
1345 }
1346 } else {
1347 pos
1348 };
1349 let p2 = std::cmp::max(pos, next_space);
1350
1351 let last_word = current_code[..p2].split_whitespace().last()?;
1352
1353 return self.stdlib_signatures.get(last_word);
1355 } else if ch == ',' {
1356 let last_paren = current_code[..pos].rfind('(')?;
1361 let last_word = current_code[..last_paren].split_whitespace().last()?;
1363 return self.stdlib_signatures.get(last_word);
1365 }
1366
1367 None
1368 };
1369
1370 if let Some(signature) = check_char(ch) {
1371 return Ok(Some(signature.clone()));
1372 }
1373
1374 if let Some(context) = params.context {
1376 if let Some(character) = context.trigger_character {
1377 for character in character.chars() {
1378 if character == '(' || character == ',' {
1380 if let Some(signature) = check_char(character) {
1381 return Ok(Some(signature.clone()));
1382 }
1383 }
1384 }
1385 }
1386 }
1387
1388 let Some(ast) = self.ast_map.get(&filename) else {
1390 return Ok(None);
1391 };
1392
1393 let Some(value) = ast.ast.get_expr_for_position(pos) else {
1394 return Ok(None);
1395 };
1396
1397 let Some(hover) =
1398 value.get_hover_value_for_position(pos, current_code, &HoverOpts::default_for_signature_help())
1399 else {
1400 return Ok(None);
1401 };
1402
1403 match hover {
1404 Hover::Function { name, range: _ } => {
1405 let Some(signature) = self.stdlib_signatures.get(&name) else {
1407 return Ok(None);
1408 };
1409
1410 Ok(Some(signature.clone()))
1411 }
1412 Hover::Signature {
1413 name,
1414 parameter_index,
1415 range: _,
1416 } => {
1417 let Some(signature) = self.stdlib_signatures.get(&name) else {
1418 return Ok(None);
1419 };
1420
1421 let mut signature = signature.clone();
1422
1423 signature.active_parameter = Some(parameter_index);
1424
1425 Ok(Some(signature))
1426 }
1427 _ => {
1428 return Ok(None);
1429 }
1430 }
1431 }
1432
1433 async fn inlay_hint(&self, _params: InlayHintParams) -> RpcResult<Option<Vec<InlayHint>>> {
1434 Ok(None)
1437 }
1438
1439 async fn semantic_tokens_full(&self, params: SemanticTokensParams) -> RpcResult<Option<SemanticTokensResult>> {
1440 let filename = params.text_document.uri.to_string();
1441
1442 let Some(semantic_tokens) = self.semantic_tokens_map.get(&filename) else {
1443 return Ok(None);
1444 };
1445
1446 Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
1447 result_id: None,
1448 data: semantic_tokens.clone(),
1449 })))
1450 }
1451
1452 async fn document_symbol(&self, params: DocumentSymbolParams) -> RpcResult<Option<DocumentSymbolResponse>> {
1453 let filename = params.text_document.uri.to_string();
1454
1455 let Some(symbols) = self.symbols_map.get(&filename) else {
1456 return Ok(None);
1457 };
1458
1459 Ok(Some(DocumentSymbolResponse::Nested(symbols.clone())))
1460 }
1461
1462 async fn formatting(&self, params: DocumentFormattingParams) -> RpcResult<Option<Vec<TextEdit>>> {
1463 let filename = params.text_document.uri.to_string();
1464
1465 let Some(current_code) = self.code_map.get(&filename) else {
1466 return Ok(None);
1467 };
1468 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1469 return Ok(None);
1470 };
1471
1472 let module_id = ModuleId::default();
1476 let Ok(ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
1477 return Ok(None);
1478 };
1479 let recast = ast.recast(
1481 &crate::parsing::ast::types::FormatOptions {
1482 tab_size: params.options.tab_size as usize,
1483 insert_final_newline: params.options.insert_final_newline.unwrap_or(false),
1484 use_tabs: !params.options.insert_spaces,
1485 },
1486 0,
1487 );
1488 let source_range = SourceRange::new(0, current_code.len(), module_id);
1489 let range = source_range.to_lsp_range(current_code);
1490 Ok(Some(vec![TextEdit {
1491 new_text: recast,
1492 range,
1493 }]))
1494 }
1495
1496 async fn rename(&self, params: RenameParams) -> RpcResult<Option<WorkspaceEdit>> {
1497 let Some((current_code, new_code)) =
1498 self.inner_prepare_rename(¶ms.text_document_position, ¶ms.new_name)?
1499 else {
1500 return Ok(None);
1501 };
1502
1503 let source_range = SourceRange::new(0, current_code.len(), ModuleId::default());
1504 let range = source_range.to_lsp_range(¤t_code);
1505 Ok(Some(WorkspaceEdit {
1506 changes: Some(HashMap::from([(
1507 params.text_document_position.text_document.uri,
1508 vec![TextEdit {
1509 new_text: new_code,
1510 range,
1511 }],
1512 )])),
1513 document_changes: None,
1514 change_annotations: None,
1515 }))
1516 }
1517
1518 async fn prepare_rename(&self, params: TextDocumentPositionParams) -> RpcResult<Option<PrepareRenameResponse>> {
1519 if self
1520 .inner_prepare_rename(¶ms, "someNameNoOneInTheirRightMindWouldEverUseForTesting")?
1521 .is_none()
1522 {
1523 return Ok(None);
1524 }
1525
1526 Ok(Some(PrepareRenameResponse::DefaultBehavior { default_behavior: true }))
1528 }
1529
1530 async fn folding_range(&self, params: FoldingRangeParams) -> RpcResult<Option<Vec<FoldingRange>>> {
1531 let filename = params.text_document.uri.to_string();
1532
1533 let Some(ast) = self.ast_map.get(&filename) else {
1535 return Ok(None);
1536 };
1537
1538 let folding_ranges = ast.ast.get_lsp_folding_ranges();
1540
1541 if folding_ranges.is_empty() {
1542 return Ok(None);
1543 }
1544
1545 Ok(Some(folding_ranges))
1546 }
1547
1548 async fn code_action(&self, params: CodeActionParams) -> RpcResult<Option<CodeActionResponse>> {
1549 let actions = params
1550 .context
1551 .diagnostics
1552 .into_iter()
1553 .filter_map(|diagnostic| {
1554 let (suggestion, range) = diagnostic
1555 .data
1556 .as_ref()
1557 .and_then(|data| serde_json::from_value::<LspSuggestion>(data.clone()).ok())?;
1558 let edit = TextEdit {
1559 range,
1560 new_text: suggestion.insert,
1561 };
1562 let changes = HashMap::from([(params.text_document.uri.clone(), vec![edit])]);
1563
1564 Some(CodeActionOrCommand::CodeAction(CodeAction {
1567 title: suggestion.title,
1568 kind: Some(CodeActionKind::QUICKFIX),
1569 diagnostics: Some(vec![diagnostic]),
1570 edit: Some(WorkspaceEdit {
1571 changes: Some(changes),
1572 document_changes: None,
1573 change_annotations: None,
1574 }),
1575 command: None,
1576 is_preferred: Some(true),
1577 disabled: None,
1578 data: None,
1579 }))
1580 })
1581 .collect();
1582
1583 Ok(Some(actions))
1584 }
1585
1586 async fn document_color(&self, params: DocumentColorParams) -> RpcResult<Vec<ColorInformation>> {
1587 let filename = params.text_document.uri.to_string();
1588
1589 let Some(current_code) = self.code_map.get(&filename) else {
1590 return Ok(vec![]);
1591 };
1592 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1593 return Ok(vec![]);
1594 };
1595
1596 let Some(ast) = self.ast_map.get(&filename) else {
1598 return Ok(vec![]);
1599 };
1600
1601 let Ok(colors) = ast.ast.document_color(current_code) else {
1603 return Ok(vec![]);
1604 };
1605
1606 Ok(colors)
1607 }
1608
1609 async fn color_presentation(&self, params: ColorPresentationParams) -> RpcResult<Vec<ColorPresentation>> {
1610 let filename = params.text_document.uri.to_string();
1611
1612 let Some(current_code) = self.code_map.get(&filename) else {
1613 return Ok(vec![]);
1614 };
1615 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1616 return Ok(vec![]);
1617 };
1618
1619 let Some(ast) = self.ast_map.get(&filename) else {
1621 return Ok(vec![]);
1622 };
1623
1624 let pos_start = position_to_char_index(params.range.start, current_code);
1625 let pos_end = position_to_char_index(params.range.end, current_code);
1626
1627 let Ok(Some(presentation)) = ast.ast.color_presentation(¶ms.color, pos_start, pos_end) else {
1629 return Ok(vec![]);
1630 };
1631
1632 Ok(vec![presentation])
1633 }
1634}
1635
1636pub fn get_completions_from_stdlib(
1638 stdlib: &crate::std::StdLib,
1639 kcl_std: &ModData,
1640) -> Result<HashMap<String, CompletionItem>> {
1641 let mut completions = HashMap::new();
1642 let combined = stdlib.combined();
1643
1644 for internal_fn in combined.values() {
1645 completions.insert(internal_fn.name(), internal_fn.to_completion_item()?);
1646 }
1647
1648 for d in kcl_std.all_docs() {
1649 if let Some(ci) = d.to_completion_item() {
1650 completions.insert(d.name().to_owned(), ci);
1651 }
1652 }
1653
1654 let variable_kinds = VariableKind::to_completion_items();
1655 for variable_kind in variable_kinds {
1656 completions.insert(variable_kind.label.clone(), variable_kind);
1657 }
1658
1659 Ok(completions)
1660}
1661
1662pub fn get_signatures_from_stdlib(stdlib: &crate::std::StdLib, kcl_std: &ModData) -> HashMap<String, SignatureHelp> {
1664 let mut signatures = HashMap::new();
1665 let combined = stdlib.combined();
1666
1667 for internal_fn in combined.values() {
1668 signatures.insert(internal_fn.name(), internal_fn.to_signature_help());
1669 }
1670
1671 for d in kcl_std.all_docs() {
1672 if let Some(sig) = d.to_signature_help() {
1673 signatures.insert(d.name().to_owned(), sig);
1674 }
1675 }
1676
1677 signatures
1678}
1679
1680pub fn get_arg_maps_from_stdlib(
1682 stdlib: &crate::std::StdLib,
1683 kcl_std: &ModData,
1684) -> HashMap<String, HashMap<String, String>> {
1685 let mut result = HashMap::new();
1686 let combined = stdlib.combined();
1687
1688 for internal_fn in combined.values() {
1689 if internal_fn.keyword_arguments() {
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
1717 for _d in kcl_std.all_docs() {
1718 }
1720
1721 result
1722}
1723
1724fn position_to_char_index(position: Position, code: &str) -> usize {
1726 let mut char_position = 0;
1728 for (index, line) in code.lines().enumerate() {
1729 if index == position.line as usize {
1730 char_position += position.character as usize;
1731 break;
1732 } else {
1733 char_position += line.len() + 1;
1734 }
1735 }
1736
1737 std::cmp::min(char_position, code.len() - 1)
1738}
1739
1740async fn with_cached_var<T>(name: &str, f: impl Fn(&KclValue) -> T) -> Option<T> {
1741 let mem = cache::read_old_memory().await?;
1742 let value = mem.0.get(name, SourceRange::default()).ok()?;
1743
1744 Some(f(value))
1745}
1746
1747#[cfg(test)]
1748mod tests {
1749 use pretty_assertions::assert_eq;
1750
1751 use super::*;
1752
1753 #[test]
1754 fn test_position_to_char_index_first_line() {
1755 let code = r#"def foo():
1756return 42"#;
1757 let position = Position::new(0, 3);
1758 let index = position_to_char_index(position, code);
1759 assert_eq!(index, 3);
1760 }
1761
1762 #[test]
1763 fn test_position_to_char_index() {
1764 let code = r#"def foo():
1765return 42"#;
1766 let position = Position::new(1, 4);
1767 let index = position_to_char_index(position, code);
1768 assert_eq!(index, 15);
1769 }
1770
1771 #[test]
1772 fn test_position_to_char_index_with_newline() {
1773 let code = r#"def foo():
1774
1775return 42"#;
1776 let position = Position::new(2, 0);
1777 let index = position_to_char_index(position, code);
1778 assert_eq!(index, 12);
1779 }
1780
1781 #[test]
1782 fn test_position_to_char_at_end() {
1783 let code = r#"def foo():
1784return 42"#;
1785
1786 let position = Position::new(1, 8);
1787 let index = position_to_char_index(position, code);
1788 assert_eq!(index, 19);
1789 }
1790}