Skip to main content

kcl_lib/lsp/kcl/
mod.rs

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