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