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, CodeActionOrCommand, CodeActionParams, CodeActionResponse, CompletionItem,
21        CompletionItemKind, CompletionOptions, CompletionParams, CompletionResponse, CreateFilesParams,
22        DeleteFilesParams, Diagnostic, DiagnosticOptions, DiagnosticServerCapabilities, DiagnosticSeverity,
23        DidChangeConfigurationParams, DidChangeTextDocumentParams, DidChangeWatchedFilesParams,
24        DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams, DidOpenTextDocumentParams,
25        DidSaveTextDocumentParams, DocumentDiagnosticParams, DocumentDiagnosticReport, DocumentDiagnosticReportResult,
26        DocumentFilter, DocumentFormattingParams, DocumentSymbol, DocumentSymbolParams, DocumentSymbolResponse,
27        Documentation, FoldingRange, FoldingRangeParams, FoldingRangeProviderCapability, FullDocumentDiagnosticReport,
28        Hover as LspHover, HoverContents, HoverParams, HoverProviderCapability, InitializeParams, InitializeResult,
29        InitializedParams, InlayHint, InlayHintParams, InsertTextFormat, MarkupContent, MarkupKind, MessageType, OneOf,
30        Position, RelatedFullDocumentDiagnosticReport, RenameFilesParams, RenameParams, SemanticToken,
31        SemanticTokenModifier, SemanticTokenType, SemanticTokens, SemanticTokensFullOptions, SemanticTokensLegend,
32        SemanticTokensOptions, SemanticTokensParams, SemanticTokensRegistrationOptions, SemanticTokensResult,
33        SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelp, SignatureHelpOptions, SignatureHelpParams,
34        StaticRegistrationOptions, TextDocumentItem, TextDocumentRegistrationOptions, TextDocumentSyncCapability,
35        TextDocumentSyncKind, TextDocumentSyncOptions, TextEdit, WorkDoneProgressOptions, WorkspaceEdit,
36        WorkspaceFolder, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
37    },
38    Client, LanguageServer,
39};
40
41use crate::{
42    docs::kcl_doc::DocData,
43    errors::Suggestion,
44    exec::KclValue,
45    execution::{cache, kcl_value::FunctionSource},
46    lsp::{
47        backend::Backend as _,
48        kcl::hover::{Hover, HoverOpts},
49        util::IntoDiagnostic,
50    },
51    parsing::{
52        ast::types::{Expr, VariableKind},
53        token::TokenStream,
54        PIPE_OPERATOR,
55    },
56    ModuleId, Program, SourceRange,
57};
58
59pub mod custom_notifications;
60mod hover;
61
62const SEMANTIC_TOKEN_TYPES: [SemanticTokenType; 10] = [
63    SemanticTokenType::NUMBER,
64    SemanticTokenType::VARIABLE,
65    SemanticTokenType::KEYWORD,
66    SemanticTokenType::TYPE,
67    SemanticTokenType::STRING,
68    SemanticTokenType::OPERATOR,
69    SemanticTokenType::COMMENT,
70    SemanticTokenType::FUNCTION,
71    SemanticTokenType::PARAMETER,
72    SemanticTokenType::PROPERTY,
73];
74
75const SEMANTIC_TOKEN_MODIFIERS: [SemanticTokenModifier; 5] = [
76    SemanticTokenModifier::DECLARATION,
77    SemanticTokenModifier::DEFINITION,
78    SemanticTokenModifier::DEFAULT_LIBRARY,
79    SemanticTokenModifier::READONLY,
80    SemanticTokenModifier::STATIC,
81];
82
83/// A subcommand for running the server.
84#[derive(Clone, Debug)]
85#[cfg_attr(feature = "cli", derive(Parser))]
86pub struct Server {
87    /// Port that the server should listen
88    #[cfg_attr(feature = "cli", clap(long, default_value = "8080"))]
89    pub socket: i32,
90
91    /// Listen over stdin and stdout instead of a tcp socket.
92    #[cfg_attr(feature = "cli", clap(short, long, default_value = "false"))]
93    pub stdio: bool,
94}
95
96/// The lsp server backend.
97#[derive(Clone)]
98pub struct Backend {
99    /// The client for the backend.
100    pub client: Client,
101    /// The file system client to use.
102    pub fs: Arc<crate::fs::FileManager>,
103    /// The workspace folders.
104    pub workspace_folders: DashMap<String, WorkspaceFolder>,
105    /// The stdlib completions for the language.
106    pub stdlib_completions: HashMap<String, CompletionItem>,
107    /// The stdlib signatures for the language.
108    pub stdlib_signatures: HashMap<String, SignatureHelp>,
109    /// For all KwArg functions in std, a map from their arg names to arg help snippets (markdown format).
110    pub stdlib_args: HashMap<String, HashMap<String, String>>,
111    /// Token maps.
112    pub(super) token_map: DashMap<String, TokenStream>,
113    /// AST maps.
114    pub ast_map: DashMap<String, crate::Program>,
115    /// Current code.
116    pub code_map: DashMap<String, Vec<u8>>,
117    /// Diagnostics.
118    pub diagnostics_map: DashMap<String, Vec<Diagnostic>>,
119    /// Symbols map.
120    pub symbols_map: DashMap<String, Vec<DocumentSymbol>>,
121    /// Semantic tokens map.
122    pub semantic_tokens_map: DashMap<String, Vec<SemanticToken>>,
123    /// The Zoo API client.
124    pub zoo_client: kittycad::Client,
125    /// If we can send telemetry for this user.
126    pub can_send_telemetry: bool,
127    /// Optional executor context to use if we want to execute the code.
128    pub executor_ctx: Arc<RwLock<Option<crate::execution::ExecutorContext>>>,
129    /// If we are currently allowed to execute the ast.
130    pub can_execute: Arc<RwLock<bool>>,
131
132    pub is_initialized: Arc<RwLock<bool>>,
133}
134
135impl Backend {
136    #[cfg(target_arch = "wasm32")]
137    pub fn new_wasm(
138        client: Client,
139        executor_ctx: Option<crate::execution::ExecutorContext>,
140        fs: crate::fs::wasm::FileSystemManager,
141        zoo_client: kittycad::Client,
142        can_send_telemetry: bool,
143    ) -> Result<Self, String> {
144        Self::with_file_manager(
145            client,
146            executor_ctx,
147            crate::fs::FileManager::new(fs),
148            zoo_client,
149            can_send_telemetry,
150        )
151    }
152
153    #[cfg(not(target_arch = "wasm32"))]
154    pub fn new(
155        client: Client,
156        executor_ctx: Option<crate::execution::ExecutorContext>,
157        zoo_client: kittycad::Client,
158        can_send_telemetry: bool,
159    ) -> Result<Self, String> {
160        Self::with_file_manager(
161            client,
162            executor_ctx,
163            crate::fs::FileManager::new(),
164            zoo_client,
165            can_send_telemetry,
166        )
167    }
168
169    fn with_file_manager(
170        client: Client,
171        executor_ctx: Option<crate::execution::ExecutorContext>,
172        fs: crate::fs::FileManager,
173        zoo_client: kittycad::Client,
174        can_send_telemetry: bool,
175    ) -> Result<Self, String> {
176        let stdlib = crate::std::StdLib::new();
177        let kcl_std = crate::docs::kcl_doc::walk_prelude();
178        let stdlib_completions = get_completions_from_stdlib(&stdlib, &kcl_std).map_err(|e| e.to_string())?;
179        let stdlib_signatures = get_signatures_from_stdlib(&stdlib, &kcl_std);
180        let stdlib_args = get_arg_maps_from_stdlib(&stdlib, &kcl_std);
181
182        Ok(Self {
183            client,
184            fs: Arc::new(fs),
185            stdlib_completions,
186            stdlib_signatures,
187            stdlib_args,
188            zoo_client,
189            can_send_telemetry,
190            can_execute: Arc::new(RwLock::new(executor_ctx.is_some())),
191            executor_ctx: Arc::new(RwLock::new(executor_ctx)),
192            workspace_folders: Default::default(),
193            token_map: Default::default(),
194            ast_map: Default::default(),
195            code_map: Default::default(),
196            diagnostics_map: Default::default(),
197            symbols_map: Default::default(),
198            semantic_tokens_map: Default::default(),
199            is_initialized: Default::default(),
200        })
201    }
202
203    fn remove_from_ast_maps(&self, filename: &str) {
204        self.ast_map.remove(filename);
205        self.symbols_map.remove(filename);
206    }
207}
208
209// Implement the shared backend trait for the language server.
210#[async_trait::async_trait]
211impl crate::lsp::backend::Backend for Backend {
212    fn client(&self) -> &Client {
213        &self.client
214    }
215
216    fn fs(&self) -> &Arc<crate::fs::FileManager> {
217        &self.fs
218    }
219
220    async fn is_initialized(&self) -> bool {
221        *self.is_initialized.read().await
222    }
223
224    async fn set_is_initialized(&self, is_initialized: bool) {
225        *self.is_initialized.write().await = is_initialized;
226    }
227
228    async fn workspace_folders(&self) -> Vec<WorkspaceFolder> {
229        // TODO: fix clone
230        self.workspace_folders.iter().map(|i| i.clone()).collect()
231    }
232
233    async fn add_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
234        for folder in folders {
235            self.workspace_folders.insert(folder.name.to_string(), folder);
236        }
237    }
238
239    async fn remove_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
240        for folder in folders {
241            self.workspace_folders.remove(&folder.name);
242        }
243    }
244
245    fn code_map(&self) -> &DashMap<String, Vec<u8>> {
246        &self.code_map
247    }
248
249    async fn insert_code_map(&self, uri: String, text: Vec<u8>) {
250        self.code_map.insert(uri, text);
251    }
252
253    async fn remove_from_code_map(&self, uri: String) -> Option<Vec<u8>> {
254        self.code_map.remove(&uri).map(|x| x.1)
255    }
256
257    async fn clear_code_state(&self) {
258        self.code_map.clear();
259        self.token_map.clear();
260        self.ast_map.clear();
261        self.diagnostics_map.clear();
262        self.symbols_map.clear();
263        self.semantic_tokens_map.clear();
264    }
265
266    fn current_diagnostics_map(&self) -> &DashMap<String, Vec<Diagnostic>> {
267        &self.diagnostics_map
268    }
269
270    async fn inner_on_change(&self, params: TextDocumentItem, force: bool) {
271        if force {
272            crate::bust_cache().await;
273        }
274
275        let filename = params.uri.to_string();
276        // We already updated the code map in the shared backend.
277
278        // Lets update the tokens.
279        let module_id = ModuleId::default();
280        let tokens = match crate::parsing::token::lex(&params.text, module_id) {
281            Ok(tokens) => tokens,
282            Err(err) => {
283                self.add_to_diagnostics(&params, &[err], true).await;
284                self.token_map.remove(&filename);
285                self.remove_from_ast_maps(&filename);
286                self.semantic_tokens_map.remove(&filename);
287                return;
288            }
289        };
290
291        // Get the previous tokens.
292        let tokens_changed = if let Some(previous_tokens) = self.token_map.get(&filename) {
293            *previous_tokens != tokens
294        } else {
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 `{:?}` not accounted for", token_type),
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 = if let Some(ast) = self.ast_map.get(params.uri.as_str()) {
439                let token_index = Arc::new(Mutex::new(token_type_index));
440                let modifier_index: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
441                crate::walk::walk(&ast.ast, |node: crate::walk::Node| {
442                    let Ok(node_range): Result<SourceRange, _> = (&node).try_into() else {
443                        return Ok(true);
444                    };
445
446                    if !node_range.contains(source_range.start()) {
447                        return Ok(true);
448                    }
449
450                    let get_modifier = |modifier: Vec<SemanticTokenModifier>| -> Result<bool> {
451                        let mut mods = modifier_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
452                        let Some(token_modifier_index) = self.get_semantic_token_modifier_index(modifier) else {
453                            return Ok(true);
454                        };
455                        if *mods == 0 {
456                            *mods = token_modifier_index;
457                        } else {
458                            *mods |= token_modifier_index;
459                        }
460                        Ok(false)
461                    };
462
463                    match node {
464                        crate::walk::Node::TagDeclarator(_) => {
465                            return get_modifier(vec![
466                                SemanticTokenModifier::DEFINITION,
467                                SemanticTokenModifier::STATIC,
468                            ]);
469                        }
470                        crate::walk::Node::VariableDeclarator(variable) => {
471                            let sr: SourceRange = (&variable.id).into();
472                            if sr.contains(source_range.start()) {
473                                if let Expr::FunctionExpression(_) = &variable.init {
474                                    let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
475                                    *ti = match self.get_semantic_token_type_index(&SemanticTokenType::FUNCTION) {
476                                        Some(index) => index,
477                                        None => token_type_index,
478                                    };
479                                }
480
481                                return get_modifier(vec![
482                                    SemanticTokenModifier::DECLARATION,
483                                    SemanticTokenModifier::READONLY,
484                                ]);
485                            }
486                        }
487                        crate::walk::Node::Parameter(_) => {
488                            let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
489                            *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PARAMETER) {
490                                Some(index) => index,
491                                None => token_type_index,
492                            };
493                            return Ok(false);
494                        }
495                        crate::walk::Node::MemberExpression(member_expression) => {
496                            let sr: SourceRange = (&member_expression.property).into();
497                            if sr.contains(source_range.start()) {
498                                let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
499                                *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PROPERTY) {
500                                    Some(index) => index,
501                                    None => token_type_index,
502                                };
503                                return Ok(false);
504                            }
505                        }
506                        crate::walk::Node::ObjectProperty(object_property) => {
507                            let sr: SourceRange = (&object_property.key).into();
508                            if sr.contains(source_range.start()) {
509                                let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
510                                *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PROPERTY) {
511                                    Some(index) => index,
512                                    None => token_type_index,
513                                };
514                            }
515                            return get_modifier(vec![SemanticTokenModifier::DECLARATION]);
516                        }
517                        crate::walk::Node::CallExpression(call_expr) => {
518                            let sr: SourceRange = (&call_expr.callee).into();
519                            if sr.contains(source_range.start()) {
520                                let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
521                                *ti = match self.get_semantic_token_type_index(&SemanticTokenType::FUNCTION) {
522                                    Some(index) => index,
523                                    None => token_type_index,
524                                };
525
526                                if self.stdlib_completions.contains_key(&call_expr.callee.name.name) {
527                                    // This is a stdlib function.
528                                    return get_modifier(vec![SemanticTokenModifier::DEFAULT_LIBRARY]);
529                                }
530
531                                return Ok(false);
532                            }
533                        }
534                        _ => {}
535                    }
536                    Ok(true)
537                })
538                .unwrap_or_default();
539
540                let t = if let Ok(guard) = token_index.lock() { *guard } else { 0 };
541                token_type_index = t;
542
543                let m = if let Ok(guard) = modifier_index.lock() {
544                    *guard
545                } else {
546                    0
547                };
548                m
549            } else {
550                0
551            };
552
553            // We need to check if we are on the last token of the line.
554            // If we are starting from the end of the last line just add 1 to the line.
555            // Check if we are on the last token of the line.
556            if let Some(line) = params.text.lines().nth(position.line as usize) {
557                if line.len() == position.character as usize {
558                    // We are on the last token of the line.
559                    // We need to add a new line.
560                    let semantic_token = SemanticToken {
561                        delta_line: position.line - last_position.line + 1,
562                        delta_start: 0,
563                        length: (token.end - token.start) as u32,
564                        token_type: token_type_index,
565                        token_modifiers_bitset,
566                    };
567
568                    semantic_tokens.push(semantic_token);
569
570                    last_position = Position::new(position.line + 1, 0);
571                    continue;
572                }
573            }
574
575            let semantic_token = SemanticToken {
576                delta_line: position.line - last_position.line,
577                delta_start: if position.line != last_position.line {
578                    position.character
579                } else {
580                    position.character - last_position.character
581                },
582                length: (token.end - token.start) as u32,
583                token_type: token_type_index,
584                token_modifiers_bitset,
585            };
586
587            semantic_tokens.push(semantic_token);
588
589            last_position = position;
590        }
591        self.semantic_tokens_map.insert(params.uri.to_string(), semantic_tokens);
592    }
593
594    async fn clear_diagnostics_map(&self, uri: &url::Url, severity: Option<DiagnosticSeverity>) {
595        let Some(mut items) = self.diagnostics_map.get_mut(uri.as_str()) else {
596            return;
597        };
598
599        // If we only want to clear a specific severity, do that.
600        if let Some(severity) = severity {
601            items.retain(|x| x.severity != Some(severity));
602        } else {
603            items.clear();
604        }
605
606        if items.is_empty() {
607            #[cfg(not(target_arch = "wasm32"))]
608            {
609                self.client.publish_diagnostics(uri.clone(), items.clone(), None).await;
610            }
611
612            // We need to drop the items here.
613            drop(items);
614
615            self.diagnostics_map.remove(uri.as_str());
616        } else {
617            // We don't need to update the map since we used get_mut.
618
619            #[cfg(not(target_arch = "wasm32"))]
620            {
621                self.client.publish_diagnostics(uri.clone(), items.clone(), None).await;
622            }
623        }
624    }
625
626    async fn add_to_diagnostics<DiagT: IntoDiagnostic + std::fmt::Debug>(
627        &self,
628        params: &TextDocumentItem,
629        diagnostics: &[DiagT],
630        clear_all_before_add: bool,
631    ) {
632        if diagnostics.is_empty() {
633            return;
634        }
635
636        if clear_all_before_add {
637            self.clear_diagnostics_map(&params.uri, None).await;
638        } else if diagnostics.iter().all(|x| x.severity() == DiagnosticSeverity::ERROR) {
639            // If the diagnostic is an error, it will be the only error we get since that halts
640            // execution.
641            // Clear the diagnostics before we add a new one.
642            self.clear_diagnostics_map(&params.uri, Some(DiagnosticSeverity::ERROR))
643                .await;
644        } else if diagnostics
645            .iter()
646            .all(|x| x.severity() == DiagnosticSeverity::INFORMATION)
647        {
648            // If the diagnostic is a lint, we will pass them all to add at once so we need to
649            // clear the old ones.
650            self.clear_diagnostics_map(&params.uri, Some(DiagnosticSeverity::INFORMATION))
651                .await;
652        }
653
654        let mut items = if let Some(items) = self.diagnostics_map.get(params.uri.as_str()) {
655            // TODO: Would be awesome to fix the clone here.
656            items.clone()
657        } else {
658            vec![]
659        };
660
661        for diagnostic in diagnostics {
662            let lsp_d = diagnostic.to_lsp_diagnostics(&params.text);
663            // Make sure we don't duplicate diagnostics.
664            for d in lsp_d {
665                if !items.iter().any(|x| x == &d) {
666                    items.push(d);
667                }
668            }
669        }
670
671        self.diagnostics_map.insert(params.uri.to_string(), items.clone());
672
673        self.client.publish_diagnostics(params.uri.clone(), items, None).await;
674    }
675
676    async fn execute(&self, params: &TextDocumentItem, ast: &Program) -> Result<()> {
677        // Check if we can execute.
678        if !self.can_execute().await {
679            return Ok(());
680        }
681
682        // Execute the code if we have an executor context.
683        let ctx = self.executor_ctx().await;
684        let Some(ref executor_ctx) = *ctx else {
685            return Ok(());
686        };
687
688        if !self.is_initialized().await {
689            // We are not initialized yet.
690            return Ok(());
691        }
692
693        match executor_ctx.run_with_caching(ast.clone()).await {
694            Err(err) => {
695                self.add_to_diagnostics(params, &[err], false).await;
696
697                // Since we already published the diagnostics we don't really care about the error
698                // string.
699                Err(anyhow::anyhow!("failed to execute code"))
700            }
701            Ok(_) => Ok(()),
702        }
703    }
704
705    pub fn get_semantic_token_type_index(&self, token_type: &SemanticTokenType) -> Option<u32> {
706        SEMANTIC_TOKEN_TYPES
707            .iter()
708            .position(|x| *x == *token_type)
709            .map(|y| y as u32)
710    }
711
712    pub fn get_semantic_token_modifier_index(&self, token_types: Vec<SemanticTokenModifier>) -> Option<u32> {
713        if token_types.is_empty() {
714            return None;
715        }
716
717        let mut modifier = None;
718        for token_type in token_types {
719            if let Some(index) = SEMANTIC_TOKEN_MODIFIERS
720                .iter()
721                .position(|x| *x == token_type)
722                .map(|y| y as u32)
723            {
724                modifier = match modifier {
725                    Some(modifier) => Some(modifier | index),
726                    None => Some(index),
727                };
728            }
729        }
730        modifier
731    }
732
733    pub async fn create_zip(&self) -> Result<Vec<u8>> {
734        // Collect all the file data we know.
735        let mut buf = vec![];
736        let mut zip = zip::ZipWriter::new(std::io::Cursor::new(&mut buf));
737        for code in self.code_map.iter() {
738            let entry = code.key();
739            let value = code.value();
740            let file_name = entry.replace("file://", "").to_string();
741
742            let options = zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
743            zip.start_file(file_name, options)?;
744            zip.write_all(value)?;
745        }
746        // Apply the changes you've made.
747        // Dropping the `ZipWriter` will have the same effect, but may silently fail
748        zip.finish()?;
749
750        Ok(buf)
751    }
752
753    pub async fn send_telemetry(&self) -> Result<()> {
754        // Get information about the user.
755        let user = self
756            .zoo_client
757            .users()
758            .get_self()
759            .await
760            .map_err(|e| anyhow::anyhow!(e.to_string()))?;
761
762        // Hash the user's id.
763        // Create a SHA-256 object
764        let mut hasher = sha2::Sha256::new();
765        // Write input message
766        hasher.update(user.id);
767        // Read hash digest and consume hasher
768        let result = hasher.finalize();
769        // Get the hash as a string.
770        let user_id_hash = format!("{:x}", result);
771
772        // Get the workspace folders.
773        // The key of the workspace folder is the project name.
774        let workspace_folders = self.workspace_folders().await;
775        let project_names: Vec<&str> = workspace_folders.iter().map(|v| v.name.as_str()).collect::<Vec<_>>();
776        // Get the first name.
777        let project_name = project_names
778            .first()
779            .ok_or_else(|| anyhow::anyhow!("no project names"))?
780            .to_string();
781
782        // Send the telemetry data.
783        self.zoo_client
784            .meta()
785            .create_event(
786                vec![kittycad::types::multipart::Attachment {
787                    // Clean the URI part.
788                    name: "attachment".to_string(),
789                    filepath: Some("attachment.zip".into()),
790                    content_type: Some("application/x-zip".to_string()),
791                    data: self.create_zip().await?,
792                }],
793                &kittycad::types::Event {
794                    // This gets generated server side so leave empty for now.
795                    attachment_uri: None,
796                    created_at: chrono::Utc::now(),
797                    event_type: kittycad::types::ModelingAppEventType::SuccessfulCompileBeforeClose,
798                    last_compiled_at: Some(chrono::Utc::now()),
799                    // We do not have project descriptions yet.
800                    project_description: None,
801                    project_name,
802                    // The UUID for the Design Studio.
803                    // We can unwrap here because we know it will not panic.
804                    source_id: uuid::Uuid::from_str("70178592-dfca-47b3-bd2d-6fce2bcaee04").unwrap(),
805                    type_: kittycad::types::Type::ModelingAppEvent,
806                    user_id: user_id_hash,
807                },
808            )
809            .await
810            .map_err(|e| anyhow::anyhow!(e.to_string()))?;
811
812        Ok(())
813    }
814
815    pub async fn update_can_execute(
816        &self,
817        params: custom_notifications::UpdateCanExecuteParams,
818    ) -> RpcResult<custom_notifications::UpdateCanExecuteResponse> {
819        let mut can_execute = self.can_execute.write().await;
820
821        if *can_execute == params.can_execute {
822            return Ok(custom_notifications::UpdateCanExecuteResponse {});
823        }
824
825        *can_execute = params.can_execute;
826
827        Ok(custom_notifications::UpdateCanExecuteResponse {})
828    }
829}
830
831#[tower_lsp::async_trait]
832impl LanguageServer for Backend {
833    async fn initialize(&self, params: InitializeParams) -> RpcResult<InitializeResult> {
834        self.client
835            .log_message(MessageType::INFO, format!("initialize: {:?}", params))
836            .await;
837
838        Ok(InitializeResult {
839            capabilities: ServerCapabilities {
840                completion_provider: Some(CompletionOptions {
841                    resolve_provider: Some(false),
842                    trigger_characters: Some(vec![".".to_string()]),
843                    work_done_progress_options: Default::default(),
844                    all_commit_characters: None,
845                    ..Default::default()
846                }),
847                diagnostic_provider: Some(DiagnosticServerCapabilities::Options(DiagnosticOptions {
848                    ..Default::default()
849                })),
850                document_formatting_provider: Some(OneOf::Left(true)),
851                folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
852                hover_provider: Some(HoverProviderCapability::Simple(true)),
853                inlay_hint_provider: Some(OneOf::Left(true)),
854                rename_provider: Some(OneOf::Left(true)),
855                semantic_tokens_provider: Some(SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(
856                    SemanticTokensRegistrationOptions {
857                        text_document_registration_options: {
858                            TextDocumentRegistrationOptions {
859                                document_selector: Some(vec![DocumentFilter {
860                                    language: Some("kcl".to_string()),
861                                    scheme: Some("file".to_string()),
862                                    pattern: None,
863                                }]),
864                            }
865                        },
866                        semantic_tokens_options: SemanticTokensOptions {
867                            work_done_progress_options: WorkDoneProgressOptions::default(),
868                            legend: SemanticTokensLegend {
869                                token_types: SEMANTIC_TOKEN_TYPES.to_vec(),
870                                token_modifiers: SEMANTIC_TOKEN_MODIFIERS.to_vec(),
871                            },
872                            range: Some(false),
873                            full: Some(SemanticTokensFullOptions::Bool(true)),
874                        },
875                        static_registration_options: StaticRegistrationOptions::default(),
876                    },
877                )),
878                signature_help_provider: Some(SignatureHelpOptions {
879                    trigger_characters: None,
880                    retrigger_characters: None,
881                    ..Default::default()
882                }),
883                text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
884                    open_close: Some(true),
885                    change: Some(TextDocumentSyncKind::FULL),
886                    ..Default::default()
887                })),
888                workspace: Some(WorkspaceServerCapabilities {
889                    workspace_folders: Some(WorkspaceFoldersServerCapabilities {
890                        supported: Some(true),
891                        change_notifications: Some(OneOf::Left(true)),
892                    }),
893                    file_operations: None,
894                }),
895                ..Default::default()
896            },
897            ..Default::default()
898        })
899    }
900
901    async fn initialized(&self, params: InitializedParams) {
902        self.do_initialized(params).await
903    }
904
905    async fn shutdown(&self) -> RpcResult<()> {
906        self.do_shutdown().await
907    }
908
909    async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
910        self.do_did_change_workspace_folders(params).await
911    }
912
913    async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
914        self.do_did_change_configuration(params).await
915    }
916
917    async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
918        self.do_did_change_watched_files(params).await
919    }
920
921    async fn did_create_files(&self, params: CreateFilesParams) {
922        self.do_did_create_files(params).await
923    }
924
925    async fn did_rename_files(&self, params: RenameFilesParams) {
926        self.do_did_rename_files(params).await
927    }
928
929    async fn did_delete_files(&self, params: DeleteFilesParams) {
930        self.do_did_delete_files(params).await
931    }
932
933    async fn did_open(&self, params: DidOpenTextDocumentParams) {
934        self.do_did_open(params).await
935    }
936
937    async fn did_change(&self, params: DidChangeTextDocumentParams) {
938        self.do_did_change(params).await;
939    }
940
941    async fn did_save(&self, params: DidSaveTextDocumentParams) {
942        self.do_did_save(params).await
943    }
944
945    async fn did_close(&self, params: DidCloseTextDocumentParams) {
946        self.do_did_close(params).await;
947
948        // Inject telemetry if we can train on the user's code.
949        // Return early if we cannot.
950        if !self.can_send_telemetry {
951            return;
952        }
953
954        // In wasm this needs to be spawn_local since fucking reqwests doesn't implement Send for wasm.
955        #[cfg(target_arch = "wasm32")]
956        {
957            let be = self.clone();
958            wasm_bindgen_futures::spawn_local(async move {
959                if let Err(err) = be.send_telemetry().await {
960                    be.client
961                        .log_message(MessageType::WARNING, format!("failed to send telemetry: {}", err))
962                        .await;
963                }
964            });
965        }
966        #[cfg(not(target_arch = "wasm32"))]
967        if let Err(err) = self.send_telemetry().await {
968            self.client
969                .log_message(MessageType::WARNING, format!("failed to send telemetry: {}", err))
970                .await;
971        }
972    }
973
974    async fn hover(&self, params: HoverParams) -> RpcResult<Option<LspHover>> {
975        let filename = params.text_document_position_params.text_document.uri.to_string();
976
977        let Some(current_code) = self.code_map.get(&filename) else {
978            return Ok(None);
979        };
980        let Ok(current_code) = std::str::from_utf8(&current_code) else {
981            return Ok(None);
982        };
983
984        let pos = position_to_char_index(params.text_document_position_params.position, current_code);
985
986        // Let's iterate over the AST and find the node that contains the cursor.
987        let Some(ast) = self.ast_map.get(&filename) else {
988            return Ok(None);
989        };
990
991        let Some(hover) = ast
992            .ast
993            .get_hover_value_for_position(pos, current_code, &HoverOpts::default_for_hover())
994        else {
995            return Ok(None);
996        };
997
998        match hover {
999            Hover::Function { name, range } => {
1000                let (sig, docs) = if let Some(Some(result)) = with_cached_var(&name, |value| {
1001                    match value {
1002                        // User-defined or KCL std function
1003                        KclValue::Function {
1004                            value: FunctionSource::User { ast, .. },
1005                            ..
1006                        } => {
1007                            // TODO get docs from comments
1008                            Some((ast.signature(), ""))
1009                        }
1010                        _ => None,
1011                    }
1012                })
1013                .await
1014                {
1015                    result
1016                } else {
1017                    // Get the docs for this function.
1018                    let Some(completion) = self.stdlib_completions.get(&name) else {
1019                        return Ok(None);
1020                    };
1021                    let Some(docs) = &completion.documentation else {
1022                        return Ok(None);
1023                    };
1024
1025                    let docs = match docs {
1026                        Documentation::String(docs) => docs,
1027                        Documentation::MarkupContent(MarkupContent { value, .. }) => value,
1028                    };
1029
1030                    let docs = if docs.len() > 320 {
1031                        let end = docs.find("\n\n").or_else(|| docs.find("\n\r\n")).unwrap_or(320);
1032                        &docs[..end]
1033                    } else {
1034                        &**docs
1035                    };
1036
1037                    let Some(label_details) = &completion.label_details else {
1038                        return Ok(None);
1039                    };
1040
1041                    let sig = if let Some(detail) = &label_details.detail {
1042                        detail.clone()
1043                    } else {
1044                        String::new()
1045                    };
1046
1047                    (sig, docs)
1048                };
1049
1050                Ok(Some(LspHover {
1051                    contents: HoverContents::Markup(MarkupContent {
1052                        kind: MarkupKind::Markdown,
1053                        value: format!("```\n{}{}\n```\n\n{}", name, sig, docs),
1054                    }),
1055                    range: Some(range),
1056                }))
1057            }
1058            Hover::Type { name, range } => {
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                Ok(Some(LspHover {
1079                    contents: HoverContents::Markup(MarkupContent {
1080                        kind: MarkupKind::Markdown,
1081                        value: format!("```\n{}\n```\n\n{}", name, docs),
1082                    }),
1083                    range: Some(range),
1084                }))
1085            }
1086            Hover::KwArg {
1087                name,
1088                callee_name,
1089                range,
1090            } => {
1091                // TODO handle user-defined functions too
1092
1093                let Some(arg_map) = self.stdlib_args.get(&callee_name) else {
1094                    return Ok(None);
1095                };
1096
1097                let Some(tip) = arg_map.get(&name) else {
1098                    return Ok(None);
1099                };
1100
1101                Ok(Some(LspHover {
1102                    contents: HoverContents::Markup(MarkupContent {
1103                        kind: MarkupKind::Markdown,
1104                        value: tip.clone(),
1105                    }),
1106                    range: Some(range),
1107                }))
1108            }
1109            Hover::Variable {
1110                name,
1111                ty: Some(ty),
1112                range,
1113            } => Ok(Some(LspHover {
1114                contents: HoverContents::Markup(MarkupContent {
1115                    kind: MarkupKind::Markdown,
1116                    value: format!("```\n{}: {}\n```", name, ty),
1117                }),
1118                range: Some(range),
1119            })),
1120            Hover::Variable { name, ty: None, range } => Ok(with_cached_var(&name, |value| {
1121                let mut text: String = format!("```\n{}", name);
1122                if let Some(ty) = value.principal_type() {
1123                    text.push_str(&format!(": {}", ty.human_friendly_type()));
1124                }
1125                if let Some(v) = value.value_str() {
1126                    text.push_str(&format!(" = {}", v));
1127                }
1128                text.push_str("\n```");
1129
1130                LspHover {
1131                    contents: HoverContents::Markup(MarkupContent {
1132                        kind: MarkupKind::Markdown,
1133                        value: text,
1134                    }),
1135                    range: Some(range),
1136                }
1137            })
1138            .await),
1139            Hover::Signature { .. } => Ok(None),
1140            Hover::Comment { value, range } => Ok(Some(LspHover {
1141                contents: HoverContents::Markup(MarkupContent {
1142                    kind: MarkupKind::Markdown,
1143                    value,
1144                }),
1145                range: Some(range),
1146            })),
1147        }
1148    }
1149
1150    async fn completion(&self, params: CompletionParams) -> RpcResult<Option<CompletionResponse>> {
1151        let mut completions = vec![CompletionItem {
1152            label: PIPE_OPERATOR.to_string(),
1153            label_details: None,
1154            kind: Some(CompletionItemKind::OPERATOR),
1155            detail: Some("A pipe operator.".to_string()),
1156            documentation: Some(Documentation::MarkupContent(MarkupContent {
1157                kind: MarkupKind::Markdown,
1158                value: "A pipe operator.".to_string(),
1159            })),
1160            deprecated: Some(false),
1161            preselect: None,
1162            sort_text: None,
1163            filter_text: None,
1164            insert_text: Some("|> ".to_string()),
1165            insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
1166            insert_text_mode: None,
1167            text_edit: None,
1168            additional_text_edits: None,
1169            command: None,
1170            commit_characters: None,
1171            data: None,
1172            tags: None,
1173        }];
1174
1175        // Get the current line up to cursor
1176        let Some(current_code) = self
1177            .code_map
1178            .get(params.text_document_position.text_document.uri.as_ref())
1179        else {
1180            return Ok(Some(CompletionResponse::Array(completions)));
1181        };
1182        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1183            return Ok(Some(CompletionResponse::Array(completions)));
1184        };
1185
1186        // Get the current line up to cursor, with bounds checking
1187        if let Some(line) = current_code
1188            .lines()
1189            .nth(params.text_document_position.position.line as usize)
1190        {
1191            let char_pos = params.text_document_position.position.character as usize;
1192            if char_pos <= line.len() {
1193                let line_prefix = &line[..char_pos];
1194                // Get last word
1195                let last_word = line_prefix
1196                    .split(|c: char| c.is_whitespace() || c.is_ascii_punctuation())
1197                    .next_back()
1198                    .unwrap_or("");
1199
1200                // If the last word starts with a digit, return no completions
1201                if !last_word.is_empty() && last_word.chars().next().unwrap().is_ascii_digit() {
1202                    return Ok(None);
1203                }
1204            }
1205        }
1206
1207        completions.extend(self.stdlib_completions.values().cloned());
1208
1209        // Add more to the completions if we have more.
1210        let Some(ast) = self
1211            .ast_map
1212            .get(params.text_document_position.text_document.uri.as_ref())
1213        else {
1214            return Ok(Some(CompletionResponse::Array(completions)));
1215        };
1216
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        let position = position_to_char_index(params.text_document_position.position, current_code);
1228        if ast.ast.in_comment(position) {
1229            // If we are in a code comment we don't want to show completions.
1230            return Ok(None);
1231        }
1232
1233        // Get the completion items for the ast.
1234        let Ok(variables) = ast.ast.completion_items(position) else {
1235            return Ok(Some(CompletionResponse::Array(completions)));
1236        };
1237
1238        // Get our variables from our AST to include in our completions.
1239        completions.extend(variables);
1240
1241        Ok(Some(CompletionResponse::Array(completions)))
1242    }
1243
1244    async fn diagnostic(&self, params: DocumentDiagnosticParams) -> RpcResult<DocumentDiagnosticReportResult> {
1245        let filename = params.text_document.uri.to_string();
1246
1247        // Get the current diagnostics for this file.
1248        let Some(items) = self.diagnostics_map.get(&filename) else {
1249            // Send an empty report.
1250            return Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1251                RelatedFullDocumentDiagnosticReport {
1252                    related_documents: None,
1253                    full_document_diagnostic_report: FullDocumentDiagnosticReport {
1254                        result_id: None,
1255                        items: vec![],
1256                    },
1257                },
1258            )));
1259        };
1260
1261        Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1262            RelatedFullDocumentDiagnosticReport {
1263                related_documents: None,
1264                full_document_diagnostic_report: FullDocumentDiagnosticReport {
1265                    result_id: None,
1266                    items: items.clone(),
1267                },
1268            },
1269        )))
1270    }
1271
1272    async fn signature_help(&self, params: SignatureHelpParams) -> RpcResult<Option<SignatureHelp>> {
1273        let filename = params.text_document_position_params.text_document.uri.to_string();
1274
1275        let Some(current_code) = self.code_map.get(&filename) else {
1276            return Ok(None);
1277        };
1278        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1279            return Ok(None);
1280        };
1281
1282        let pos = position_to_char_index(params.text_document_position_params.position, current_code);
1283
1284        // Let's iterate over the AST and find the node that contains the cursor.
1285        let Some(ast) = self.ast_map.get(&filename) else {
1286            return Ok(None);
1287        };
1288
1289        let Some(value) = ast.ast.get_expr_for_position(pos) else {
1290            return Ok(None);
1291        };
1292
1293        let Some(hover) =
1294            value.get_hover_value_for_position(pos, current_code, &HoverOpts::default_for_signature_help())
1295        else {
1296            return Ok(None);
1297        };
1298
1299        match hover {
1300            Hover::Function { name, range: _ } => {
1301                // Get the docs for this function.
1302                let Some(signature) = self.stdlib_signatures.get(&name) else {
1303                    return Ok(None);
1304                };
1305
1306                Ok(Some(signature.clone()))
1307            }
1308            Hover::Signature {
1309                name,
1310                parameter_index,
1311                range: _,
1312            } => {
1313                let Some(signature) = self.stdlib_signatures.get(&name) else {
1314                    return Ok(None);
1315                };
1316
1317                let mut signature = signature.clone();
1318
1319                signature.active_parameter = Some(parameter_index);
1320
1321                Ok(Some(signature))
1322            }
1323            _ => {
1324                return Ok(None);
1325            }
1326        }
1327    }
1328
1329    async fn inlay_hint(&self, _params: InlayHintParams) -> RpcResult<Option<Vec<InlayHint>>> {
1330        // TODO: do this
1331
1332        Ok(None)
1333    }
1334
1335    async fn semantic_tokens_full(&self, params: SemanticTokensParams) -> RpcResult<Option<SemanticTokensResult>> {
1336        let filename = params.text_document.uri.to_string();
1337
1338        let Some(semantic_tokens) = self.semantic_tokens_map.get(&filename) else {
1339            return Ok(None);
1340        };
1341
1342        Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
1343            result_id: None,
1344            data: semantic_tokens.clone(),
1345        })))
1346    }
1347
1348    async fn document_symbol(&self, params: DocumentSymbolParams) -> RpcResult<Option<DocumentSymbolResponse>> {
1349        let filename = params.text_document.uri.to_string();
1350
1351        let Some(symbols) = self.symbols_map.get(&filename) else {
1352            return Ok(None);
1353        };
1354
1355        Ok(Some(DocumentSymbolResponse::Nested(symbols.clone())))
1356    }
1357
1358    async fn formatting(&self, params: DocumentFormattingParams) -> RpcResult<Option<Vec<TextEdit>>> {
1359        let filename = params.text_document.uri.to_string();
1360
1361        let Some(current_code) = self.code_map.get(&filename) else {
1362            return Ok(None);
1363        };
1364        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1365            return Ok(None);
1366        };
1367
1368        // Parse the ast.
1369        // I don't know if we need to do this again since it should be updated in the context.
1370        // But I figure better safe than sorry since this will write back out to the file.
1371        let module_id = ModuleId::default();
1372        let Ok(ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
1373            return Ok(None);
1374        };
1375        // Now recast it.
1376        let recast = ast.recast(
1377            &crate::parsing::ast::types::FormatOptions {
1378                tab_size: params.options.tab_size as usize,
1379                insert_final_newline: params.options.insert_final_newline.unwrap_or(false),
1380                use_tabs: !params.options.insert_spaces,
1381            },
1382            0,
1383        );
1384        let source_range = SourceRange::new(0, current_code.len(), module_id);
1385        let range = source_range.to_lsp_range(current_code);
1386        Ok(Some(vec![TextEdit {
1387            new_text: recast,
1388            range,
1389        }]))
1390    }
1391
1392    async fn rename(&self, params: RenameParams) -> RpcResult<Option<WorkspaceEdit>> {
1393        let filename = params.text_document_position.text_document.uri.to_string();
1394
1395        let Some(current_code) = self.code_map.get(&filename) else {
1396            return Ok(None);
1397        };
1398        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1399            return Ok(None);
1400        };
1401
1402        // Parse the ast.
1403        // I don't know if we need to do this again since it should be updated in the context.
1404        // But I figure better safe than sorry since this will write back out to the file.
1405        let module_id = ModuleId::default();
1406        let Ok(mut ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
1407            return Ok(None);
1408        };
1409
1410        // Let's convert the position to a character index.
1411        let pos = position_to_char_index(params.text_document_position.position, current_code);
1412        // Now let's perform the rename on the ast.
1413        ast.rename_symbol(&params.new_name, pos);
1414        // Now recast it.
1415        let recast = ast.recast(&Default::default(), 0);
1416        let source_range = SourceRange::new(0, current_code.len() - 1, module_id);
1417        let range = source_range.to_lsp_range(current_code);
1418        Ok(Some(WorkspaceEdit {
1419            changes: Some(HashMap::from([(
1420                params.text_document_position.text_document.uri,
1421                vec![TextEdit {
1422                    new_text: recast,
1423                    range,
1424                }],
1425            )])),
1426            document_changes: None,
1427            change_annotations: None,
1428        }))
1429    }
1430
1431    async fn folding_range(&self, params: FoldingRangeParams) -> RpcResult<Option<Vec<FoldingRange>>> {
1432        let filename = params.text_document.uri.to_string();
1433
1434        // Get the ast.
1435        let Some(ast) = self.ast_map.get(&filename) else {
1436            return Ok(None);
1437        };
1438
1439        // Get the folding ranges.
1440        let folding_ranges = ast.ast.get_lsp_folding_ranges();
1441
1442        if folding_ranges.is_empty() {
1443            return Ok(None);
1444        }
1445
1446        Ok(Some(folding_ranges))
1447    }
1448
1449    async fn code_action(&self, params: CodeActionParams) -> RpcResult<Option<CodeActionResponse>> {
1450        let actions = params
1451            .context
1452            .diagnostics
1453            .into_iter()
1454            .filter_map(|diagnostic| {
1455                let (suggestion, range) = diagnostic.data.as_ref().and_then(|data| {
1456                    serde_json::from_value::<(Suggestion, tower_lsp::lsp_types::Range)>(data.clone()).ok()
1457                })?;
1458                let edit = TextEdit {
1459                    range,
1460                    new_text: suggestion.insert,
1461                };
1462                let changes = HashMap::from([(params.text_document.uri.clone(), vec![edit])]);
1463                Some(CodeActionOrCommand::CodeAction(CodeAction {
1464                    title: suggestion.title,
1465                    kind: Some(CodeActionKind::QUICKFIX),
1466                    diagnostics: Some(vec![diagnostic]),
1467                    edit: Some(WorkspaceEdit {
1468                        changes: Some(changes),
1469                        document_changes: None,
1470                        change_annotations: None,
1471                    }),
1472                    command: None,
1473                    is_preferred: Some(true),
1474                    disabled: None,
1475                    data: None,
1476                }))
1477            })
1478            .collect();
1479
1480        Ok(Some(actions))
1481    }
1482}
1483
1484/// Get completions from our stdlib.
1485pub fn get_completions_from_stdlib(
1486    stdlib: &crate::std::StdLib,
1487    kcl_std: &[DocData],
1488) -> Result<HashMap<String, CompletionItem>> {
1489    let mut completions = HashMap::new();
1490    let combined = stdlib.combined();
1491
1492    for internal_fn in combined.values() {
1493        completions.insert(internal_fn.name(), internal_fn.to_completion_item()?);
1494    }
1495
1496    for d in kcl_std {
1497        completions.insert(d.name().to_owned(), d.to_completion_item());
1498    }
1499
1500    let variable_kinds = VariableKind::to_completion_items();
1501    for variable_kind in variable_kinds {
1502        completions.insert(variable_kind.label.clone(), variable_kind);
1503    }
1504
1505    Ok(completions)
1506}
1507
1508/// Get signatures from our stdlib.
1509pub fn get_signatures_from_stdlib(stdlib: &crate::std::StdLib, kcl_std: &[DocData]) -> HashMap<String, SignatureHelp> {
1510    let mut signatures = HashMap::new();
1511    let combined = stdlib.combined();
1512
1513    for internal_fn in combined.values() {
1514        signatures.insert(internal_fn.name(), internal_fn.to_signature_help());
1515    }
1516
1517    for d in kcl_std {
1518        if let Some(sig) = d.to_signature_help() {
1519            signatures.insert(d.name().to_owned(), sig);
1520        }
1521    }
1522
1523    signatures
1524}
1525
1526/// Get signatures from our stdlib.
1527pub fn get_arg_maps_from_stdlib(
1528    stdlib: &crate::std::StdLib,
1529    kcl_std: &[DocData],
1530) -> HashMap<String, HashMap<String, String>> {
1531    let mut result = HashMap::new();
1532    let combined = stdlib.combined();
1533
1534    for internal_fn in combined.values() {
1535        if internal_fn.keyword_arguments() {
1536            let arg_map: HashMap<String, String> = internal_fn
1537                .args(false)
1538                .into_iter()
1539                .map(|data| {
1540                    let mut tip = "```\n".to_owned();
1541                    tip.push_str(&data.name.clone());
1542                    if !data.required {
1543                        tip.push('?');
1544                    }
1545                    if !data.type_.is_empty() {
1546                        tip.push_str(": ");
1547                        tip.push_str(&data.type_);
1548                    }
1549                    tip.push_str("\n```");
1550                    if !data.description.is_empty() {
1551                        tip.push_str("\n\n");
1552                        tip.push_str(&data.description);
1553                    }
1554                    (data.name, tip)
1555                })
1556                .collect();
1557            if !arg_map.is_empty() {
1558                result.insert(internal_fn.name(), arg_map);
1559            }
1560        }
1561    }
1562
1563    for _d in kcl_std {
1564        // TODO add KCL std fns
1565    }
1566
1567    result
1568}
1569
1570/// Convert a position to a character index from the start of the file.
1571fn position_to_char_index(position: Position, code: &str) -> usize {
1572    // Get the character position from the start of the file.
1573    let mut char_position = 0;
1574    for (index, line) in code.lines().enumerate() {
1575        if index == position.line as usize {
1576            char_position += position.character as usize;
1577            break;
1578        } else {
1579            char_position += line.len() + 1;
1580        }
1581    }
1582
1583    char_position
1584}
1585
1586async fn with_cached_var<T>(name: &str, f: impl Fn(&KclValue) -> T) -> Option<T> {
1587    let mem = cache::read_old_memory().await?;
1588    let value = mem.0.get(name, SourceRange::default()).ok()?;
1589
1590    Some(f(value))
1591}