1#![allow(dead_code)]
3
4use std::cell::Cell;
5use std::collections::HashMap;
6use std::sync::Arc;
7use std::sync::Mutex;
8
9use anyhow::Result;
10#[cfg(feature = "cli")]
11use clap::Parser;
12use dashmap::DashMap;
13use tokio::sync::RwLock;
14use tower_lsp::Client;
15use tower_lsp::LanguageServer;
16use tower_lsp::jsonrpc::Result as RpcResult;
17use tower_lsp::lsp_types::CodeAction;
18use tower_lsp::lsp_types::CodeActionKind;
19use tower_lsp::lsp_types::CodeActionOptions;
20use tower_lsp::lsp_types::CodeActionOrCommand;
21use tower_lsp::lsp_types::CodeActionParams;
22use tower_lsp::lsp_types::CodeActionProviderCapability;
23use tower_lsp::lsp_types::CodeActionResponse;
24use tower_lsp::lsp_types::ColorInformation;
25use tower_lsp::lsp_types::ColorPresentation;
26use tower_lsp::lsp_types::ColorPresentationParams;
27use tower_lsp::lsp_types::ColorProviderCapability;
28use tower_lsp::lsp_types::CompletionItem;
29use tower_lsp::lsp_types::CompletionItemKind;
30use tower_lsp::lsp_types::CompletionOptions;
31use tower_lsp::lsp_types::CompletionParams;
32use tower_lsp::lsp_types::CompletionResponse;
33use tower_lsp::lsp_types::CreateFilesParams;
34use tower_lsp::lsp_types::DeleteFilesParams;
35use tower_lsp::lsp_types::Diagnostic;
36use tower_lsp::lsp_types::DiagnosticOptions;
37use tower_lsp::lsp_types::DiagnosticServerCapabilities;
38use tower_lsp::lsp_types::DiagnosticSeverity;
39use tower_lsp::lsp_types::DidChangeConfigurationParams;
40use tower_lsp::lsp_types::DidChangeTextDocumentParams;
41use tower_lsp::lsp_types::DidChangeWatchedFilesParams;
42use tower_lsp::lsp_types::DidChangeWorkspaceFoldersParams;
43use tower_lsp::lsp_types::DidCloseTextDocumentParams;
44use tower_lsp::lsp_types::DidOpenTextDocumentParams;
45use tower_lsp::lsp_types::DidSaveTextDocumentParams;
46use tower_lsp::lsp_types::DocumentColorParams;
47use tower_lsp::lsp_types::DocumentDiagnosticParams;
48use tower_lsp::lsp_types::DocumentDiagnosticReport;
49use tower_lsp::lsp_types::DocumentDiagnosticReportResult;
50use tower_lsp::lsp_types::DocumentFilter;
51use tower_lsp::lsp_types::DocumentFormattingParams;
52use tower_lsp::lsp_types::DocumentSymbol;
53use tower_lsp::lsp_types::DocumentSymbolParams;
54use tower_lsp::lsp_types::DocumentSymbolResponse;
55use tower_lsp::lsp_types::Documentation;
56use tower_lsp::lsp_types::FoldingRange;
57use tower_lsp::lsp_types::FoldingRangeParams;
58use tower_lsp::lsp_types::FoldingRangeProviderCapability;
59use tower_lsp::lsp_types::FullDocumentDiagnosticReport;
60use tower_lsp::lsp_types::Hover as LspHover;
61use tower_lsp::lsp_types::HoverContents;
62use tower_lsp::lsp_types::HoverParams;
63use tower_lsp::lsp_types::HoverProviderCapability;
64use tower_lsp::lsp_types::InitializeParams;
65use tower_lsp::lsp_types::InitializeResult;
66use tower_lsp::lsp_types::InitializedParams;
67use tower_lsp::lsp_types::InlayHint;
68use tower_lsp::lsp_types::InlayHintParams;
69use tower_lsp::lsp_types::InsertTextFormat;
70use tower_lsp::lsp_types::MarkupContent;
71use tower_lsp::lsp_types::MarkupKind;
72use tower_lsp::lsp_types::MessageType;
73use tower_lsp::lsp_types::OneOf;
74use tower_lsp::lsp_types::Position;
75use tower_lsp::lsp_types::PrepareRenameResponse;
76use tower_lsp::lsp_types::RelatedFullDocumentDiagnosticReport;
77use tower_lsp::lsp_types::RenameFilesParams;
78use tower_lsp::lsp_types::RenameParams;
79use tower_lsp::lsp_types::SemanticToken;
80use tower_lsp::lsp_types::SemanticTokenModifier;
81use tower_lsp::lsp_types::SemanticTokenType;
82use tower_lsp::lsp_types::SemanticTokens;
83use tower_lsp::lsp_types::SemanticTokensFullOptions;
84use tower_lsp::lsp_types::SemanticTokensLegend;
85use tower_lsp::lsp_types::SemanticTokensOptions;
86use tower_lsp::lsp_types::SemanticTokensParams;
87use tower_lsp::lsp_types::SemanticTokensRegistrationOptions;
88use tower_lsp::lsp_types::SemanticTokensResult;
89use tower_lsp::lsp_types::SemanticTokensServerCapabilities;
90use tower_lsp::lsp_types::ServerCapabilities;
91use tower_lsp::lsp_types::SignatureHelp;
92use tower_lsp::lsp_types::SignatureHelpOptions;
93use tower_lsp::lsp_types::SignatureHelpParams;
94use tower_lsp::lsp_types::StaticRegistrationOptions;
95use tower_lsp::lsp_types::TextDocumentItem;
96use tower_lsp::lsp_types::TextDocumentPositionParams;
97use tower_lsp::lsp_types::TextDocumentRegistrationOptions;
98use tower_lsp::lsp_types::TextDocumentSyncCapability;
99use tower_lsp::lsp_types::TextDocumentSyncKind;
100use tower_lsp::lsp_types::TextDocumentSyncOptions;
101use tower_lsp::lsp_types::TextEdit;
102use tower_lsp::lsp_types::WorkDoneProgressOptions;
103use tower_lsp::lsp_types::WorkspaceEdit;
104use tower_lsp::lsp_types::WorkspaceFolder;
105use tower_lsp::lsp_types::WorkspaceFoldersServerCapabilities;
106use tower_lsp::lsp_types::WorkspaceServerCapabilities;
107
108use crate::ModuleId;
109use crate::Program;
110use crate::SourceRange;
111use crate::docs::kcl_doc::ArgData;
112use crate::docs::kcl_doc::ModData;
113use crate::exec::KclValue;
114use crate::execution::cache;
115use crate::lsp::LspSuggestion;
116use crate::lsp::ToLspRange;
117use crate::lsp::backend::Backend as _;
118use crate::lsp::kcl::hover::Hover;
119use crate::lsp::kcl::hover::HoverOpts;
120use crate::lsp::util::IntoDiagnostic;
121use crate::parsing::PIPE_OPERATOR;
122use crate::parsing::ast::types::Expr;
123use crate::parsing::ast::types::VariableKind;
124use crate::parsing::token::LexerMode;
125use crate::parsing::token::RESERVED_WORDS;
126use crate::parsing::token::TokenStream;
127use crate::parsing::token::adapter;
128use crate::parsing::token::lex;
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: crate::fs::FileSystemHandle,
174 pub workspace_folders: DashMap<String, WorkspaceFolder>,
176 pub stdlib_completions: HashMap<String, CompletionItem>,
178 pub sketch_block_stdlib_completions: HashMap<String, CompletionItem>,
181 pub stdlib_signatures: HashMap<String, SignatureHelp>,
183 pub sketch_block_stdlib_signatures: HashMap<String, SignatureHelp>,
185 pub stdlib_args: HashMap<String, HashMap<String, LspArgData>>,
187 pub sketch_block_stdlib_args: HashMap<String, HashMap<String, LspArgData>>,
189 pub kcl_keywords: HashMap<String, CompletionItem>,
191 pub(super) token_map: DashMap<String, TokenStream>,
193 pub ast_map: DashMap<String, crate::Program>,
195 pub code_map: DashMap<String, Vec<u8>>,
197 pub diagnostics_map: DashMap<String, Vec<Diagnostic>>,
199 pub symbols_map: DashMap<String, Vec<DocumentSymbol>>,
201 pub semantic_tokens_map: DashMap<String, Vec<SemanticToken>>,
203 pub zoo_client: kittycad::Client,
205 pub executor_ctx: Arc<RwLock<Option<crate::execution::ExecutorContext>>>,
207 pub can_execute: Arc<RwLock<bool>>,
209
210 pub is_initialized: Arc<RwLock<bool>>,
211}
212
213impl Backend {
214 #[cfg(target_arch = "wasm32")]
215 pub fn new_wasm(
216 client: Client,
217 executor_ctx: Option<crate::execution::ExecutorContext>,
218 fs: crate::fs::wasm::FileSystemManager,
219 zoo_client: kittycad::Client,
220 ) -> Result<Self, String> {
221 Self::with_file_system(
222 client,
223 executor_ctx,
224 crate::fs::new_file_system_handle(crate::fs::FileManager::new(fs)),
225 zoo_client,
226 )
227 }
228
229 #[cfg(not(target_arch = "wasm32"))]
230 pub fn new(
231 client: Client,
232 executor_ctx: Option<crate::execution::ExecutorContext>,
233 zoo_client: kittycad::Client,
234 ) -> Result<Self, String> {
235 Self::with_file_system(
236 client,
237 executor_ctx,
238 crate::fs::new_file_system_handle(crate::fs::FileManager::new()),
239 zoo_client,
240 )
241 }
242
243 fn with_file_system(
244 client: Client,
245 executor_ctx: Option<crate::execution::ExecutorContext>,
246 fs: crate::fs::FileSystemHandle,
247 zoo_client: kittycad::Client,
248 ) -> Result<Self, String> {
249 let kcl_std = crate::docs::kcl_doc::walk_stdlib();
250 let stdlib_completions = get_completions_from_stdlib(&kcl_std).map_err(|e| e.to_string())?;
251 let sketch_block_stdlib_completions =
252 get_completions_from_stdlib_for_sketch_block(&kcl_std).map_err(|e| e.to_string())?;
253 let stdlib_signatures = get_signatures_from_stdlib(&kcl_std);
254 let sketch_block_stdlib_signatures = get_signatures_from_stdlib_for_sketch_block(&kcl_std);
255 let stdlib_args = get_arg_maps_from_stdlib(&kcl_std);
256 let sketch_block_stdlib_args = get_arg_maps_from_stdlib_for_sketch_block(&kcl_std);
257 let kcl_keywords = get_keywords();
258
259 Ok(Self {
260 client,
261 fs,
262 stdlib_completions,
263 sketch_block_stdlib_completions,
264 stdlib_signatures,
265 sketch_block_stdlib_signatures,
266 stdlib_args,
267 sketch_block_stdlib_args,
268 kcl_keywords,
269 zoo_client,
270 can_execute: Arc::new(RwLock::new(executor_ctx.is_some())),
271 executor_ctx: Arc::new(RwLock::new(executor_ctx)),
272 workspace_folders: Default::default(),
273 token_map: Default::default(),
274 ast_map: Default::default(),
275 code_map: Default::default(),
276 diagnostics_map: Default::default(),
277 symbols_map: Default::default(),
278 semantic_tokens_map: Default::default(),
279 is_initialized: Default::default(),
280 })
281 }
282
283 fn is_in_sketch_block(ast: &crate::Program, position: usize) -> bool {
284 let in_sketch_block = Cell::new(false);
285 let _ = crate::walk::walk(&ast.ast, |node| {
286 if let crate::walk::Node::SketchBlock(sketch_block) = node
287 && SourceRange::from(&sketch_block.body).contains(position)
288 {
289 in_sketch_block.set(true);
290 return Ok::<bool, anyhow::Error>(false);
291 }
292
293 Ok::<bool, anyhow::Error>(true)
294 });
295
296 in_sketch_block.get()
297 }
298
299 fn stdlib_completions_for_position<'a>(
300 &'a self,
301 ast: &crate::Program,
302 position: usize,
303 ) -> &'a HashMap<String, CompletionItem> {
304 if Self::is_in_sketch_block(ast, position) {
305 &self.sketch_block_stdlib_completions
306 } else {
307 &self.stdlib_completions
308 }
309 }
310
311 fn stdlib_signatures_for_position<'a>(
312 &'a self,
313 ast: &crate::Program,
314 position: usize,
315 ) -> &'a HashMap<String, SignatureHelp> {
316 if Self::is_in_sketch_block(ast, position) {
317 &self.sketch_block_stdlib_signatures
318 } else {
319 &self.stdlib_signatures
320 }
321 }
322
323 fn stdlib_args_for_position<'a>(
324 &'a self,
325 ast: &crate::Program,
326 position: usize,
327 ) -> &'a HashMap<String, HashMap<String, LspArgData>> {
328 if Self::is_in_sketch_block(ast, position) {
329 &self.sketch_block_stdlib_args
330 } else {
331 &self.stdlib_args
332 }
333 }
334
335 fn remove_from_ast_maps(&self, filename: &str) {
336 self.ast_map.remove(filename);
337 self.symbols_map.remove(filename);
338 }
339
340 fn try_arg_completions(
341 &self,
342 program: &crate::Program,
343 position: usize,
344 current_code: &str,
345 ) -> Option<impl Iterator<Item = CompletionItem>> {
346 let curr_expr = program.ast.get_expr_for_position(position)?;
347 let hover =
348 curr_expr.get_hover_value_for_position(position, current_code, &HoverOpts::default_for_signature_help())?;
349
350 let maybe_callee = match hover {
353 Hover::Function { name, range: _ } => Some(name),
354 Hover::Signature {
355 name,
356 parameter_index: _,
357 range: _,
358 } => Some(name),
359 Hover::Comment { .. } => None,
360 Hover::Variable { .. } => None,
361 Hover::KwArg {
362 callee_name,
363 name: _,
364 range: _,
365 } => Some(callee_name),
366 Hover::Type { .. } => None,
367 };
368 let stdlib_args = self.stdlib_args_for_position(program, position);
369 let callee_args = maybe_callee.and_then(|fn_name| stdlib_args.get(&fn_name))?;
370
371 let arg_label_completions = callee_args
372 .iter()
373 .filter(|(_arg_name, arg_data)| arg_data.props.is_labelled())
375 .map(|(arg_name, arg_data)| CompletionItem {
376 label: arg_name.to_owned(),
377 label_details: None,
378 kind: Some(CompletionItemKind::PROPERTY),
379 detail: arg_data.props.ty.clone(),
380 documentation: arg_data.props.docs.clone().map(|docs| {
381 Documentation::MarkupContent(MarkupContent {
382 kind: MarkupKind::Markdown,
383 value: docs,
384 })
385 }),
386 deprecated: None,
387 preselect: None,
388 sort_text: Some(arg_name.to_owned()),
389 filter_text: Some(arg_name.to_owned()),
390 insert_text: {
391 if let Some(snippet) = arg_data.props.get_autocomplete_snippet(0).map(|(_i, snippet)| snippet) {
393 Some(snippet)
394 } else {
395 Some(format!("{arg_name} = "))
396 }
397 },
398 insert_text_format: Some(InsertTextFormat::SNIPPET),
399 insert_text_mode: None,
400 text_edit: None,
401 additional_text_edits: None,
402 command: None,
403 commit_characters: None,
404 data: None,
405 tags: None,
406 });
407 Some(arg_label_completions)
408 }
409}
410
411#[async_trait::async_trait]
413impl crate::lsp::backend::Backend for Backend {
414 fn client(&self) -> &Client {
415 &self.client
416 }
417
418 fn fs(&self) -> &crate::fs::FileSystemHandle {
419 &self.fs
420 }
421
422 async fn is_initialized(&self) -> bool {
423 *self.is_initialized.read().await
424 }
425
426 async fn set_is_initialized(&self, is_initialized: bool) {
427 *self.is_initialized.write().await = is_initialized;
428 }
429
430 async fn workspace_folders(&self) -> Vec<WorkspaceFolder> {
431 self.workspace_folders.iter().map(|i| i.clone()).collect()
433 }
434
435 async fn add_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
436 for folder in folders {
437 self.workspace_folders.insert(folder.name.to_string(), folder);
438 }
439 }
440
441 async fn remove_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
442 for folder in folders {
443 self.workspace_folders.remove(&folder.name);
444 }
445 }
446
447 fn code_map(&self) -> &DashMap<String, Vec<u8>> {
448 &self.code_map
449 }
450
451 async fn insert_code_map(&self, uri: String, text: Vec<u8>) {
452 self.code_map.insert(uri, text);
453 }
454
455 async fn remove_from_code_map(&self, uri: String) -> Option<Vec<u8>> {
456 self.code_map.remove(&uri).map(|x| x.1)
457 }
458
459 async fn clear_code_state(&self) {
460 self.code_map.clear();
461 self.token_map.clear();
462 self.ast_map.clear();
463 self.diagnostics_map.clear();
464 self.symbols_map.clear();
465 self.semantic_tokens_map.clear();
466 }
467
468 fn current_diagnostics_map(&self) -> &DashMap<String, Vec<Diagnostic>> {
469 &self.diagnostics_map
470 }
471
472 async fn inner_on_change(&self, params: TextDocumentItem, force: bool) {
473 if force {
474 crate::bust_cache().await;
475 }
476
477 let filename = params.uri.to_string();
478 let module_id = ModuleId::default();
482 let (tokens, lex_error) = match LexerMode::resolve() {
487 LexerMode::Old => match lex(¶ms.text, module_id) {
488 Ok(tokens) => (tokens, None),
489 Err(err) => {
490 self.add_to_diagnostics(¶ms, &[err], true).await;
491 self.token_map.remove(&filename);
492 self.remove_from_ast_maps(&filename);
493 self.semantic_tokens_map.remove(&filename);
494 return;
495 }
496 },
497 LexerMode::New => {
498 let result = adapter::lex_with_diagnostics(¶ms.text, module_id);
499 let lex_error = result.to_lexical_error();
500 (result.tokens, lex_error)
501 }
502 };
503
504 let tokens_changed = match self.token_map.get(&filename) {
506 Some(previous_tokens) => *previous_tokens != tokens,
507 _ => true,
508 };
509
510 let had_diagnostics = self.has_diagnostics(params.uri.as_ref()).await;
511
512 if !tokens_changed && !force && !had_diagnostics {
514 return;
516 }
517
518 if tokens_changed {
519 self.token_map.insert(params.uri.to_string(), tokens.clone());
521 self.update_semantic_tokens(&tokens, ¶ms).await;
523 }
524
525 if let Some(err) = lex_error {
530 self.add_to_diagnostics(¶ms, &[err], true).await;
531 self.remove_from_ast_maps(&filename);
532 return;
533 }
534
535 let (ast, errs) = match crate::parsing::parse_tokens(tokens.clone()).0 {
538 Ok(result) => result,
539 Err(err) => {
540 self.add_to_diagnostics(¶ms, &[err], true).await;
541 self.remove_from_ast_maps(&filename);
542 return;
543 }
544 };
545
546 self.add_to_diagnostics(¶ms, &errs, true).await;
547
548 if errs.iter().any(|e| e.severity == crate::errors::Severity::Fatal) {
549 self.remove_from_ast_maps(&filename);
550 return;
551 }
552
553 let Some(mut ast) = ast else {
554 self.remove_from_ast_maps(&filename);
555 return;
556 };
557
558 ast.compute_digest();
562
563 let ast = crate::Program {
565 ast,
566 original_file_contents: params.text.clone(),
567 };
568
569 let ast_changed = match self.ast_map.get(&filename) {
571 Some(old_ast) => {
572 *old_ast.ast != *ast.ast
574 }
575 None => true,
576 };
577
578 if !ast_changed && !force && !had_diagnostics {
579 return;
581 }
582
583 if ast_changed {
584 self.ast_map.insert(params.uri.to_string(), ast.clone());
585 self.symbols_map.insert(
587 params.uri.to_string(),
588 ast.ast.get_lsp_symbols(¶ms.text).unwrap_or_default(),
589 );
590
591 self.update_semantic_tokens(&tokens, ¶ms).await;
593
594 let mut discovered_findings: Vec<_> = ast.lint_all().into_iter().flatten().collect();
595 discovered_findings.retain(|finding| finding.finding.code != "Z0005");
598 self.add_to_diagnostics(¶ms, &discovered_findings, false).await;
599 }
600
601 if self.can_execute().await || self.executor_ctx().await.is_none() {
603 self.client
606 .send_notification::<custom_notifications::AstUpdated>(ast.ast.clone())
607 .await;
608 }
609
610 if self.execute(¶ms, &ast).await.is_err() {
614 return;
615 }
616
617 self.clear_diagnostics_map(¶ms.uri, Some(DiagnosticSeverity::ERROR))
619 .await;
620 }
621}
622
623impl Backend {
624 pub async fn can_execute(&self) -> bool {
625 *self.can_execute.read().await
626 }
627
628 pub async fn executor_ctx(&self) -> tokio::sync::RwLockReadGuard<'_, Option<crate::execution::ExecutorContext>> {
629 self.executor_ctx.read().await
630 }
631
632 async fn update_semantic_tokens(&self, tokens: &TokenStream, params: &TextDocumentItem) {
633 let mut semantic_tokens = vec![];
635 let mut last_position = Position::new(0, 0);
636 for token in tokens.as_slice() {
637 let Ok(token_type) = SemanticTokenType::try_from(token.token_type) else {
638 continue;
641 };
642
643 let mut token_type_index = match self.get_semantic_token_type_index(&token_type) {
644 Some(index) => index,
645 None => {
648 self.client
649 .log_message(
650 MessageType::ERROR,
651 format!("token type `{token_type:?}` not accounted for"),
652 )
653 .await;
654 continue;
655 }
656 };
657
658 let source_range: SourceRange = token.into();
659 let position = source_range.start_to_lsp_position(¶ms.text);
660
661 let token_modifiers_bitset = match self.ast_map.get(params.uri.as_str()) {
664 Some(ast) => {
665 let token_index = Arc::new(Mutex::new(token_type_index));
666 let modifier_index: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
667 crate::walk::walk(&ast.ast, |node: crate::walk::Node| {
668 let Ok(node_range): Result<SourceRange, _> = (&node).try_into() else {
669 return Ok(true);
670 };
671
672 if !node_range.contains(source_range.start()) {
673 return Ok(true);
674 }
675
676 let get_modifier = |modifier: Vec<SemanticTokenModifier>| -> Result<bool> {
677 let mut mods = modifier_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
678 let Some(token_modifier_index) = self.get_semantic_token_modifier_index(modifier) else {
679 return Ok(true);
680 };
681 if *mods == 0 {
682 *mods = token_modifier_index;
683 } else {
684 *mods |= token_modifier_index;
685 }
686 Ok(false)
687 };
688
689 match node {
690 crate::walk::Node::TagDeclarator(_) => {
691 return get_modifier(vec![
692 SemanticTokenModifier::DEFINITION,
693 SemanticTokenModifier::STATIC,
694 ]);
695 }
696 crate::walk::Node::VariableDeclarator(variable) => {
697 let sr: SourceRange = (&variable.id).into();
698 if sr.contains(source_range.start()) {
699 if let Expr::FunctionExpression(_) = &variable.init {
700 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
701 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::FUNCTION) {
702 Some(index) => index,
703 None => token_type_index,
704 };
705 }
706
707 return get_modifier(vec![
708 SemanticTokenModifier::DECLARATION,
709 SemanticTokenModifier::READONLY,
710 ]);
711 }
712 }
713 crate::walk::Node::Parameter(_) => {
714 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
715 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PARAMETER) {
716 Some(index) => index,
717 None => token_type_index,
718 };
719 return Ok(false);
720 }
721 crate::walk::Node::MemberExpression(member_expression) => {
722 let sr: SourceRange = (&member_expression.property).into();
723 if sr.contains(source_range.start()) {
724 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
725 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PROPERTY) {
726 Some(index) => index,
727 None => token_type_index,
728 };
729 return Ok(false);
730 }
731 }
732 crate::walk::Node::ObjectProperty(object_property) => {
733 let sr: SourceRange = (&object_property.key).into();
734 if sr.contains(source_range.start()) {
735 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
736 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PROPERTY) {
737 Some(index) => index,
738 None => token_type_index,
739 };
740 }
741 return get_modifier(vec![SemanticTokenModifier::DECLARATION]);
742 }
743 crate::walk::Node::CallExpressionKw(call_expr) => {
744 let sr: SourceRange = (&call_expr.callee).into();
745 if sr.contains(source_range.start()) {
746 let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
747 *ti = match self.get_semantic_token_type_index(&SemanticTokenType::FUNCTION) {
748 Some(index) => index,
749 None => token_type_index,
750 };
751
752 if self.stdlib_completions.contains_key(&call_expr.callee.name.name) {
753 return get_modifier(vec![SemanticTokenModifier::DEFAULT_LIBRARY]);
755 }
756
757 return Ok(false);
758 }
759 }
760 _ => {}
761 }
762 Ok(true)
763 })
764 .unwrap_or_default();
765
766 let t = match token_index.lock() {
767 Ok(guard) => *guard,
768 _ => 0,
769 };
770 token_type_index = t;
771
772 match modifier_index.lock() {
773 Ok(guard) => *guard,
774 _ => 0,
775 }
776 }
777 _ => 0,
778 };
779
780 if let Some(line) = params.text.lines().nth(position.line as usize)
784 && line.len() == position.character as usize
785 {
786 let semantic_token = SemanticToken {
789 delta_line: position.line - last_position.line + 1,
790 delta_start: 0,
791 length: (token.end - token.start) as u32,
792 token_type: token_type_index,
793 token_modifiers_bitset,
794 };
795
796 semantic_tokens.push(semantic_token);
797
798 last_position = Position::new(position.line + 1, 0);
799 continue;
800 }
801
802 let semantic_token = SemanticToken {
803 delta_line: position.line - last_position.line,
804 delta_start: if position.line != last_position.line {
805 position.character
806 } else {
807 position.character - last_position.character
808 },
809 length: (token.end - token.start) as u32,
810 token_type: token_type_index,
811 token_modifiers_bitset,
812 };
813
814 semantic_tokens.push(semantic_token);
815
816 last_position = position;
817 }
818 self.semantic_tokens_map.insert(params.uri.to_string(), semantic_tokens);
819 }
820
821 async fn clear_diagnostics_map(&self, uri: &url::Url, severity: Option<DiagnosticSeverity>) {
822 let Some(mut items) = self.diagnostics_map.get_mut(uri.as_str()) else {
823 return;
824 };
825
826 if let Some(severity) = severity {
828 items.retain(|x| x.severity != Some(severity));
829 } else {
830 items.clear();
831 }
832
833 if items.is_empty() {
834 #[cfg(not(target_arch = "wasm32"))]
835 {
836 self.client.publish_diagnostics(uri.clone(), items.clone(), None).await;
837 }
838
839 drop(items);
841
842 self.diagnostics_map.remove(uri.as_str());
843 } else {
844 #[cfg(not(target_arch = "wasm32"))]
847 {
848 self.client.publish_diagnostics(uri.clone(), items.clone(), None).await;
849 }
850 }
851 }
852
853 async fn add_to_diagnostics<DiagT: IntoDiagnostic + std::fmt::Debug>(
854 &self,
855 params: &TextDocumentItem,
856 diagnostics: &[DiagT],
857 clear_all_before_add: bool,
858 ) {
859 if diagnostics.is_empty() {
860 return;
861 }
862
863 if clear_all_before_add {
864 self.clear_diagnostics_map(¶ms.uri, None).await;
865 } else if diagnostics.iter().all(|x| x.severity() == DiagnosticSeverity::ERROR) {
866 self.clear_diagnostics_map(¶ms.uri, Some(DiagnosticSeverity::ERROR))
870 .await;
871 } else if diagnostics
872 .iter()
873 .all(|x| x.severity() == DiagnosticSeverity::INFORMATION)
874 {
875 self.clear_diagnostics_map(¶ms.uri, Some(DiagnosticSeverity::INFORMATION))
878 .await;
879 }
880
881 let mut items = match self.diagnostics_map.get(params.uri.as_str()) {
882 Some(items) => {
883 items.clone()
885 }
886 _ => {
887 vec![]
888 }
889 };
890
891 for diagnostic in diagnostics {
892 let lsp_d = diagnostic.to_lsp_diagnostics(¶ms.text);
893 for d in lsp_d {
895 if !items.iter().any(|x| x == &d) {
896 items.push(d);
897 }
898 }
899 }
900
901 self.diagnostics_map.insert(params.uri.to_string(), items.clone());
902
903 self.client.publish_diagnostics(params.uri.clone(), items, None).await;
904 }
905
906 async fn execute(&self, params: &TextDocumentItem, ast: &Program) -> Result<()> {
907 if !self.can_execute().await {
909 return Ok(());
910 }
911
912 let ctx = self.executor_ctx().await;
914 let Some(ref executor_ctx) = *ctx else {
915 return Ok(());
916 };
917
918 if !self.is_initialized().await {
919 return Ok(());
921 }
922
923 let result = if executor_ctx.is_mock() {
925 executor_ctx
926 .run_mock(ast, &crate::execution::MockConfig::default())
927 .await
928 } else {
929 executor_ctx.run_with_caching(ast.clone()).await
930 };
931
932 match result {
933 Err(err) => {
934 self.add_to_diagnostics(params, &[err], false).await;
935
936 Err(anyhow::anyhow!("failed to execute code"))
939 }
940 Ok(_) => Ok(()),
941 }
942 }
943
944 pub fn get_semantic_token_type_index(&self, token_type: &SemanticTokenType) -> Option<u32> {
945 SEMANTIC_TOKEN_TYPES
946 .iter()
947 .position(|x| *x == *token_type)
948 .map(|y| y as u32)
949 }
950
951 pub fn get_semantic_token_modifier_index(&self, token_types: Vec<SemanticTokenModifier>) -> Option<u32> {
952 if token_types.is_empty() {
953 return None;
954 }
955
956 let mut modifier = None;
957 for token_type in token_types {
958 if let Some(index) = SEMANTIC_TOKEN_MODIFIERS
959 .iter()
960 .position(|x| *x == token_type)
961 .map(|y| y as u32)
962 {
963 modifier = match modifier {
964 Some(modifier) => Some(modifier | index),
965 None => Some(index),
966 };
967 }
968 }
969 modifier
970 }
971
972 pub async fn update_can_execute(
973 &self,
974 params: custom_notifications::UpdateCanExecuteParams,
975 ) -> RpcResult<custom_notifications::UpdateCanExecuteResponse> {
976 let mut can_execute = self.can_execute.write().await;
977
978 if *can_execute == params.can_execute {
979 return Ok(custom_notifications::UpdateCanExecuteResponse {});
980 }
981
982 *can_execute = params.can_execute;
983
984 Ok(custom_notifications::UpdateCanExecuteResponse {})
985 }
986
987 pub fn inner_prepare_rename(
989 &self,
990 params: &TextDocumentPositionParams,
991 new_name: &str,
992 ) -> RpcResult<Option<(String, String)>> {
993 let filename = params.text_document.uri.to_string();
994
995 let Some(current_code) = self.code_map.get(&filename) else {
996 return Ok(None);
997 };
998 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
999 return Ok(None);
1000 };
1001
1002 let module_id = ModuleId::default();
1006 let Ok(mut ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
1007 return Ok(None);
1008 };
1009
1010 let pos = position_to_char_index(params.position, current_code);
1012 ast.rename_symbol(new_name, pos);
1014 let recast = ast.recast_top(&Default::default(), 0);
1016
1017 Ok(Some((current_code.to_string(), recast)))
1018 }
1019}
1020
1021#[tower_lsp::async_trait]
1022impl LanguageServer for Backend {
1023 async fn initialize(&self, params: InitializeParams) -> RpcResult<InitializeResult> {
1024 self.client
1025 .log_message(MessageType::INFO, format!("initialize: {params:?}"))
1026 .await;
1027
1028 Ok(InitializeResult {
1029 capabilities: ServerCapabilities {
1030 color_provider: Some(ColorProviderCapability::Simple(true)),
1031 code_action_provider: Some(CodeActionProviderCapability::Options(CodeActionOptions {
1032 code_action_kinds: Some(vec![CodeActionKind::QUICKFIX]),
1033 resolve_provider: Some(false),
1034 work_done_progress_options: WorkDoneProgressOptions::default(),
1035 })),
1036 completion_provider: Some(CompletionOptions {
1037 resolve_provider: Some(false),
1038 trigger_characters: Some(vec![".".to_string()]),
1039 work_done_progress_options: Default::default(),
1040 all_commit_characters: None,
1041 ..Default::default()
1042 }),
1043 diagnostic_provider: Some(DiagnosticServerCapabilities::Options(DiagnosticOptions {
1044 ..Default::default()
1045 })),
1046 document_formatting_provider: Some(OneOf::Left(true)),
1047 folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
1048 hover_provider: Some(HoverProviderCapability::Simple(true)),
1049 inlay_hint_provider: Some(OneOf::Left(true)),
1050 rename_provider: Some(OneOf::Left(true)),
1051 semantic_tokens_provider: Some(SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(
1052 SemanticTokensRegistrationOptions {
1053 text_document_registration_options: {
1054 TextDocumentRegistrationOptions {
1055 document_selector: Some(vec![DocumentFilter {
1056 language: Some("kcl".to_string()),
1057 scheme: Some("file".to_string()),
1058 pattern: None,
1059 }]),
1060 }
1061 },
1062 semantic_tokens_options: SemanticTokensOptions {
1063 work_done_progress_options: WorkDoneProgressOptions::default(),
1064 legend: SemanticTokensLegend {
1065 token_types: SEMANTIC_TOKEN_TYPES.to_vec(),
1066 token_modifiers: SEMANTIC_TOKEN_MODIFIERS.to_vec(),
1067 },
1068 range: Some(false),
1069 full: Some(SemanticTokensFullOptions::Bool(true)),
1070 },
1071 static_registration_options: StaticRegistrationOptions::default(),
1072 },
1073 )),
1074 signature_help_provider: Some(SignatureHelpOptions {
1075 trigger_characters: None,
1076 retrigger_characters: None,
1077 ..Default::default()
1078 }),
1079 text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
1080 open_close: Some(true),
1081 change: Some(TextDocumentSyncKind::FULL),
1082 ..Default::default()
1083 })),
1084 workspace: Some(WorkspaceServerCapabilities {
1085 workspace_folders: Some(WorkspaceFoldersServerCapabilities {
1086 supported: Some(true),
1087 change_notifications: Some(OneOf::Left(true)),
1088 }),
1089 file_operations: None,
1090 }),
1091 ..Default::default()
1092 },
1093 ..Default::default()
1094 })
1095 }
1096
1097 async fn initialized(&self, params: InitializedParams) {
1098 self.do_initialized(params).await
1099 }
1100
1101 async fn shutdown(&self) -> RpcResult<()> {
1102 self.do_shutdown().await
1103 }
1104
1105 async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
1106 self.do_did_change_workspace_folders(params).await
1107 }
1108
1109 async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
1110 self.do_did_change_configuration(params).await
1111 }
1112
1113 async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
1114 self.do_did_change_watched_files(params).await
1115 }
1116
1117 async fn did_create_files(&self, params: CreateFilesParams) {
1118 self.do_did_create_files(params).await
1119 }
1120
1121 async fn did_rename_files(&self, params: RenameFilesParams) {
1122 self.do_did_rename_files(params).await
1123 }
1124
1125 async fn did_delete_files(&self, params: DeleteFilesParams) {
1126 self.do_did_delete_files(params).await
1127 }
1128
1129 async fn did_open(&self, params: DidOpenTextDocumentParams) {
1130 self.do_did_open(params).await
1131 }
1132
1133 async fn did_change(&self, params: DidChangeTextDocumentParams) {
1134 self.do_did_change(params).await;
1135 }
1136
1137 async fn did_save(&self, params: DidSaveTextDocumentParams) {
1138 self.do_did_save(params).await
1139 }
1140
1141 async fn did_close(&self, params: DidCloseTextDocumentParams) {
1142 self.do_did_close(params).await;
1143 }
1144
1145 async fn hover(&self, params: HoverParams) -> RpcResult<Option<LspHover>> {
1146 let filename = params.text_document_position_params.text_document.uri.to_string();
1147
1148 let Some(current_code) = self.code_map.get(&filename) else {
1149 return Ok(None);
1150 };
1151 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1152 return Ok(None);
1153 };
1154
1155 let pos = position_to_char_index(params.text_document_position_params.position, current_code);
1156
1157 let Some(ast) = self.ast_map.get(&filename) else {
1159 return Ok(None);
1160 };
1161
1162 let Some(hover) = ast
1163 .ast
1164 .get_hover_value_for_position(pos, current_code, &HoverOpts::default_for_hover())
1165 else {
1166 return Ok(None);
1167 };
1168 let stdlib_completions = self.stdlib_completions_for_position(&ast, pos);
1169 let stdlib_args = self.stdlib_args_for_position(&ast, pos);
1170
1171 match hover {
1172 Hover::Function { name, range } => {
1173 let (sig, docs) = if let Some(Some(result)) = with_cached_var(&name, |value| {
1174 match value {
1175 KclValue::Function { value, .. } if !value.is_std() => {
1177 Some((value.ast.signature(), ""))
1179 }
1180 _ => None,
1181 }
1182 })
1183 .await
1184 {
1185 result
1186 } else {
1187 let Some(completion) = stdlib_completions.get(&name) else {
1189 return Ok(None);
1190 };
1191 let Some(docs) = &completion.documentation else {
1192 return Ok(None);
1193 };
1194
1195 let docs = match docs {
1196 Documentation::String(docs) => docs,
1197 Documentation::MarkupContent(MarkupContent { value, .. }) => value,
1198 };
1199
1200 let docs = if docs.len() > 320 {
1201 let end = docs.find("\n\n").or_else(|| docs.find("\n\r\n")).unwrap_or(320);
1202 &docs[..end]
1203 } else {
1204 &**docs
1205 };
1206
1207 let Some(label_details) = &completion.label_details else {
1208 return Ok(None);
1209 };
1210
1211 let sig = if let Some(detail) = &label_details.detail {
1212 detail.clone()
1213 } else {
1214 String::new()
1215 };
1216
1217 (sig, docs)
1218 };
1219
1220 Ok(Some(LspHover {
1221 contents: HoverContents::Markup(MarkupContent {
1222 kind: MarkupKind::Markdown,
1223 value: format!("```\n{name}{sig}\n```\n\n{docs}"),
1224 }),
1225 range: Some(range),
1226 }))
1227 }
1228 Hover::Type { name, range } => {
1229 let Some(completion) = stdlib_completions.get(&name) else {
1230 return Ok(None);
1231 };
1232 let Some(docs) = &completion.documentation else {
1233 return Ok(None);
1234 };
1235
1236 let docs = match docs {
1237 Documentation::String(docs) => docs,
1238 Documentation::MarkupContent(MarkupContent { value, .. }) => value,
1239 };
1240
1241 let docs = if docs.len() > 320 {
1242 let end = docs.find("\n\n").or_else(|| docs.find("\n\r\n")).unwrap_or(320);
1243 &docs[..end]
1244 } else {
1245 &**docs
1246 };
1247
1248 Ok(Some(LspHover {
1249 contents: HoverContents::Markup(MarkupContent {
1250 kind: MarkupKind::Markdown,
1251 value: format!("```\n{name}\n```\n\n{docs}"),
1252 }),
1253 range: Some(range),
1254 }))
1255 }
1256 Hover::KwArg {
1257 name,
1258 callee_name,
1259 range,
1260 } => {
1261 let Some(arg_map) = stdlib_args.get(&callee_name) else {
1264 return Ok(None);
1265 };
1266
1267 let Some(arg_entry) = arg_map.get(&name) else {
1268 return Ok(None);
1269 };
1270
1271 Ok(Some(LspHover {
1272 contents: HoverContents::Markup(MarkupContent {
1273 kind: MarkupKind::Markdown,
1274 value: arg_entry.tip.clone(),
1275 }),
1276 range: Some(range),
1277 }))
1278 }
1279 Hover::Variable {
1280 name,
1281 ty: Some(ty),
1282 range,
1283 } => Ok(Some(LspHover {
1284 contents: HoverContents::Markup(MarkupContent {
1285 kind: MarkupKind::Markdown,
1286 value: format!("```\n{name}: {ty}\n```"),
1287 }),
1288 range: Some(range),
1289 })),
1290 Hover::Variable { name, ty: None, range } => Ok(with_cached_var(&name, |value| {
1291 let mut text: String = format!("```\n{name}");
1292 if let Some(ty) = value.principal_type() {
1293 text.push_str(&format!(": {}", ty.human_friendly_type()));
1294 }
1295 if let Some(v) = value.value_str() {
1296 text.push_str(&format!(" = {v}"));
1297 }
1298 text.push_str("\n```");
1299
1300 LspHover {
1301 contents: HoverContents::Markup(MarkupContent {
1302 kind: MarkupKind::Markdown,
1303 value: text,
1304 }),
1305 range: Some(range),
1306 }
1307 })
1308 .await),
1309 Hover::Signature { .. } => Ok(None),
1310 Hover::Comment { value, range } => Ok(Some(LspHover {
1311 contents: HoverContents::Markup(MarkupContent {
1312 kind: MarkupKind::Markdown,
1313 value,
1314 }),
1315 range: Some(range),
1316 })),
1317 }
1318 }
1319
1320 async fn completion(&self, params: CompletionParams) -> RpcResult<Option<CompletionResponse>> {
1321 let mut completions = vec![CompletionItem {
1322 label: PIPE_OPERATOR.to_string(),
1323 label_details: None,
1324 kind: Some(CompletionItemKind::OPERATOR),
1325 detail: Some("A pipe operator.".to_string()),
1326 documentation: Some(Documentation::MarkupContent(MarkupContent {
1327 kind: MarkupKind::Markdown,
1328 value: "A pipe operator.".to_string(),
1329 })),
1330 deprecated: Some(false),
1331 preselect: None,
1332 sort_text: None,
1333 filter_text: None,
1334 insert_text: Some("|> ".to_string()),
1335 insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
1336 insert_text_mode: None,
1337 text_edit: None,
1338 additional_text_edits: None,
1339 command: None,
1340 commit_characters: None,
1341 data: None,
1342 tags: None,
1343 }];
1344
1345 let Some(current_code) = self
1347 .code_map
1348 .get(params.text_document_position.text_document.uri.as_ref())
1349 else {
1350 return Ok(Some(CompletionResponse::Array(completions)));
1351 };
1352 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1353 return Ok(Some(CompletionResponse::Array(completions)));
1354 };
1355
1356 if let Some(line) = current_code
1358 .lines()
1359 .nth(params.text_document_position.position.line as usize)
1360 {
1361 let char_pos = params.text_document_position.position.character as usize;
1362 if char_pos <= line.len() {
1363 let line_prefix = &line[..char_pos];
1364 let last_word = line_prefix
1366 .split(|c: char| c.is_whitespace() || c.is_ascii_punctuation())
1367 .next_back()
1368 .unwrap_or("");
1369
1370 if !last_word.is_empty() && last_word.chars().next().unwrap().is_ascii_digit() {
1372 return Ok(None);
1373 }
1374 }
1375 }
1376
1377 let Some(ast) = self
1379 .ast_map
1380 .get(params.text_document_position.text_document.uri.as_ref())
1381 else {
1382 completions.extend(self.stdlib_completions.values().cloned());
1383 completions.extend(self.kcl_keywords.values().cloned());
1384 return Ok(Some(CompletionResponse::Array(completions)));
1385 };
1386
1387 let position = position_to_char_index(params.text_document_position.position, current_code);
1388 if ast.ast.in_comment(position) {
1389 return Ok(None);
1391 }
1392
1393 completions.extend(self.stdlib_completions_for_position(&ast, position).values().cloned());
1394 completions.extend(self.kcl_keywords.values().cloned());
1395
1396 if let Some(arg_label_completions) = self.try_arg_completions(&ast, position, current_code) {
1400 completions.extend(arg_label_completions);
1401 }
1402
1403 let Ok(variables) = ast.ast.completion_items(position) else {
1405 return Ok(Some(CompletionResponse::Array(completions)));
1406 };
1407
1408 completions.extend(variables);
1410
1411 Ok(Some(CompletionResponse::Array(completions)))
1412 }
1413
1414 async fn diagnostic(&self, params: DocumentDiagnosticParams) -> RpcResult<DocumentDiagnosticReportResult> {
1415 let filename = params.text_document.uri.to_string();
1416
1417 let Some(items) = self.diagnostics_map.get(&filename) else {
1419 return Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1421 RelatedFullDocumentDiagnosticReport {
1422 related_documents: None,
1423 full_document_diagnostic_report: FullDocumentDiagnosticReport {
1424 result_id: None,
1425 items: vec![],
1426 },
1427 },
1428 )));
1429 };
1430
1431 Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1432 RelatedFullDocumentDiagnosticReport {
1433 related_documents: None,
1434 full_document_diagnostic_report: FullDocumentDiagnosticReport {
1435 result_id: None,
1436 items: items.clone(),
1437 },
1438 },
1439 )))
1440 }
1441
1442 async fn signature_help(&self, params: SignatureHelpParams) -> RpcResult<Option<SignatureHelp>> {
1443 let filename = params.text_document_position_params.text_document.uri.to_string();
1444
1445 let Some(current_code) = self.code_map.get(&filename) else {
1446 return Ok(None);
1447 };
1448 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1449 return Ok(None);
1450 };
1451
1452 let pos = position_to_char_index(params.text_document_position_params.position, current_code);
1453
1454 let ast = self.ast_map.get(&filename);
1455 let stdlib_signatures = ast
1456 .as_ref()
1457 .map(|ast| self.stdlib_signatures_for_position(ast, pos))
1458 .unwrap_or(&self.stdlib_signatures);
1459
1460 let Some(ch) = current_code.chars().nth(pos) else {
1462 return Ok(None);
1463 };
1464
1465 let check_char = |ch: char| {
1466 if ch == '(' {
1470 let next_space = if ch != ' ' {
1474 if let Some(next_space) = current_code[pos..].find(' ') {
1475 pos + next_space
1476 } else if let Some(next_space) = current_code[pos..].find('(') {
1477 pos + next_space
1478 } else {
1479 pos
1480 }
1481 } else {
1482 pos
1483 };
1484 let p2 = std::cmp::max(pos, next_space);
1485
1486 let last_word = current_code[..p2].split_whitespace().last()?;
1487
1488 return stdlib_signatures.get(last_word);
1490 } else if ch == ',' {
1491 let last_paren = current_code[..pos].rfind('(')?;
1496 let last_word = current_code[..last_paren].split_whitespace().last()?;
1498 return stdlib_signatures.get(last_word);
1500 }
1501
1502 None
1503 };
1504
1505 if let Some(signature) = check_char(ch) {
1506 return Ok(Some(signature.clone()));
1507 }
1508
1509 if let Some(context) = params.context
1511 && let Some(character) = context.trigger_character
1512 {
1513 for character in character.chars() {
1514 if (character == '(' || character == ',')
1516 && let Some(signature) = check_char(character)
1517 {
1518 return Ok(Some(signature.clone()));
1519 }
1520 }
1521 }
1522
1523 let Some(ast) = ast else {
1524 return Ok(None);
1525 };
1526
1527 let Some(value) = ast.ast.get_expr_for_position(pos) else {
1528 return Ok(None);
1529 };
1530
1531 let Some(hover) =
1532 value.get_hover_value_for_position(pos, current_code, &HoverOpts::default_for_signature_help())
1533 else {
1534 return Ok(None);
1535 };
1536
1537 match hover {
1538 Hover::Function { name, range: _ } => {
1539 let Some(signature) = stdlib_signatures.get(&name) else {
1541 return Ok(None);
1542 };
1543
1544 Ok(Some(signature.clone()))
1545 }
1546 Hover::Signature {
1547 name,
1548 parameter_index,
1549 range: _,
1550 } => {
1551 let Some(signature) = stdlib_signatures.get(&name) else {
1552 return Ok(None);
1553 };
1554
1555 let mut signature = signature.clone();
1556
1557 signature.active_parameter = Some(parameter_index);
1558
1559 Ok(Some(signature))
1560 }
1561 _ => {
1562 return Ok(None);
1563 }
1564 }
1565 }
1566
1567 async fn inlay_hint(&self, _params: InlayHintParams) -> RpcResult<Option<Vec<InlayHint>>> {
1568 Ok(None)
1571 }
1572
1573 async fn semantic_tokens_full(&self, params: SemanticTokensParams) -> RpcResult<Option<SemanticTokensResult>> {
1574 let filename = params.text_document.uri.to_string();
1575
1576 let Some(semantic_tokens) = self.semantic_tokens_map.get(&filename) else {
1577 return Ok(None);
1578 };
1579
1580 Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
1581 result_id: None,
1582 data: semantic_tokens.clone(),
1583 })))
1584 }
1585
1586 async fn document_symbol(&self, params: DocumentSymbolParams) -> RpcResult<Option<DocumentSymbolResponse>> {
1587 let filename = params.text_document.uri.to_string();
1588
1589 let Some(symbols) = self.symbols_map.get(&filename) else {
1590 return Ok(None);
1591 };
1592
1593 Ok(Some(DocumentSymbolResponse::Nested(symbols.clone())))
1594 }
1595
1596 async fn formatting(&self, params: DocumentFormattingParams) -> RpcResult<Option<Vec<TextEdit>>> {
1597 let filename = params.text_document.uri.to_string();
1598
1599 let Some(current_code) = self.code_map.get(&filename) else {
1600 return Ok(None);
1601 };
1602 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1603 return Ok(None);
1604 };
1605
1606 let module_id = ModuleId::default();
1610 let Ok(ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
1611 return Ok(None);
1612 };
1613 let recast = ast.recast_top(
1615 &crate::parsing::ast::types::FormatOptions {
1616 tab_size: params.options.tab_size as usize,
1617 insert_final_newline: params.options.insert_final_newline.unwrap_or(false),
1618 use_tabs: !params.options.insert_spaces,
1619 },
1620 0,
1621 );
1622 let source_range = SourceRange::new(0, current_code.len(), module_id);
1623 let range = source_range.to_lsp_range(current_code);
1624 Ok(Some(vec![TextEdit {
1625 new_text: recast,
1626 range,
1627 }]))
1628 }
1629
1630 async fn rename(&self, params: RenameParams) -> RpcResult<Option<WorkspaceEdit>> {
1631 let Some((current_code, new_code)) =
1632 self.inner_prepare_rename(¶ms.text_document_position, ¶ms.new_name)?
1633 else {
1634 return Ok(None);
1635 };
1636
1637 let source_range = SourceRange::new(0, current_code.len(), ModuleId::default());
1638 let range = source_range.to_lsp_range(¤t_code);
1639 Ok(Some(WorkspaceEdit {
1640 changes: Some(HashMap::from([(
1641 params.text_document_position.text_document.uri,
1642 vec![TextEdit {
1643 new_text: new_code,
1644 range,
1645 }],
1646 )])),
1647 document_changes: None,
1648 change_annotations: None,
1649 }))
1650 }
1651
1652 async fn prepare_rename(&self, params: TextDocumentPositionParams) -> RpcResult<Option<PrepareRenameResponse>> {
1653 if self
1654 .inner_prepare_rename(¶ms, "someNameNoOneInTheirRightMindWouldEverUseForTesting")?
1655 .is_none()
1656 {
1657 return Ok(None);
1658 }
1659
1660 Ok(Some(PrepareRenameResponse::DefaultBehavior { default_behavior: true }))
1662 }
1663
1664 async fn folding_range(&self, params: FoldingRangeParams) -> RpcResult<Option<Vec<FoldingRange>>> {
1665 let filename = params.text_document.uri.to_string();
1666
1667 let Some(ast) = self.ast_map.get(&filename) else {
1669 return Ok(None);
1670 };
1671
1672 let folding_ranges = ast.ast.get_lsp_folding_ranges();
1674
1675 if folding_ranges.is_empty() {
1676 return Ok(None);
1677 }
1678
1679 Ok(Some(folding_ranges))
1680 }
1681
1682 async fn code_action(&self, params: CodeActionParams) -> RpcResult<Option<CodeActionResponse>> {
1683 let filename = params.text_document.uri.to_string();
1685 let stored_diagnostics = self
1686 .diagnostics_map
1687 .get(&filename)
1688 .map(|d| d.clone())
1689 .unwrap_or_default();
1690
1691 let diagnostics_to_check: Vec<_> = if stored_diagnostics.is_empty() {
1693 params.context.diagnostics.clone()
1694 } else {
1695 params
1697 .context
1698 .diagnostics
1699 .iter()
1700 .filter_map(|param_diag| {
1701 stored_diagnostics
1702 .iter()
1703 .find(|stored_diag| stored_diag.range == param_diag.range)
1704 .cloned()
1705 })
1706 .collect()
1707 };
1708
1709 let actions = diagnostics_to_check
1710 .into_iter()
1711 .filter_map(|diagnostic| {
1712 let (suggestion, range) = diagnostic
1714 .data
1715 .as_ref()
1716 .and_then(|data| serde_json::from_value::<LspSuggestion>(data.clone()).ok())?;
1717 let edit = TextEdit {
1718 range,
1719 new_text: suggestion.insert,
1720 };
1721 let changes = HashMap::from([(params.text_document.uri.clone(), vec![edit])]);
1722
1723 Some(CodeActionOrCommand::CodeAction(CodeAction {
1726 title: suggestion.title,
1727 kind: Some(CodeActionKind::QUICKFIX),
1728 diagnostics: Some(vec![diagnostic]),
1729 edit: Some(WorkspaceEdit {
1730 changes: Some(changes),
1731 document_changes: None,
1732 change_annotations: None,
1733 }),
1734 command: None,
1735 is_preferred: Some(true),
1736 disabled: None,
1737 data: None,
1738 }))
1739 })
1740 .collect();
1741
1742 Ok(Some(actions))
1743 }
1744
1745 async fn document_color(&self, params: DocumentColorParams) -> RpcResult<Vec<ColorInformation>> {
1746 let filename = params.text_document.uri.to_string();
1747
1748 let Some(current_code) = self.code_map.get(&filename) else {
1749 return Ok(vec![]);
1750 };
1751 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1752 return Ok(vec![]);
1753 };
1754
1755 let Some(ast) = self.ast_map.get(&filename) else {
1757 return Ok(vec![]);
1758 };
1759
1760 let Ok(colors) = ast.ast.document_color(current_code) else {
1762 return Ok(vec![]);
1763 };
1764
1765 Ok(colors)
1766 }
1767
1768 async fn color_presentation(&self, params: ColorPresentationParams) -> RpcResult<Vec<ColorPresentation>> {
1769 let filename = params.text_document.uri.to_string();
1770
1771 let Some(current_code) = self.code_map.get(&filename) else {
1772 return Ok(vec![]);
1773 };
1774 let Ok(current_code) = std::str::from_utf8(¤t_code) else {
1775 return Ok(vec![]);
1776 };
1777
1778 let Some(ast) = self.ast_map.get(&filename) else {
1780 return Ok(vec![]);
1781 };
1782
1783 let pos_start = position_to_char_index(params.range.start, current_code);
1784 let pos_end = position_to_char_index(params.range.end, current_code);
1785
1786 let Ok(Some(presentation)) = ast.ast.color_presentation(¶ms.color, pos_start, pos_end) else {
1788 return Ok(vec![]);
1789 };
1790
1791 Ok(vec![presentation])
1792 }
1793}
1794
1795#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1796enum StdlibCompletionContext {
1797 Default,
1798 SketchBlock,
1799}
1800
1801fn is_sketch2_doc(doc: &crate::docs::kcl_doc::DocData) -> bool {
1802 doc.qual_name().starts_with("std::solver::")
1803}
1804
1805fn strip_sketch2_prefix(value: &str) -> String {
1806 value.strip_prefix("solver::").unwrap_or(value).to_owned()
1807}
1808
1809fn rewrite_completion_for_sketch_block(mut completion: CompletionItem) -> CompletionItem {
1810 completion.label = strip_sketch2_prefix(&completion.label);
1811 completion.insert_text = completion.insert_text.map(|text| strip_sketch2_prefix(&text));
1812 completion
1813}
1814
1815fn rewrite_signature_for_sketch_block(mut signature: SignatureHelp) -> SignatureHelp {
1816 for info in &mut signature.signatures {
1817 info.label = strip_sketch2_prefix(&info.label);
1818 }
1819 signature
1820}
1821
1822fn should_skip_stdlib_doc(
1823 doc: &crate::docs::kcl_doc::DocData,
1824 has_existing: bool,
1825 context: StdlibCompletionContext,
1826) -> bool {
1827 match context {
1828 StdlibCompletionContext::Default => is_sketch2_doc(doc) || (has_existing && doc.is_experimental()),
1829 StdlibCompletionContext::SketchBlock => has_existing && doc.is_experimental() && !is_sketch2_doc(doc),
1830 }
1831}
1832
1833pub fn get_completions_from_stdlib(kcl_std: &ModData) -> Result<HashMap<String, CompletionItem>> {
1835 get_completions_from_stdlib_in_context(kcl_std, StdlibCompletionContext::Default)
1836}
1837
1838pub fn get_completions_from_stdlib_for_sketch_block(kcl_std: &ModData) -> Result<HashMap<String, CompletionItem>> {
1839 get_completions_from_stdlib_in_context(kcl_std, StdlibCompletionContext::SketchBlock)
1840}
1841
1842fn get_completions_from_stdlib_in_context(
1843 kcl_std: &ModData,
1844 context: StdlibCompletionContext,
1845) -> Result<HashMap<String, CompletionItem>> {
1846 let mut completions = HashMap::new();
1847
1848 for d in kcl_std.all_docs() {
1849 if let Some(mut ci) = d.to_completion_item() {
1850 let preferred = d.preferred_name();
1851 if should_skip_stdlib_doc(d, completions.contains_key(preferred), context) {
1852 continue;
1853 }
1854 let key = if context == StdlibCompletionContext::SketchBlock && is_sketch2_doc(d) {
1855 ci = rewrite_completion_for_sketch_block(ci);
1856 strip_sketch2_prefix(preferred)
1857 } else {
1858 preferred.to_owned()
1859 };
1860 completions.insert(key, ci);
1861 }
1862 }
1863
1864 let variable_kinds = VariableKind::to_completion_items();
1865 for variable_kind in variable_kinds {
1866 completions.insert(variable_kind.label.clone(), variable_kind);
1867 }
1868
1869 Ok(completions)
1870}
1871
1872pub fn get_signatures_from_stdlib(kcl_std: &ModData) -> HashMap<String, SignatureHelp> {
1874 get_signatures_from_stdlib_in_context(kcl_std, StdlibCompletionContext::Default)
1875}
1876
1877pub fn get_signatures_from_stdlib_for_sketch_block(kcl_std: &ModData) -> HashMap<String, SignatureHelp> {
1878 get_signatures_from_stdlib_in_context(kcl_std, StdlibCompletionContext::SketchBlock)
1879}
1880
1881fn get_signatures_from_stdlib_in_context(
1882 kcl_std: &ModData,
1883 context: StdlibCompletionContext,
1884) -> HashMap<String, SignatureHelp> {
1885 let mut signatures = HashMap::new();
1886
1887 for d in kcl_std.all_docs() {
1888 if let Some(mut sig) = d.to_signature_help() {
1889 let preferred = d.preferred_name();
1890 if should_skip_stdlib_doc(d, signatures.contains_key(preferred), context) {
1891 continue;
1892 }
1893 let key = if context == StdlibCompletionContext::SketchBlock && is_sketch2_doc(d) {
1894 sig = rewrite_signature_for_sketch_block(sig);
1895 strip_sketch2_prefix(preferred)
1896 } else {
1897 preferred.to_owned()
1898 };
1899 signatures.insert(key, sig);
1900 }
1901 }
1902
1903 signatures
1904}
1905
1906pub fn get_keywords() -> HashMap<String, CompletionItem> {
1908 RESERVED_WORDS
1909 .keys()
1910 .map(|k| (k.to_string(), keyword_to_completion(k.to_string())))
1911 .collect()
1912}
1913
1914fn keyword_to_completion(kw: String) -> CompletionItem {
1915 CompletionItem {
1916 label: kw,
1917 label_details: None,
1918 kind: Some(CompletionItemKind::KEYWORD),
1919 detail: None,
1920 documentation: None,
1921 deprecated: Some(false),
1922 preselect: None,
1923 sort_text: None,
1924 filter_text: None,
1925 insert_text: None,
1926 insert_text_format: None,
1927 insert_text_mode: None,
1928 text_edit: None,
1929 additional_text_edits: None,
1930 command: None,
1931 commit_characters: None,
1932 data: None,
1933 tags: None,
1934 }
1935}
1936
1937#[derive(Clone, Debug)]
1938pub struct LspArgData {
1939 pub tip: String,
1940 pub props: ArgData,
1941}
1942
1943pub fn get_arg_maps_from_stdlib(kcl_std: &ModData) -> HashMap<String, HashMap<String, LspArgData>> {
1945 get_arg_maps_from_stdlib_in_context(kcl_std, StdlibCompletionContext::Default)
1946}
1947
1948pub fn get_arg_maps_from_stdlib_for_sketch_block(kcl_std: &ModData) -> HashMap<String, HashMap<String, LspArgData>> {
1949 get_arg_maps_from_stdlib_in_context(kcl_std, StdlibCompletionContext::SketchBlock)
1950}
1951
1952fn get_arg_maps_from_stdlib_in_context(
1953 kcl_std: &ModData,
1954 context: StdlibCompletionContext,
1955) -> HashMap<String, HashMap<String, LspArgData>> {
1956 let mut result = HashMap::new();
1957
1958 for d in kcl_std.all_docs() {
1959 let crate::docs::kcl_doc::DocData::Fn(f) = d else {
1960 continue;
1961 };
1962 let arg_map: HashMap<String, _> = f
1963 .args
1964 .iter()
1965 .map(|data| {
1966 let mut tip = "```\n".to_owned();
1967 tip.push_str(&data.to_string());
1968 tip.push_str("\n```");
1969 if let Some(docs) = &data.docs {
1970 tip.push_str("\n\n");
1971 tip.push_str(docs);
1972 }
1973 let arg_data = LspArgData {
1974 tip,
1975 props: data.clone(),
1976 };
1977 (data.name.clone(), arg_data)
1978 })
1979 .collect();
1980 if !arg_map.is_empty() {
1981 let key = if context == StdlibCompletionContext::SketchBlock && is_sketch2_doc(d) {
1982 strip_sketch2_prefix(&f.preferred_name)
1983 } else {
1984 f.preferred_name.clone()
1985 };
1986 if should_skip_stdlib_doc(d, result.contains_key(&key), context) {
1987 continue;
1988 }
1989 result.insert(key, arg_map);
1990 }
1991 }
1992
1993 result
1994}
1995
1996fn position_to_char_index(position: Position, code: &str) -> usize {
1998 let mut char_position = 0;
2000 for (index, line) in code.lines().enumerate() {
2001 if index == position.line as usize {
2002 char_position += position.character as usize;
2003 break;
2004 } else {
2005 char_position += line.len() + 1;
2006 }
2007 }
2008
2009 let end_of_file = if code.is_empty() { 0 } else { code.len() - 1 };
2010 std::cmp::min(char_position, end_of_file)
2011}
2012
2013async fn with_cached_var<T>(name: &str, f: impl Fn(&KclValue) -> T) -> Option<T> {
2014 let mem = cache::read_old_memory().await?;
2015 let value = mem.stack.get(name, SourceRange::default()).ok()?;
2016
2017 Some(f(&value))
2018}
2019
2020#[cfg(test)]
2021mod tests {
2022 use pretty_assertions::assert_eq;
2023
2024 use super::*;
2025
2026 #[test]
2027 fn test_position_to_char_index_first_line() {
2028 let code = r#"def foo():
2029return 42"#;
2030 let position = Position::new(0, 3);
2031 let index = position_to_char_index(position, code);
2032 assert_eq!(index, 3);
2033 }
2034
2035 #[test]
2036 fn test_position_to_char_index() {
2037 let code = r#"def foo():
2038return 42"#;
2039 let position = Position::new(1, 4);
2040 let index = position_to_char_index(position, code);
2041 assert_eq!(index, 15);
2042 }
2043
2044 #[test]
2045 fn test_position_to_char_index_with_newline() {
2046 let code = r#"def foo():
2047
2048return 42"#;
2049 let position = Position::new(2, 0);
2050 let index = position_to_char_index(position, code);
2051 assert_eq!(index, 12);
2052 }
2053
2054 #[test]
2055 fn test_position_to_char_at_end() {
2056 let code = r#"def foo():
2057return 42"#;
2058
2059 let position = Position::new(1, 8);
2060 let index = position_to_char_index(position, code);
2061 assert_eq!(index, 19);
2062 }
2063
2064 #[test]
2065 fn test_position_to_char_empty() {
2066 let code = r#""#;
2067 let position = Position::new(0, 0);
2068 let index = position_to_char_index(position, code);
2069 assert_eq!(index, 0);
2070 }
2071
2072 #[test]
2073 fn stdlib_completions_keyed_by_preferred_name() {
2074 let stdlib = crate::docs::kcl_doc::walk_stdlib();
2075 let completions = get_completions_from_stdlib(&stdlib).unwrap();
2076
2077 assert!(
2078 completions.contains_key("clone"),
2079 "prelude fn should be keyed by short name"
2080 );
2081 assert!(
2082 completions.contains_key("units::toRadians"),
2083 "module fn should be keyed by preferred name with module prefix"
2084 );
2085 assert!(
2086 !completions.contains_key("toRadians"),
2087 "module fn should NOT be keyed by bare short name"
2088 );
2089 }
2090
2091 #[test]
2092 fn stdlib_signatures_keyed_by_preferred_name() {
2093 let stdlib = crate::docs::kcl_doc::walk_stdlib();
2094 let signatures = get_signatures_from_stdlib(&stdlib);
2095
2096 assert!(
2097 signatures.contains_key("clone"),
2098 "prelude fn should be keyed by short name"
2099 );
2100 assert!(
2101 signatures.contains_key("units::toRadians"),
2102 "module fn should be keyed by preferred name with module prefix"
2103 );
2104 assert!(
2105 !signatures.contains_key("toRadians"),
2106 "module fn should NOT be keyed by bare short name"
2107 );
2108 }
2109
2110 #[test]
2111 fn stdlib_args_keyed_by_preferred_name() {
2112 let stdlib = crate::docs::kcl_doc::walk_stdlib();
2113 let args = get_arg_maps_from_stdlib(&stdlib);
2114
2115 assert!(args.contains_key("clone"), "prelude fn should be keyed by short name");
2116 assert!(
2117 args.contains_key("units::toRadians"),
2118 "module fn should be keyed by preferred name with module prefix"
2119 );
2120 assert!(
2121 !args.contains_key("toRadians"),
2122 "module fn should NOT be keyed by bare short name"
2123 );
2124 }
2125
2126 #[test]
2127 fn default_completions_exclude_solver() {
2128 let stdlib = crate::docs::kcl_doc::walk_stdlib();
2129 let completions = get_completions_from_stdlib(&stdlib).unwrap();
2130
2131 assert!(
2132 completions.contains_key("line"),
2133 "prelude line should be present in default context"
2134 );
2135 assert!(
2136 !completions.contains_key("solver::line"),
2137 "solver::line should NOT appear in default context"
2138 );
2139 assert!(
2140 !completions.contains_key("solver::coincident"),
2141 "solver-only functions should NOT appear in default context"
2142 );
2143 }
2144
2145 #[test]
2146 fn sketch1_block_completions_exclude_solver() {
2147 let stdlib = crate::docs::kcl_doc::walk_stdlib();
2150 let completions = get_completions_from_stdlib(&stdlib).unwrap();
2151
2152 assert!(
2153 completions.contains_key("line"),
2154 "prelude line should be present for old sketch blocks"
2155 );
2156 assert!(
2157 !completions.contains_key("solver::line"),
2158 "solver::line should NOT appear for old sketch blocks"
2159 );
2160 assert!(
2161 !completions.contains_key("solver::coincident"),
2162 "solver-only functions should NOT appear for old sketch blocks"
2163 );
2164 }
2165
2166 #[test]
2167 fn sketch2_completions_include_solver() {
2168 let stdlib = crate::docs::kcl_doc::walk_stdlib();
2169 let completions = get_completions_from_stdlib_for_sketch_block(&stdlib).unwrap();
2170
2171 assert!(
2173 completions.contains_key("line"),
2174 "solver line should be present under bare name in sketch2 context"
2175 );
2176 let line = &completions["line"];
2177 assert!(
2178 line.insert_text
2179 .as_ref()
2180 .is_some_and(|t| t.starts_with("line(start = ")),
2181 "sketch2 line should use solver signature (start =), not prelude signature"
2182 );
2183
2184 assert!(
2186 completions.contains_key("coincident"),
2187 "solver-only functions should be present under bare name in sketch2 context"
2188 );
2189 assert!(
2190 !completions.contains_key("solver::coincident"),
2191 "solver functions should NOT keep solver:: prefix in sketch2 context"
2192 );
2193 }
2194}