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        let mut completions = vec![CompletionItem {
1193            label: PIPE_OPERATOR.to_string(),
1194            label_details: None,
1195            kind: Some(CompletionItemKind::OPERATOR),
1196            detail: Some("A pipe operator.".to_string()),
1197            documentation: Some(Documentation::MarkupContent(MarkupContent {
1198                kind: MarkupKind::Markdown,
1199                value: "A pipe operator.".to_string(),
1200            })),
1201            deprecated: Some(false),
1202            preselect: None,
1203            sort_text: None,
1204            filter_text: None,
1205            insert_text: Some("|> ".to_string()),
1206            insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
1207            insert_text_mode: None,
1208            text_edit: None,
1209            additional_text_edits: None,
1210            command: None,
1211            commit_characters: None,
1212            data: None,
1213            tags: None,
1214        }];
1215
1216        // Get the current line up to cursor
1217        let Some(current_code) = self
1218            .code_map
1219            .get(params.text_document_position.text_document.uri.as_ref())
1220        else {
1221            return Ok(Some(CompletionResponse::Array(completions)));
1222        };
1223        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1224            return Ok(Some(CompletionResponse::Array(completions)));
1225        };
1226
1227        // Get the current line up to cursor, with bounds checking
1228        if let Some(line) = current_code
1229            .lines()
1230            .nth(params.text_document_position.position.line as usize)
1231        {
1232            let char_pos = params.text_document_position.position.character as usize;
1233            if char_pos <= line.len() {
1234                let line_prefix = &line[..char_pos];
1235                // Get last word
1236                let last_word = line_prefix
1237                    .split(|c: char| c.is_whitespace() || c.is_ascii_punctuation())
1238                    .next_back()
1239                    .unwrap_or("");
1240
1241                // If the last word starts with a digit, return no completions
1242                if !last_word.is_empty() && last_word.chars().next().unwrap().is_ascii_digit() {
1243                    return Ok(None);
1244                }
1245            }
1246        }
1247
1248        completions.extend(self.stdlib_completions.values().cloned());
1249
1250        // Add more to the completions if we have more.
1251        let Some(ast) = self
1252            .ast_map
1253            .get(params.text_document_position.text_document.uri.as_ref())
1254        else {
1255            return Ok(Some(CompletionResponse::Array(completions)));
1256        };
1257
1258        let Some(current_code) = self
1259            .code_map
1260            .get(params.text_document_position.text_document.uri.as_ref())
1261        else {
1262            return Ok(Some(CompletionResponse::Array(completions)));
1263        };
1264        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1265            return Ok(Some(CompletionResponse::Array(completions)));
1266        };
1267
1268        let position = position_to_char_index(params.text_document_position.position, current_code);
1269        if ast.ast.in_comment(position) {
1270            // If we are in a code comment we don't want to show completions.
1271            return Ok(None);
1272        }
1273
1274        // Get the completion items for the ast.
1275        let Ok(variables) = ast.ast.completion_items(position) else {
1276            return Ok(Some(CompletionResponse::Array(completions)));
1277        };
1278
1279        // Get our variables from our AST to include in our completions.
1280        completions.extend(variables);
1281
1282        Ok(Some(CompletionResponse::Array(completions)))
1283    }
1284
1285    async fn diagnostic(&self, params: DocumentDiagnosticParams) -> RpcResult<DocumentDiagnosticReportResult> {
1286        let filename = params.text_document.uri.to_string();
1287
1288        // Get the current diagnostics for this file.
1289        let Some(items) = self.diagnostics_map.get(&filename) else {
1290            // Send an empty report.
1291            return Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1292                RelatedFullDocumentDiagnosticReport {
1293                    related_documents: None,
1294                    full_document_diagnostic_report: FullDocumentDiagnosticReport {
1295                        result_id: None,
1296                        items: vec![],
1297                    },
1298                },
1299            )));
1300        };
1301
1302        Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1303            RelatedFullDocumentDiagnosticReport {
1304                related_documents: None,
1305                full_document_diagnostic_report: FullDocumentDiagnosticReport {
1306                    result_id: None,
1307                    items: items.clone(),
1308                },
1309            },
1310        )))
1311    }
1312
1313    async fn signature_help(&self, params: SignatureHelpParams) -> RpcResult<Option<SignatureHelp>> {
1314        let filename = params.text_document_position_params.text_document.uri.to_string();
1315
1316        let Some(current_code) = self.code_map.get(&filename) else {
1317            return Ok(None);
1318        };
1319        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1320            return Ok(None);
1321        };
1322
1323        let pos = position_to_char_index(params.text_document_position_params.position, current_code);
1324
1325        // Get the character at the position.
1326        let Some(ch) = current_code.chars().nth(pos) else {
1327            return Ok(None);
1328        };
1329
1330        let check_char = |ch: char| {
1331            // If  we are on a (, then get the string in front of the (
1332            // and try to get the signature.
1333            // We do these before the ast check because we might not have a valid ast.
1334            if ch == '(' {
1335                // If the current character is not a " " then get the next space after
1336                // our position so we can split on that.
1337                // Find the next space after the current position.
1338                let next_space = if ch != ' ' {
1339                    if let Some(next_space) = current_code[pos..].find(' ') {
1340                        pos + next_space
1341                    } else if let Some(next_space) = current_code[pos..].find('(') {
1342                        pos + next_space
1343                    } else {
1344                        pos
1345                    }
1346                } else {
1347                    pos
1348                };
1349                let p2 = std::cmp::max(pos, next_space);
1350
1351                let last_word = current_code[..p2].split_whitespace().last()?;
1352
1353                // Get the function name.
1354                return self.stdlib_signatures.get(last_word);
1355            } else if ch == ',' {
1356                // If we have a comma, then get the string in front of
1357                // the closest ( and try to get the signature.
1358
1359                // Find the last ( before the comma.
1360                let last_paren = current_code[..pos].rfind('(')?;
1361                // Get the string in front of the (.
1362                let last_word = current_code[..last_paren].split_whitespace().last()?;
1363                // Get the function name.
1364                return self.stdlib_signatures.get(last_word);
1365            }
1366
1367            None
1368        };
1369
1370        if let Some(signature) = check_char(ch) {
1371            return Ok(Some(signature.clone()));
1372        }
1373
1374        // Check if we have context.
1375        if let Some(context) = params.context {
1376            if let Some(character) = context.trigger_character {
1377                for character in character.chars() {
1378                    // Check if we are on a ( or a ,.
1379                    if character == '(' || character == ',' {
1380                        if let Some(signature) = check_char(character) {
1381                            return Ok(Some(signature.clone()));
1382                        }
1383                    }
1384                }
1385            }
1386        }
1387
1388        // Let's iterate over the AST and find the node that contains the cursor.
1389        let Some(ast) = self.ast_map.get(&filename) else {
1390            return Ok(None);
1391        };
1392
1393        let Some(value) = ast.ast.get_expr_for_position(pos) else {
1394            return Ok(None);
1395        };
1396
1397        let Some(hover) =
1398            value.get_hover_value_for_position(pos, current_code, &HoverOpts::default_for_signature_help())
1399        else {
1400            return Ok(None);
1401        };
1402
1403        match hover {
1404            Hover::Function { name, range: _ } => {
1405                // Get the docs for this function.
1406                let Some(signature) = self.stdlib_signatures.get(&name) else {
1407                    return Ok(None);
1408                };
1409
1410                Ok(Some(signature.clone()))
1411            }
1412            Hover::Signature {
1413                name,
1414                parameter_index,
1415                range: _,
1416            } => {
1417                let Some(signature) = self.stdlib_signatures.get(&name) else {
1418                    return Ok(None);
1419                };
1420
1421                let mut signature = signature.clone();
1422
1423                signature.active_parameter = Some(parameter_index);
1424
1425                Ok(Some(signature))
1426            }
1427            _ => {
1428                return Ok(None);
1429            }
1430        }
1431    }
1432
1433    async fn inlay_hint(&self, _params: InlayHintParams) -> RpcResult<Option<Vec<InlayHint>>> {
1434        // TODO: do this
1435
1436        Ok(None)
1437    }
1438
1439    async fn semantic_tokens_full(&self, params: SemanticTokensParams) -> RpcResult<Option<SemanticTokensResult>> {
1440        let filename = params.text_document.uri.to_string();
1441
1442        let Some(semantic_tokens) = self.semantic_tokens_map.get(&filename) else {
1443            return Ok(None);
1444        };
1445
1446        Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
1447            result_id: None,
1448            data: semantic_tokens.clone(),
1449        })))
1450    }
1451
1452    async fn document_symbol(&self, params: DocumentSymbolParams) -> RpcResult<Option<DocumentSymbolResponse>> {
1453        let filename = params.text_document.uri.to_string();
1454
1455        let Some(symbols) = self.symbols_map.get(&filename) else {
1456            return Ok(None);
1457        };
1458
1459        Ok(Some(DocumentSymbolResponse::Nested(symbols.clone())))
1460    }
1461
1462    async fn formatting(&self, params: DocumentFormattingParams) -> RpcResult<Option<Vec<TextEdit>>> {
1463        let filename = params.text_document.uri.to_string();
1464
1465        let Some(current_code) = self.code_map.get(&filename) else {
1466            return Ok(None);
1467        };
1468        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1469            return Ok(None);
1470        };
1471
1472        // Parse the ast.
1473        // I don't know if we need to do this again since it should be updated in the context.
1474        // But I figure better safe than sorry since this will write back out to the file.
1475        let module_id = ModuleId::default();
1476        let Ok(ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
1477            return Ok(None);
1478        };
1479        // Now recast it.
1480        let recast = ast.recast(
1481            &crate::parsing::ast::types::FormatOptions {
1482                tab_size: params.options.tab_size as usize,
1483                insert_final_newline: params.options.insert_final_newline.unwrap_or(false),
1484                use_tabs: !params.options.insert_spaces,
1485            },
1486            0,
1487        );
1488        let source_range = SourceRange::new(0, current_code.len(), module_id);
1489        let range = source_range.to_lsp_range(current_code);
1490        Ok(Some(vec![TextEdit {
1491            new_text: recast,
1492            range,
1493        }]))
1494    }
1495
1496    async fn rename(&self, params: RenameParams) -> RpcResult<Option<WorkspaceEdit>> {
1497        let Some((current_code, new_code)) =
1498            self.inner_prepare_rename(&params.text_document_position, &params.new_name)?
1499        else {
1500            return Ok(None);
1501        };
1502
1503        let source_range = SourceRange::new(0, current_code.len(), ModuleId::default());
1504        let range = source_range.to_lsp_range(&current_code);
1505        Ok(Some(WorkspaceEdit {
1506            changes: Some(HashMap::from([(
1507                params.text_document_position.text_document.uri,
1508                vec![TextEdit {
1509                    new_text: new_code,
1510                    range,
1511                }],
1512            )])),
1513            document_changes: None,
1514            change_annotations: None,
1515        }))
1516    }
1517
1518    async fn prepare_rename(&self, params: TextDocumentPositionParams) -> RpcResult<Option<PrepareRenameResponse>> {
1519        if self
1520            .inner_prepare_rename(&params, "someNameNoOneInTheirRightMindWouldEverUseForTesting")?
1521            .is_none()
1522        {
1523            return Ok(None);
1524        }
1525
1526        // Return back to the client, that it is safe to use the rename behavior.
1527        Ok(Some(PrepareRenameResponse::DefaultBehavior { default_behavior: true }))
1528    }
1529
1530    async fn folding_range(&self, params: FoldingRangeParams) -> RpcResult<Option<Vec<FoldingRange>>> {
1531        let filename = params.text_document.uri.to_string();
1532
1533        // Get the ast.
1534        let Some(ast) = self.ast_map.get(&filename) else {
1535            return Ok(None);
1536        };
1537
1538        // Get the folding ranges.
1539        let folding_ranges = ast.ast.get_lsp_folding_ranges();
1540
1541        if folding_ranges.is_empty() {
1542            return Ok(None);
1543        }
1544
1545        Ok(Some(folding_ranges))
1546    }
1547
1548    async fn code_action(&self, params: CodeActionParams) -> RpcResult<Option<CodeActionResponse>> {
1549        let actions = params
1550            .context
1551            .diagnostics
1552            .into_iter()
1553            .filter_map(|diagnostic| {
1554                let (suggestion, range) = diagnostic
1555                    .data
1556                    .as_ref()
1557                    .and_then(|data| serde_json::from_value::<LspSuggestion>(data.clone()).ok())?;
1558                let edit = TextEdit {
1559                    range,
1560                    new_text: suggestion.insert,
1561                };
1562                let changes = HashMap::from([(params.text_document.uri.clone(), vec![edit])]);
1563
1564                // If you add more code action kinds, make sure you add it to the server
1565                // capabilities on initialization!
1566                Some(CodeActionOrCommand::CodeAction(CodeAction {
1567                    title: suggestion.title,
1568                    kind: Some(CodeActionKind::QUICKFIX),
1569                    diagnostics: Some(vec![diagnostic]),
1570                    edit: Some(WorkspaceEdit {
1571                        changes: Some(changes),
1572                        document_changes: None,
1573                        change_annotations: None,
1574                    }),
1575                    command: None,
1576                    is_preferred: Some(true),
1577                    disabled: None,
1578                    data: None,
1579                }))
1580            })
1581            .collect();
1582
1583        Ok(Some(actions))
1584    }
1585
1586    async fn document_color(&self, params: DocumentColorParams) -> RpcResult<Vec<ColorInformation>> {
1587        let filename = params.text_document.uri.to_string();
1588
1589        let Some(current_code) = self.code_map.get(&filename) else {
1590            return Ok(vec![]);
1591        };
1592        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1593            return Ok(vec![]);
1594        };
1595
1596        // Get the ast from our map.
1597        let Some(ast) = self.ast_map.get(&filename) else {
1598            return Ok(vec![]);
1599        };
1600
1601        // Get the colors from the ast.
1602        let Ok(colors) = ast.ast.document_color(current_code) else {
1603            return Ok(vec![]);
1604        };
1605
1606        Ok(colors)
1607    }
1608
1609    async fn color_presentation(&self, params: ColorPresentationParams) -> RpcResult<Vec<ColorPresentation>> {
1610        let filename = params.text_document.uri.to_string();
1611
1612        let Some(current_code) = self.code_map.get(&filename) else {
1613            return Ok(vec![]);
1614        };
1615        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1616            return Ok(vec![]);
1617        };
1618
1619        // Get the ast from our map.
1620        let Some(ast) = self.ast_map.get(&filename) else {
1621            return Ok(vec![]);
1622        };
1623
1624        let pos_start = position_to_char_index(params.range.start, current_code);
1625        let pos_end = position_to_char_index(params.range.end, current_code);
1626
1627        // Get the colors from the ast.
1628        let Ok(Some(presentation)) = ast.ast.color_presentation(&params.color, pos_start, pos_end) else {
1629            return Ok(vec![]);
1630        };
1631
1632        Ok(vec![presentation])
1633    }
1634}
1635
1636/// Get completions from our stdlib.
1637pub fn get_completions_from_stdlib(
1638    stdlib: &crate::std::StdLib,
1639    kcl_std: &ModData,
1640) -> Result<HashMap<String, CompletionItem>> {
1641    let mut completions = HashMap::new();
1642    let combined = stdlib.combined();
1643
1644    for internal_fn in combined.values() {
1645        completions.insert(internal_fn.name(), internal_fn.to_completion_item()?);
1646    }
1647
1648    for d in kcl_std.all_docs() {
1649        if let Some(ci) = d.to_completion_item() {
1650            completions.insert(d.name().to_owned(), ci);
1651        }
1652    }
1653
1654    let variable_kinds = VariableKind::to_completion_items();
1655    for variable_kind in variable_kinds {
1656        completions.insert(variable_kind.label.clone(), variable_kind);
1657    }
1658
1659    Ok(completions)
1660}
1661
1662/// Get signatures from our stdlib.
1663pub fn get_signatures_from_stdlib(stdlib: &crate::std::StdLib, kcl_std: &ModData) -> HashMap<String, SignatureHelp> {
1664    let mut signatures = HashMap::new();
1665    let combined = stdlib.combined();
1666
1667    for internal_fn in combined.values() {
1668        signatures.insert(internal_fn.name(), internal_fn.to_signature_help());
1669    }
1670
1671    for d in kcl_std.all_docs() {
1672        if let Some(sig) = d.to_signature_help() {
1673            signatures.insert(d.name().to_owned(), sig);
1674        }
1675    }
1676
1677    signatures
1678}
1679
1680/// Get signatures from our stdlib.
1681pub fn get_arg_maps_from_stdlib(
1682    stdlib: &crate::std::StdLib,
1683    kcl_std: &ModData,
1684) -> HashMap<String, HashMap<String, String>> {
1685    let mut result = HashMap::new();
1686    let combined = stdlib.combined();
1687
1688    for internal_fn in combined.values() {
1689        if internal_fn.keyword_arguments() {
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
1717    for _d in kcl_std.all_docs() {
1718        // TODO add KCL std fns
1719    }
1720
1721    result
1722}
1723
1724/// Convert a position to a character index from the start of the file.
1725fn position_to_char_index(position: Position, code: &str) -> usize {
1726    // Get the character position from the start of the file.
1727    let mut char_position = 0;
1728    for (index, line) in code.lines().enumerate() {
1729        if index == position.line as usize {
1730            char_position += position.character as usize;
1731            break;
1732        } else {
1733            char_position += line.len() + 1;
1734        }
1735    }
1736
1737    std::cmp::min(char_position, code.len() - 1)
1738}
1739
1740async fn with_cached_var<T>(name: &str, f: impl Fn(&KclValue) -> T) -> Option<T> {
1741    let mem = cache::read_old_memory().await?;
1742    let value = mem.0.get(name, SourceRange::default()).ok()?;
1743
1744    Some(f(value))
1745}
1746
1747#[cfg(test)]
1748mod tests {
1749    use pretty_assertions::assert_eq;
1750
1751    use super::*;
1752
1753    #[test]
1754    fn test_position_to_char_index_first_line() {
1755        let code = r#"def foo():
1756return 42"#;
1757        let position = Position::new(0, 3);
1758        let index = position_to_char_index(position, code);
1759        assert_eq!(index, 3);
1760    }
1761
1762    #[test]
1763    fn test_position_to_char_index() {
1764        let code = r#"def foo():
1765return 42"#;
1766        let position = Position::new(1, 4);
1767        let index = position_to_char_index(position, code);
1768        assert_eq!(index, 15);
1769    }
1770
1771    #[test]
1772    fn test_position_to_char_index_with_newline() {
1773        let code = r#"def foo():
1774
1775return 42"#;
1776        let position = Position::new(2, 0);
1777        let index = position_to_char_index(position, code);
1778        assert_eq!(index, 12);
1779    }
1780
1781    #[test]
1782    fn test_position_to_char_at_end() {
1783        let code = r#"def foo():
1784return 42"#;
1785
1786        let position = Position::new(1, 8);
1787        let index = position_to_char_index(position, code);
1788        assert_eq!(index, 19);
1789    }
1790}