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