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