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