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