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