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