kcl_lib/lsp/kcl/
mod.rs

1//! Functions for the `kcl` lsp server.
2#![allow(dead_code)]
3
4use std::{
5    collections::HashMap,
6    io::Write,
7    str::FromStr,
8    sync::{Arc, Mutex},
9};
10
11use anyhow::Result;
12#[cfg(feature = "cli")]
13use clap::Parser;
14use dashmap::DashMap;
15use sha2::Digest;
16use tokio::sync::RwLock;
17use tower_lsp::{
18    Client, LanguageServer,
19    jsonrpc::Result as RpcResult,
20    lsp_types::{
21        CodeAction, CodeActionKind, CodeActionOptions, CodeActionOrCommand, CodeActionParams,
22        CodeActionProviderCapability, CodeActionResponse, ColorInformation, ColorPresentation, ColorPresentationParams,
23        ColorProviderCapability, CompletionItem, CompletionItemKind, CompletionOptions, CompletionParams,
24        CompletionResponse, CreateFilesParams, DeleteFilesParams, Diagnostic, DiagnosticOptions,
25        DiagnosticServerCapabilities, DiagnosticSeverity, DidChangeConfigurationParams, DidChangeTextDocumentParams,
26        DidChangeWatchedFilesParams, DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams,
27        DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentColorParams, DocumentDiagnosticParams,
28        DocumentDiagnosticReport, DocumentDiagnosticReportResult, DocumentFilter, DocumentFormattingParams,
29        DocumentSymbol, DocumentSymbolParams, DocumentSymbolResponse, Documentation, FoldingRange, FoldingRangeParams,
30        FoldingRangeProviderCapability, FullDocumentDiagnosticReport, Hover as LspHover, HoverContents, HoverParams,
31        HoverProviderCapability, InitializeParams, InitializeResult, InitializedParams, InlayHint, InlayHintParams,
32        InsertTextFormat, MarkupContent, MarkupKind, MessageType, OneOf, Position, PrepareRenameResponse,
33        RelatedFullDocumentDiagnosticReport, RenameFilesParams, RenameParams, SemanticToken, SemanticTokenModifier,
34        SemanticTokenType, SemanticTokens, SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions,
35        SemanticTokensParams, SemanticTokensRegistrationOptions, SemanticTokensResult,
36        SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelp, SignatureHelpOptions, SignatureHelpParams,
37        StaticRegistrationOptions, TextDocumentItem, TextDocumentPositionParams, TextDocumentRegistrationOptions,
38        TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, TextEdit, WorkDoneProgressOptions,
39        WorkspaceEdit, WorkspaceFolder, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
40    },
41};
42
43use crate::{
44    ModuleId, Program, SourceRange,
45    docs::kcl_doc::ModData,
46    exec::KclValue,
47    execution::cache,
48    lsp::{
49        LspSuggestion, ToLspRange,
50        backend::Backend as _,
51        kcl::hover::{Hover, HoverOpts},
52        util::IntoDiagnostic,
53    },
54    parsing::{
55        PIPE_OPERATOR,
56        ast::types::{Expr, VariableKind},
57        token::TokenStream,
58    },
59};
60
61pub mod custom_notifications;
62mod hover;
63
64const SEMANTIC_TOKEN_TYPES: [SemanticTokenType; 10] = [
65    SemanticTokenType::NUMBER,
66    SemanticTokenType::VARIABLE,
67    SemanticTokenType::KEYWORD,
68    SemanticTokenType::TYPE,
69    SemanticTokenType::STRING,
70    SemanticTokenType::OPERATOR,
71    SemanticTokenType::COMMENT,
72    SemanticTokenType::FUNCTION,
73    SemanticTokenType::PARAMETER,
74    SemanticTokenType::PROPERTY,
75];
76
77const SEMANTIC_TOKEN_MODIFIERS: [SemanticTokenModifier; 5] = [
78    SemanticTokenModifier::DECLARATION,
79    SemanticTokenModifier::DEFINITION,
80    SemanticTokenModifier::DEFAULT_LIBRARY,
81    SemanticTokenModifier::READONLY,
82    SemanticTokenModifier::STATIC,
83];
84
85/// A subcommand for running the server.
86#[derive(Clone, Debug)]
87#[cfg_attr(feature = "cli", derive(Parser))]
88pub struct Server {
89    /// Port that the server should listen
90    #[cfg_attr(feature = "cli", clap(long, default_value = "8080"))]
91    pub socket: i32,
92
93    /// Listen over stdin and stdout instead of a tcp socket.
94    #[cfg_attr(feature = "cli", clap(short, long, default_value = "false"))]
95    pub stdio: bool,
96}
97
98/// The lsp server backend.
99#[derive(Clone)]
100pub struct Backend {
101    /// The client for the backend.
102    pub client: Client,
103    /// The file system client to use.
104    pub fs: Arc<crate::fs::FileManager>,
105    /// The workspace folders.
106    pub workspace_folders: DashMap<String, WorkspaceFolder>,
107    /// The stdlib completions for the language.
108    pub stdlib_completions: HashMap<String, CompletionItem>,
109    /// The stdlib signatures for the language.
110    pub stdlib_signatures: HashMap<String, SignatureHelp>,
111    /// For all KwArg functions in std, a map from their arg names to arg help snippets (markdown format).
112    pub stdlib_args: HashMap<String, HashMap<String, String>>,
113    /// Token maps.
114    pub(super) token_map: DashMap<String, TokenStream>,
115    /// AST maps.
116    pub ast_map: DashMap<String, crate::Program>,
117    /// Current code.
118    pub code_map: DashMap<String, Vec<u8>>,
119    /// Diagnostics.
120    pub diagnostics_map: DashMap<String, Vec<Diagnostic>>,
121    /// Symbols map.
122    pub symbols_map: DashMap<String, Vec<DocumentSymbol>>,
123    /// Semantic tokens map.
124    pub semantic_tokens_map: DashMap<String, Vec<SemanticToken>>,
125    /// The Zoo API client.
126    pub zoo_client: kittycad::Client,
127    /// If we can send telemetry for this user.
128    pub can_send_telemetry: bool,
129    /// Optional executor context to use if we want to execute the code.
130    pub executor_ctx: Arc<RwLock<Option<crate::execution::ExecutorContext>>>,
131    /// If we are currently allowed to execute the ast.
132    pub can_execute: Arc<RwLock<bool>>,
133
134    pub is_initialized: Arc<RwLock<bool>>,
135}
136
137impl Backend {
138    #[cfg(target_arch = "wasm32")]
139    pub fn new_wasm(
140        client: Client,
141        executor_ctx: Option<crate::execution::ExecutorContext>,
142        fs: crate::fs::wasm::FileSystemManager,
143        zoo_client: kittycad::Client,
144        can_send_telemetry: bool,
145    ) -> Result<Self, String> {
146        Self::with_file_manager(
147            client,
148            executor_ctx,
149            crate::fs::FileManager::new(fs),
150            zoo_client,
151            can_send_telemetry,
152        )
153    }
154
155    #[cfg(not(target_arch = "wasm32"))]
156    pub fn new(
157        client: Client,
158        executor_ctx: Option<crate::execution::ExecutorContext>,
159        zoo_client: kittycad::Client,
160        can_send_telemetry: bool,
161    ) -> Result<Self, String> {
162        Self::with_file_manager(
163            client,
164            executor_ctx,
165            crate::fs::FileManager::new(),
166            zoo_client,
167            can_send_telemetry,
168        )
169    }
170
171    fn with_file_manager(
172        client: Client,
173        executor_ctx: Option<crate::execution::ExecutorContext>,
174        fs: crate::fs::FileManager,
175        zoo_client: kittycad::Client,
176        can_send_telemetry: bool,
177    ) -> Result<Self, String> {
178        let kcl_std = crate::docs::kcl_doc::walk_prelude();
179        let stdlib_completions = get_completions_from_stdlib(&kcl_std).map_err(|e| e.to_string())?;
180        let stdlib_signatures = get_signatures_from_stdlib(&kcl_std);
181        let stdlib_args = get_arg_maps_from_stdlib(&kcl_std);
182
183        Ok(Self {
184            client,
185            fs: Arc::new(fs),
186            stdlib_completions,
187            stdlib_signatures,
188            stdlib_args,
189            zoo_client,
190            can_send_telemetry,
191            can_execute: Arc::new(RwLock::new(executor_ctx.is_some())),
192            executor_ctx: Arc::new(RwLock::new(executor_ctx)),
193            workspace_folders: Default::default(),
194            token_map: Default::default(),
195            ast_map: Default::default(),
196            code_map: Default::default(),
197            diagnostics_map: Default::default(),
198            symbols_map: Default::default(),
199            semantic_tokens_map: Default::default(),
200            is_initialized: Default::default(),
201        })
202    }
203
204    fn remove_from_ast_maps(&self, filename: &str) {
205        self.ast_map.remove(filename);
206        self.symbols_map.remove(filename);
207    }
208}
209
210// Implement the shared backend trait for the language server.
211#[async_trait::async_trait]
212impl crate::lsp::backend::Backend for Backend {
213    fn client(&self) -> &Client {
214        &self.client
215    }
216
217    fn fs(&self) -> &Arc<crate::fs::FileManager> {
218        &self.fs
219    }
220
221    async fn is_initialized(&self) -> bool {
222        *self.is_initialized.read().await
223    }
224
225    async fn set_is_initialized(&self, is_initialized: bool) {
226        *self.is_initialized.write().await = is_initialized;
227    }
228
229    async fn workspace_folders(&self) -> Vec<WorkspaceFolder> {
230        // TODO: fix clone
231        self.workspace_folders.iter().map(|i| i.clone()).collect()
232    }
233
234    async fn add_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
235        for folder in folders {
236            self.workspace_folders.insert(folder.name.to_string(), folder);
237        }
238    }
239
240    async fn remove_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
241        for folder in folders {
242            self.workspace_folders.remove(&folder.name);
243        }
244    }
245
246    fn code_map(&self) -> &DashMap<String, Vec<u8>> {
247        &self.code_map
248    }
249
250    async fn insert_code_map(&self, uri: String, text: Vec<u8>) {
251        self.code_map.insert(uri, text);
252    }
253
254    async fn remove_from_code_map(&self, uri: String) -> Option<Vec<u8>> {
255        self.code_map.remove(&uri).map(|x| x.1)
256    }
257
258    async fn clear_code_state(&self) {
259        self.code_map.clear();
260        self.token_map.clear();
261        self.ast_map.clear();
262        self.diagnostics_map.clear();
263        self.symbols_map.clear();
264        self.semantic_tokens_map.clear();
265    }
266
267    fn current_diagnostics_map(&self) -> &DashMap<String, Vec<Diagnostic>> {
268        &self.diagnostics_map
269    }
270
271    async fn inner_on_change(&self, params: TextDocumentItem, force: bool) {
272        if force {
273            crate::bust_cache().await;
274        }
275
276        let filename = params.uri.to_string();
277        // We already updated the code map in the shared backend.
278
279        // Lets update the tokens.
280        let module_id = ModuleId::default();
281        let tokens = match crate::parsing::token::lex(&params.text, module_id) {
282            Ok(tokens) => tokens,
283            Err(err) => {
284                self.add_to_diagnostics(&params, &[err], true).await;
285                self.token_map.remove(&filename);
286                self.remove_from_ast_maps(&filename);
287                self.semantic_tokens_map.remove(&filename);
288                return;
289            }
290        };
291
292        // Get the previous tokens.
293        let tokens_changed = match self.token_map.get(&filename) {
294            Some(previous_tokens) => *previous_tokens != tokens,
295            _ => true,
296        };
297
298        let had_diagnostics = self.has_diagnostics(params.uri.as_ref()).await;
299
300        // Check if the tokens are the same.
301        if !tokens_changed && !force && !had_diagnostics {
302            // We return early here because the tokens are the same.
303            return;
304        }
305
306        if tokens_changed {
307            // Update our token map.
308            self.token_map.insert(params.uri.to_string(), tokens.clone());
309            // Update our semantic tokens.
310            self.update_semantic_tokens(&tokens, &params).await;
311        }
312
313        // Lets update the ast.
314
315        let (ast, errs) = match crate::parsing::parse_tokens(tokens.clone()).0 {
316            Ok(result) => result,
317            Err(err) => {
318                self.add_to_diagnostics(&params, &[err], true).await;
319                self.remove_from_ast_maps(&filename);
320                return;
321            }
322        };
323
324        self.add_to_diagnostics(&params, &errs, true).await;
325
326        if errs.iter().any(|e| e.severity == crate::errors::Severity::Fatal) {
327            self.remove_from_ast_maps(&filename);
328            return;
329        }
330
331        let Some(mut ast) = ast else {
332            self.remove_from_ast_maps(&filename);
333            return;
334        };
335
336        // Here we will want to store the digest and compare, but for now
337        // we're doing this in a non-load-bearing capacity so we can remove
338        // this if it backfires and only hork the LSP.
339        ast.compute_digest();
340
341        // Save it as a program.
342        let ast = crate::Program {
343            ast,
344            original_file_contents: params.text.clone(),
345        };
346
347        // Check if the ast changed.
348        let ast_changed = match self.ast_map.get(&filename) {
349            Some(old_ast) => {
350                // Check if the ast changed.
351                *old_ast.ast != *ast.ast
352            }
353            None => true,
354        };
355
356        if !ast_changed && !force && !had_diagnostics {
357            // Return early if the ast did not change and we don't need to force.
358            return;
359        }
360
361        if ast_changed {
362            self.ast_map.insert(params.uri.to_string(), ast.clone());
363            // Update the symbols map.
364            self.symbols_map.insert(
365                params.uri.to_string(),
366                ast.ast.get_lsp_symbols(&params.text).unwrap_or_default(),
367            );
368
369            // Update our semantic tokens.
370            self.update_semantic_tokens(&tokens, &params).await;
371
372            let discovered_findings = ast.lint_all().into_iter().flatten().collect::<Vec<_>>();
373            self.add_to_diagnostics(&params, &discovered_findings, false).await;
374        }
375
376        // Send the notification to the client that the ast was updated.
377        if self.can_execute().await || self.executor_ctx().await.is_none() {
378            // Only send the notification if we can execute.
379            // Otherwise it confuses the client.
380            self.client
381                .send_notification::<custom_notifications::AstUpdated>(ast.ast.clone())
382                .await;
383        }
384
385        // Execute the code if we have an executor context.
386        // This function automatically executes if we should & updates the diagnostics if we got
387        // errors.
388        if self.execute(&params, &ast).await.is_err() {
389            return;
390        }
391
392        // If we made it here we can clear the diagnostics.
393        self.clear_diagnostics_map(&params.uri, Some(DiagnosticSeverity::ERROR))
394            .await;
395    }
396}
397
398impl Backend {
399    pub async fn can_execute(&self) -> bool {
400        *self.can_execute.read().await
401    }
402
403    pub async fn executor_ctx(&self) -> tokio::sync::RwLockReadGuard<'_, Option<crate::execution::ExecutorContext>> {
404        self.executor_ctx.read().await
405    }
406
407    async fn update_semantic_tokens(&self, tokens: &TokenStream, params: &TextDocumentItem) {
408        // Update the semantic tokens map.
409        let mut semantic_tokens = vec![];
410        let mut last_position = Position::new(0, 0);
411        for token in tokens.as_slice() {
412            let Ok(token_type) = SemanticTokenType::try_from(token.token_type) else {
413                // We continue here because not all tokens can be converted this way, we will get
414                // the rest from the ast.
415                continue;
416            };
417
418            let mut token_type_index = match self.get_semantic_token_type_index(&token_type) {
419                Some(index) => index,
420                // This is actually bad this should not fail.
421                // The test for listing all semantic token types should make this never happen.
422                None => {
423                    self.client
424                        .log_message(
425                            MessageType::ERROR,
426                            format!("token type `{token_type:?}` not accounted for"),
427                        )
428                        .await;
429                    continue;
430                }
431            };
432
433            let source_range: SourceRange = token.into();
434            let position = source_range.start_to_lsp_position(&params.text);
435
436            // Calculate the token modifiers.
437            // Get the value at the current position.
438            let token_modifiers_bitset = match self.ast_map.get(params.uri.as_str()) {
439                Some(ast) => {
440                    let token_index = Arc::new(Mutex::new(token_type_index));
441                    let modifier_index: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
442                    crate::walk::walk(&ast.ast, |node: crate::walk::Node| {
443                        let Ok(node_range): Result<SourceRange, _> = (&node).try_into() else {
444                            return Ok(true);
445                        };
446
447                        if !node_range.contains(source_range.start()) {
448                            return Ok(true);
449                        }
450
451                        let get_modifier = |modifier: Vec<SemanticTokenModifier>| -> Result<bool> {
452                            let mut mods = modifier_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
453                            let Some(token_modifier_index) = self.get_semantic_token_modifier_index(modifier) else {
454                                return Ok(true);
455                            };
456                            if *mods == 0 {
457                                *mods = token_modifier_index;
458                            } else {
459                                *mods |= token_modifier_index;
460                            }
461                            Ok(false)
462                        };
463
464                        match node {
465                            crate::walk::Node::TagDeclarator(_) => {
466                                return get_modifier(vec![
467                                    SemanticTokenModifier::DEFINITION,
468                                    SemanticTokenModifier::STATIC,
469                                ]);
470                            }
471                            crate::walk::Node::VariableDeclarator(variable) => {
472                                let sr: SourceRange = (&variable.id).into();
473                                if sr.contains(source_range.start()) {
474                                    if let Expr::FunctionExpression(_) = &variable.init {
475                                        let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
476                                        *ti = match self.get_semantic_token_type_index(&SemanticTokenType::FUNCTION) {
477                                            Some(index) => index,
478                                            None => token_type_index,
479                                        };
480                                    }
481
482                                    return get_modifier(vec![
483                                        SemanticTokenModifier::DECLARATION,
484                                        SemanticTokenModifier::READONLY,
485                                    ]);
486                                }
487                            }
488                            crate::walk::Node::Parameter(_) => {
489                                let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
490                                *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PARAMETER) {
491                                    Some(index) => index,
492                                    None => token_type_index,
493                                };
494                                return Ok(false);
495                            }
496                            crate::walk::Node::MemberExpression(member_expression) => {
497                                let sr: SourceRange = (&member_expression.property).into();
498                                if sr.contains(source_range.start()) {
499                                    let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
500                                    *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PROPERTY) {
501                                        Some(index) => index,
502                                        None => token_type_index,
503                                    };
504                                    return Ok(false);
505                                }
506                            }
507                            crate::walk::Node::ObjectProperty(object_property) => {
508                                let sr: SourceRange = (&object_property.key).into();
509                                if sr.contains(source_range.start()) {
510                                    let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
511                                    *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PROPERTY) {
512                                        Some(index) => index,
513                                        None => token_type_index,
514                                    };
515                                }
516                                return get_modifier(vec![SemanticTokenModifier::DECLARATION]);
517                            }
518                            crate::walk::Node::CallExpressionKw(call_expr) => {
519                                let sr: SourceRange = (&call_expr.callee).into();
520                                if sr.contains(source_range.start()) {
521                                    let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
522                                    *ti = match self.get_semantic_token_type_index(&SemanticTokenType::FUNCTION) {
523                                        Some(index) => index,
524                                        None => token_type_index,
525                                    };
526
527                                    if self.stdlib_completions.contains_key(&call_expr.callee.name.name) {
528                                        // This is a stdlib function.
529                                        return get_modifier(vec![SemanticTokenModifier::DEFAULT_LIBRARY]);
530                                    }
531
532                                    return Ok(false);
533                                }
534                            }
535                            _ => {}
536                        }
537                        Ok(true)
538                    })
539                    .unwrap_or_default();
540
541                    let t = match token_index.lock() {
542                        Ok(guard) => *guard,
543                        _ => 0,
544                    };
545                    token_type_index = t;
546
547                    match modifier_index.lock() {
548                        Ok(guard) => *guard,
549                        _ => 0,
550                    }
551                }
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                && line.len() == position.character as usize
560            {
561                // We are on the last token of the line.
562                // We need to add a new line.
563                let semantic_token = SemanticToken {
564                    delta_line: position.line - last_position.line + 1,
565                    delta_start: 0,
566                    length: (token.end - token.start) as u32,
567                    token_type: token_type_index,
568                    token_modifiers_bitset,
569                };
570
571                semantic_tokens.push(semantic_token);
572
573                last_position = Position::new(position.line + 1, 0);
574                continue;
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 = match self.diagnostics_map.get(params.uri.as_str()) {
657            Some(items) => {
658                // TODO: Would be awesome to fix the clone here.
659                items.clone()
660            }
661            _ => {
662                vec![]
663            }
664        };
665
666        for diagnostic in diagnostics {
667            let lsp_d = diagnostic.to_lsp_diagnostics(&params.text);
668            // Make sure we don't duplicate diagnostics.
669            for d in lsp_d {
670                if !items.iter().any(|x| x == &d) {
671                    items.push(d);
672                }
673            }
674        }
675
676        self.diagnostics_map.insert(params.uri.to_string(), items.clone());
677
678        self.client.publish_diagnostics(params.uri.clone(), items, None).await;
679    }
680
681    async fn execute(&self, params: &TextDocumentItem, ast: &Program) -> Result<()> {
682        // Check if we can execute.
683        if !self.can_execute().await {
684            return Ok(());
685        }
686
687        // Execute the code if we have an executor context.
688        let ctx = self.executor_ctx().await;
689        let Some(ref executor_ctx) = *ctx else {
690            return Ok(());
691        };
692
693        if !self.is_initialized().await {
694            // We are not initialized yet.
695            return Ok(());
696        }
697
698        match executor_ctx.run_with_caching(ast.clone()).await {
699            Err(err) => {
700                self.add_to_diagnostics(params, &[err], false).await;
701
702                // Since we already published the diagnostics we don't really care about the error
703                // string.
704                Err(anyhow::anyhow!("failed to execute code"))
705            }
706            Ok(_) => Ok(()),
707        }
708    }
709
710    pub fn get_semantic_token_type_index(&self, token_type: &SemanticTokenType) -> Option<u32> {
711        SEMANTIC_TOKEN_TYPES
712            .iter()
713            .position(|x| *x == *token_type)
714            .map(|y| y as u32)
715    }
716
717    pub fn get_semantic_token_modifier_index(&self, token_types: Vec<SemanticTokenModifier>) -> Option<u32> {
718        if token_types.is_empty() {
719            return None;
720        }
721
722        let mut modifier = None;
723        for token_type in token_types {
724            if let Some(index) = SEMANTIC_TOKEN_MODIFIERS
725                .iter()
726                .position(|x| *x == token_type)
727                .map(|y| y as u32)
728            {
729                modifier = match modifier {
730                    Some(modifier) => Some(modifier | index),
731                    None => Some(index),
732                };
733            }
734        }
735        modifier
736    }
737
738    pub async fn create_zip(&self) -> Result<Vec<u8>> {
739        // Collect all the file data we know.
740        let mut buf = vec![];
741        let mut zip = zip::ZipWriter::new(std::io::Cursor::new(&mut buf));
742        for code in self.code_map.iter() {
743            let entry = code.key();
744            let value = code.value();
745            let file_name = entry.replace("file://", "").to_string();
746
747            let options = zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
748            zip.start_file(file_name, options)?;
749            zip.write_all(value)?;
750        }
751        // Apply the changes you've made.
752        // Dropping the `ZipWriter` will have the same effect, but may silently fail
753        zip.finish()?;
754
755        Ok(buf)
756    }
757
758    pub async fn send_telemetry(&self) -> Result<()> {
759        // Get information about the user.
760        let user = self
761            .zoo_client
762            .users()
763            .get_self()
764            .await
765            .map_err(|e| anyhow::anyhow!(e.to_string()))?;
766
767        // Hash the user's id.
768        // Create a SHA-256 object
769        let mut hasher = sha2::Sha256::new();
770        // Write input message
771        hasher.update(user.id);
772        // Read hash digest and consume hasher
773        let result = hasher.finalize();
774        // Get the hash as a string.
775        let user_id_hash = format!("{result:x}");
776
777        // Get the workspace folders.
778        // The key of the workspace folder is the project name.
779        let workspace_folders = self.workspace_folders().await;
780        let project_names: Vec<&str> = workspace_folders.iter().map(|v| v.name.as_str()).collect::<Vec<_>>();
781        // Get the first name.
782        let project_name = project_names
783            .first()
784            .ok_or_else(|| anyhow::anyhow!("no project names"))?
785            .to_string();
786
787        // Send the telemetry data.
788        self.zoo_client
789            .meta()
790            .create_event(
791                vec![kittycad::types::multipart::Attachment {
792                    // Clean the URI part.
793                    name: "attachment".to_string(),
794                    filepath: Some("attachment.zip".into()),
795                    content_type: Some("application/x-zip".to_string()),
796                    data: self.create_zip().await?,
797                }],
798                &kittycad::types::Event {
799                    // This gets generated server side so leave empty for now.
800                    attachment_uri: None,
801                    created_at: chrono::Utc::now(),
802                    event_type: kittycad::types::ModelingAppEventType::SuccessfulCompileBeforeClose,
803                    last_compiled_at: Some(chrono::Utc::now()),
804                    // We do not have project descriptions yet.
805                    project_description: None,
806                    project_name,
807                    // The UUID for the Design Studio.
808                    // We can unwrap here because we know it will not panic.
809                    source_id: uuid::Uuid::from_str("70178592-dfca-47b3-bd2d-6fce2bcaee04").unwrap(),
810                    type_: kittycad::types::Type::ModelingAppEvent,
811                    user_id: user_id_hash,
812                },
813            )
814            .await
815            .map_err(|e| anyhow::anyhow!(e.to_string()))?;
816
817        Ok(())
818    }
819
820    pub async fn update_can_execute(
821        &self,
822        params: custom_notifications::UpdateCanExecuteParams,
823    ) -> RpcResult<custom_notifications::UpdateCanExecuteResponse> {
824        let mut can_execute = self.can_execute.write().await;
825
826        if *can_execute == params.can_execute {
827            return Ok(custom_notifications::UpdateCanExecuteResponse {});
828        }
829
830        *can_execute = params.can_execute;
831
832        Ok(custom_notifications::UpdateCanExecuteResponse {})
833    }
834
835    /// Returns the new string for the code after rename.
836    pub fn inner_prepare_rename(
837        &self,
838        params: &TextDocumentPositionParams,
839        new_name: &str,
840    ) -> RpcResult<Option<(String, String)>> {
841        let filename = params.text_document.uri.to_string();
842
843        let Some(current_code) = self.code_map.get(&filename) else {
844            return Ok(None);
845        };
846        let Ok(current_code) = std::str::from_utf8(&current_code) else {
847            return Ok(None);
848        };
849
850        // Parse the ast.
851        // I don't know if we need to do this again since it should be updated in the context.
852        // But I figure better safe than sorry since this will write back out to the file.
853        let module_id = ModuleId::default();
854        let Ok(mut ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
855            return Ok(None);
856        };
857
858        // Let's convert the position to a character index.
859        let pos = position_to_char_index(params.position, current_code);
860        // Now let's perform the rename on the ast.
861        ast.rename_symbol(new_name, pos);
862        // Now recast it.
863        let recast = ast.recast_top(&Default::default(), 0);
864
865        Ok(Some((current_code.to_string(), recast)))
866    }
867}
868
869#[tower_lsp::async_trait]
870impl LanguageServer for Backend {
871    async fn initialize(&self, params: InitializeParams) -> RpcResult<InitializeResult> {
872        self.client
873            .log_message(MessageType::INFO, format!("initialize: {params:?}"))
874            .await;
875
876        Ok(InitializeResult {
877            capabilities: ServerCapabilities {
878                color_provider: Some(ColorProviderCapability::Simple(true)),
879                code_action_provider: Some(CodeActionProviderCapability::Options(CodeActionOptions {
880                    code_action_kinds: Some(vec![CodeActionKind::QUICKFIX]),
881                    resolve_provider: Some(false),
882                    work_done_progress_options: WorkDoneProgressOptions::default(),
883                })),
884                completion_provider: Some(CompletionOptions {
885                    resolve_provider: Some(false),
886                    trigger_characters: Some(vec![".".to_string()]),
887                    work_done_progress_options: Default::default(),
888                    all_commit_characters: None,
889                    ..Default::default()
890                }),
891                diagnostic_provider: Some(DiagnosticServerCapabilities::Options(DiagnosticOptions {
892                    ..Default::default()
893                })),
894                document_formatting_provider: Some(OneOf::Left(true)),
895                folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
896                hover_provider: Some(HoverProviderCapability::Simple(true)),
897                inlay_hint_provider: Some(OneOf::Left(true)),
898                rename_provider: Some(OneOf::Left(true)),
899                semantic_tokens_provider: Some(SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(
900                    SemanticTokensRegistrationOptions {
901                        text_document_registration_options: {
902                            TextDocumentRegistrationOptions {
903                                document_selector: Some(vec![DocumentFilter {
904                                    language: Some("kcl".to_string()),
905                                    scheme: Some("file".to_string()),
906                                    pattern: None,
907                                }]),
908                            }
909                        },
910                        semantic_tokens_options: SemanticTokensOptions {
911                            work_done_progress_options: WorkDoneProgressOptions::default(),
912                            legend: SemanticTokensLegend {
913                                token_types: SEMANTIC_TOKEN_TYPES.to_vec(),
914                                token_modifiers: SEMANTIC_TOKEN_MODIFIERS.to_vec(),
915                            },
916                            range: Some(false),
917                            full: Some(SemanticTokensFullOptions::Bool(true)),
918                        },
919                        static_registration_options: StaticRegistrationOptions::default(),
920                    },
921                )),
922                signature_help_provider: Some(SignatureHelpOptions {
923                    trigger_characters: None,
924                    retrigger_characters: None,
925                    ..Default::default()
926                }),
927                text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
928                    open_close: Some(true),
929                    change: Some(TextDocumentSyncKind::FULL),
930                    ..Default::default()
931                })),
932                workspace: Some(WorkspaceServerCapabilities {
933                    workspace_folders: Some(WorkspaceFoldersServerCapabilities {
934                        supported: Some(true),
935                        change_notifications: Some(OneOf::Left(true)),
936                    }),
937                    file_operations: None,
938                }),
939                ..Default::default()
940            },
941            ..Default::default()
942        })
943    }
944
945    async fn initialized(&self, params: InitializedParams) {
946        self.do_initialized(params).await
947    }
948
949    async fn shutdown(&self) -> RpcResult<()> {
950        self.do_shutdown().await
951    }
952
953    async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
954        self.do_did_change_workspace_folders(params).await
955    }
956
957    async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
958        self.do_did_change_configuration(params).await
959    }
960
961    async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
962        self.do_did_change_watched_files(params).await
963    }
964
965    async fn did_create_files(&self, params: CreateFilesParams) {
966        self.do_did_create_files(params).await
967    }
968
969    async fn did_rename_files(&self, params: RenameFilesParams) {
970        self.do_did_rename_files(params).await
971    }
972
973    async fn did_delete_files(&self, params: DeleteFilesParams) {
974        self.do_did_delete_files(params).await
975    }
976
977    async fn did_open(&self, params: DidOpenTextDocumentParams) {
978        self.do_did_open(params).await
979    }
980
981    async fn did_change(&self, params: DidChangeTextDocumentParams) {
982        self.do_did_change(params).await;
983    }
984
985    async fn did_save(&self, params: DidSaveTextDocumentParams) {
986        self.do_did_save(params).await
987    }
988
989    async fn did_close(&self, params: DidCloseTextDocumentParams) {
990        self.do_did_close(params).await;
991
992        // Inject telemetry if we can train on the user's code.
993        // Return early if we cannot.
994        if !self.can_send_telemetry {
995            return;
996        }
997
998        // In wasm this needs to be spawn_local since fucking reqwests doesn't implement Send for wasm.
999        #[cfg(target_arch = "wasm32")]
1000        {
1001            let be = self.clone();
1002            wasm_bindgen_futures::spawn_local(async move {
1003                if let Err(err) = be.send_telemetry().await {
1004                    be.client
1005                        .log_message(MessageType::WARNING, format!("failed to send telemetry: {}", err))
1006                        .await;
1007                }
1008            });
1009        }
1010        #[cfg(not(target_arch = "wasm32"))]
1011        if let Err(err) = self.send_telemetry().await {
1012            self.client
1013                .log_message(MessageType::WARNING, format!("failed to send telemetry: {err}"))
1014                .await;
1015        }
1016    }
1017
1018    async fn hover(&self, params: HoverParams) -> RpcResult<Option<LspHover>> {
1019        let filename = params.text_document_position_params.text_document.uri.to_string();
1020
1021        let Some(current_code) = self.code_map.get(&filename) else {
1022            return Ok(None);
1023        };
1024        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1025            return Ok(None);
1026        };
1027
1028        let pos = position_to_char_index(params.text_document_position_params.position, current_code);
1029
1030        // Let's iterate over the AST and find the node that contains the cursor.
1031        let Some(ast) = self.ast_map.get(&filename) else {
1032            return Ok(None);
1033        };
1034
1035        let Some(hover) = ast
1036            .ast
1037            .get_hover_value_for_position(pos, current_code, &HoverOpts::default_for_hover())
1038        else {
1039            return Ok(None);
1040        };
1041
1042        match hover {
1043            Hover::Function { name, range } => {
1044                let (sig, docs) = if let Some(Some(result)) = with_cached_var(&name, |value| {
1045                    match value {
1046                        // User-defined function
1047                        KclValue::Function { value, .. } if !value.is_std => {
1048                            // TODO get docs from comments
1049                            Some((value.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{name}{sig}\n```\n\n{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{name}\n```\n\n{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{name}: {ty}\n```"),
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            && let Some(character) = context.trigger_character
1377        {
1378            for character in character.chars() {
1379                // Check if we are on a ( or a ,.
1380                if (character == '(' || character == ',')
1381                    && let Some(signature) = check_char(character)
1382                {
1383                    return Ok(Some(signature.clone()));
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_top(
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(kcl_std: &ModData) -> Result<HashMap<String, CompletionItem>> {
1638    let mut completions = HashMap::new();
1639
1640    for d in kcl_std.all_docs() {
1641        if let Some(ci) = d.to_completion_item() {
1642            completions.insert(d.name().to_owned(), ci);
1643        }
1644    }
1645
1646    let variable_kinds = VariableKind::to_completion_items();
1647    for variable_kind in variable_kinds {
1648        completions.insert(variable_kind.label.clone(), variable_kind);
1649    }
1650
1651    Ok(completions)
1652}
1653
1654/// Get signatures from our stdlib.
1655pub fn get_signatures_from_stdlib(kcl_std: &ModData) -> HashMap<String, SignatureHelp> {
1656    let mut signatures = HashMap::new();
1657
1658    for d in kcl_std.all_docs() {
1659        if let Some(sig) = d.to_signature_help() {
1660            signatures.insert(d.name().to_owned(), sig);
1661        }
1662    }
1663
1664    signatures
1665}
1666
1667/// Get signatures from our stdlib.
1668pub fn get_arg_maps_from_stdlib(kcl_std: &ModData) -> HashMap<String, HashMap<String, String>> {
1669    let mut result = HashMap::new();
1670
1671    for d in kcl_std.all_docs() {
1672        let crate::docs::kcl_doc::DocData::Fn(f) = d else {
1673            continue;
1674        };
1675        let arg_map: HashMap<String, String> = f
1676            .args
1677            .iter()
1678            .map(|data| {
1679                let mut tip = "```\n".to_owned();
1680                tip.push_str(&data.to_string());
1681                tip.push_str("\n```");
1682                if let Some(docs) = &data.docs {
1683                    tip.push_str("\n\n");
1684                    tip.push_str(docs);
1685                }
1686                (data.name.clone(), tip)
1687            })
1688            .collect();
1689        if !arg_map.is_empty() {
1690            result.insert(f.name.clone(), arg_map);
1691        }
1692    }
1693
1694    result
1695}
1696
1697/// Convert a position to a character index from the start of the file.
1698fn position_to_char_index(position: Position, code: &str) -> usize {
1699    // Get the character position from the start of the file.
1700    let mut char_position = 0;
1701    for (index, line) in code.lines().enumerate() {
1702        if index == position.line as usize {
1703            char_position += position.character as usize;
1704            break;
1705        } else {
1706            char_position += line.len() + 1;
1707        }
1708    }
1709
1710    std::cmp::min(char_position, code.len() - 1)
1711}
1712
1713async fn with_cached_var<T>(name: &str, f: impl Fn(&KclValue) -> T) -> Option<T> {
1714    let mem = cache::read_old_memory().await?;
1715    let value = mem.0.get(name, SourceRange::default()).ok()?;
1716
1717    Some(f(value))
1718}
1719
1720#[cfg(test)]
1721mod tests {
1722    use pretty_assertions::assert_eq;
1723
1724    use super::*;
1725
1726    #[test]
1727    fn test_position_to_char_index_first_line() {
1728        let code = r#"def foo():
1729return 42"#;
1730        let position = Position::new(0, 3);
1731        let index = position_to_char_index(position, code);
1732        assert_eq!(index, 3);
1733    }
1734
1735    #[test]
1736    fn test_position_to_char_index() {
1737        let code = r#"def foo():
1738return 42"#;
1739        let position = Position::new(1, 4);
1740        let index = position_to_char_index(position, code);
1741        assert_eq!(index, 15);
1742    }
1743
1744    #[test]
1745    fn test_position_to_char_index_with_newline() {
1746        let code = r#"def foo():
1747
1748return 42"#;
1749        let position = Position::new(2, 0);
1750        let index = position_to_char_index(position, code);
1751        assert_eq!(index, 12);
1752    }
1753
1754    #[test]
1755    fn test_position_to_char_at_end() {
1756        let code = r#"def foo():
1757return 42"#;
1758
1759        let position = Position::new(1, 8);
1760        let index = position_to_char_index(position, code);
1761        assert_eq!(index, 19);
1762    }
1763}