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