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