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