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