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