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