Skip to main content

kcl_lib/lsp/kcl/
mod.rs

1//! Functions for the `kcl` lsp server.
2#![allow(dead_code)]
3
4use std::{
5    collections::HashMap,
6    io::Write,
7    str::FromStr,
8    sync::{Arc, Mutex},
9};
10
11use anyhow::Result;
12#[cfg(feature = "cli")]
13use clap::Parser;
14use dashmap::DashMap;
15use sha2::Digest;
16use tokio::sync::RwLock;
17use tower_lsp::{
18    Client, LanguageServer,
19    jsonrpc::Result as RpcResult,
20    lsp_types::{
21        CodeAction, CodeActionKind, CodeActionOptions, CodeActionOrCommand, CodeActionParams,
22        CodeActionProviderCapability, CodeActionResponse, ColorInformation, ColorPresentation, ColorPresentationParams,
23        ColorProviderCapability, CompletionItem, CompletionItemKind, CompletionOptions, CompletionParams,
24        CompletionResponse, CreateFilesParams, DeleteFilesParams, Diagnostic, DiagnosticOptions,
25        DiagnosticServerCapabilities, DiagnosticSeverity, DidChangeConfigurationParams, DidChangeTextDocumentParams,
26        DidChangeWatchedFilesParams, DidChangeWorkspaceFoldersParams, DidCloseTextDocumentParams,
27        DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentColorParams, DocumentDiagnosticParams,
28        DocumentDiagnosticReport, DocumentDiagnosticReportResult, DocumentFilter, DocumentFormattingParams,
29        DocumentSymbol, DocumentSymbolParams, DocumentSymbolResponse, Documentation, FoldingRange, FoldingRangeParams,
30        FoldingRangeProviderCapability, FullDocumentDiagnosticReport, Hover as LspHover, HoverContents, HoverParams,
31        HoverProviderCapability, InitializeParams, InitializeResult, InitializedParams, InlayHint, InlayHintParams,
32        InsertTextFormat, MarkupContent, MarkupKind, MessageType, OneOf, Position, PrepareRenameResponse,
33        RelatedFullDocumentDiagnosticReport, RenameFilesParams, RenameParams, SemanticToken, SemanticTokenModifier,
34        SemanticTokenType, SemanticTokens, SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions,
35        SemanticTokensParams, SemanticTokensRegistrationOptions, SemanticTokensResult,
36        SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelp, SignatureHelpOptions, SignatureHelpParams,
37        StaticRegistrationOptions, TextDocumentItem, TextDocumentPositionParams, TextDocumentRegistrationOptions,
38        TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, TextEdit, WorkDoneProgressOptions,
39        WorkspaceEdit, WorkspaceFolder, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
40    },
41};
42
43use crate::{
44    ModuleId, Program, SourceRange,
45    docs::kcl_doc::{ArgData, ModData},
46    exec::KclValue,
47    execution::cache,
48    lsp::{
49        LspSuggestion, ToLspRange,
50        backend::Backend as _,
51        kcl::hover::{Hover, HoverOpts},
52        util::IntoDiagnostic,
53    },
54    parsing::{
55        PIPE_OPERATOR,
56        ast::types::{Expr, Node, VariableKind},
57        token::TokenStream,
58    },
59};
60
61pub mod custom_notifications;
62mod hover;
63
64const SEMANTIC_TOKEN_TYPES: [SemanticTokenType; 10] = [
65    SemanticTokenType::NUMBER,
66    SemanticTokenType::VARIABLE,
67    SemanticTokenType::KEYWORD,
68    SemanticTokenType::TYPE,
69    SemanticTokenType::STRING,
70    SemanticTokenType::OPERATOR,
71    SemanticTokenType::COMMENT,
72    SemanticTokenType::FUNCTION,
73    SemanticTokenType::PARAMETER,
74    SemanticTokenType::PROPERTY,
75];
76
77const SEMANTIC_TOKEN_MODIFIERS: [SemanticTokenModifier; 5] = [
78    SemanticTokenModifier::DECLARATION,
79    SemanticTokenModifier::DEFINITION,
80    SemanticTokenModifier::DEFAULT_LIBRARY,
81    SemanticTokenModifier::READONLY,
82    SemanticTokenModifier::STATIC,
83];
84
85/// A subcommand for running the server.
86#[derive(Clone, Debug)]
87#[cfg_attr(feature = "cli", derive(Parser))]
88pub struct Server {
89    /// Port that the server should listen
90    #[cfg_attr(feature = "cli", clap(long, default_value = "8080"))]
91    pub socket: i32,
92
93    /// Listen over stdin and stdout instead of a tcp socket.
94    #[cfg_attr(feature = "cli", clap(short, long, default_value = "false"))]
95    pub stdio: bool,
96}
97
98/// The lsp server backend.
99#[derive(Clone)]
100pub struct Backend {
101    /// The client for the backend.
102    pub client: Client,
103    /// The file system client to use.
104    pub fs: Arc<crate::fs::FileManager>,
105    /// The workspace folders.
106    pub workspace_folders: DashMap<String, WorkspaceFolder>,
107    /// The stdlib completions for the language.
108    pub stdlib_completions: HashMap<String, CompletionItem>,
109    /// The stdlib signatures for the language.
110    pub stdlib_signatures: HashMap<String, SignatureHelp>,
111    /// For all KwArg functions in std, a map from their arg names to arg help snippets (markdown format).
112    pub stdlib_args: HashMap<String, HashMap<String, LspArgData>>,
113    /// Token maps.
114    pub(super) token_map: DashMap<String, TokenStream>,
115    /// AST maps.
116    pub ast_map: DashMap<String, crate::Program>,
117    /// Current code.
118    pub code_map: DashMap<String, Vec<u8>>,
119    /// Diagnostics.
120    pub diagnostics_map: DashMap<String, Vec<Diagnostic>>,
121    /// Symbols map.
122    pub symbols_map: DashMap<String, Vec<DocumentSymbol>>,
123    /// Semantic tokens map.
124    pub semantic_tokens_map: DashMap<String, Vec<SemanticToken>>,
125    /// The Zoo API client.
126    pub zoo_client: kittycad::Client,
127    /// If we can send telemetry for this user.
128    pub can_send_telemetry: bool,
129    /// Optional executor context to use if we want to execute the code.
130    pub executor_ctx: Arc<RwLock<Option<crate::execution::ExecutorContext>>>,
131    /// If we are currently allowed to execute the ast.
132    pub can_execute: Arc<RwLock<bool>>,
133
134    pub is_initialized: Arc<RwLock<bool>>,
135}
136
137impl Backend {
138    #[cfg(target_arch = "wasm32")]
139    pub fn new_wasm(
140        client: Client,
141        executor_ctx: Option<crate::execution::ExecutorContext>,
142        fs: crate::fs::wasm::FileSystemManager,
143        zoo_client: kittycad::Client,
144        can_send_telemetry: bool,
145    ) -> Result<Self, String> {
146        Self::with_file_manager(
147            client,
148            executor_ctx,
149            crate::fs::FileManager::new(fs),
150            zoo_client,
151            can_send_telemetry,
152        )
153    }
154
155    #[cfg(not(target_arch = "wasm32"))]
156    pub fn new(
157        client: Client,
158        executor_ctx: Option<crate::execution::ExecutorContext>,
159        zoo_client: kittycad::Client,
160        can_send_telemetry: bool,
161    ) -> Result<Self, String> {
162        Self::with_file_manager(
163            client,
164            executor_ctx,
165            crate::fs::FileManager::new(),
166            zoo_client,
167            can_send_telemetry,
168        )
169    }
170
171    fn with_file_manager(
172        client: Client,
173        executor_ctx: Option<crate::execution::ExecutorContext>,
174        fs: crate::fs::FileManager,
175        zoo_client: kittycad::Client,
176        can_send_telemetry: bool,
177    ) -> Result<Self, String> {
178        let kcl_std = crate::docs::kcl_doc::walk_prelude();
179        let stdlib_completions = get_completions_from_stdlib(&kcl_std).map_err(|e| e.to_string())?;
180        let stdlib_signatures = get_signatures_from_stdlib(&kcl_std);
181        let stdlib_args = get_arg_maps_from_stdlib(&kcl_std);
182
183        Ok(Self {
184            client,
185            fs: Arc::new(fs),
186            stdlib_completions,
187            stdlib_signatures,
188            stdlib_args,
189            zoo_client,
190            can_send_telemetry,
191            can_execute: Arc::new(RwLock::new(executor_ctx.is_some())),
192            executor_ctx: Arc::new(RwLock::new(executor_ctx)),
193            workspace_folders: Default::default(),
194            token_map: Default::default(),
195            ast_map: Default::default(),
196            code_map: Default::default(),
197            diagnostics_map: Default::default(),
198            symbols_map: Default::default(),
199            semantic_tokens_map: Default::default(),
200            is_initialized: Default::default(),
201        })
202    }
203
204    fn remove_from_ast_maps(&self, filename: &str) {
205        self.ast_map.remove(filename);
206        self.symbols_map.remove(filename);
207    }
208
209    fn try_arg_completions(
210        &self,
211        ast: &Node<crate::parsing::ast::types::Program>,
212        position: usize,
213        current_code: &str,
214    ) -> Option<impl Iterator<Item = CompletionItem>> {
215        let curr_expr = ast.get_expr_for_position(position)?;
216        let hover =
217            curr_expr.get_hover_value_for_position(position, current_code, &HoverOpts::default_for_signature_help())?;
218
219        // Now we can tell if the user's cursor is inside a callable function.
220        // If so, get its name (the function name being called.)
221        let maybe_callee = match hover {
222            Hover::Function { name, range: _ } => Some(name),
223            Hover::Signature {
224                name,
225                parameter_index: _,
226                range: _,
227            } => Some(name),
228            Hover::Comment { .. } => None,
229            Hover::Variable { .. } => None,
230            Hover::KwArg {
231                callee_name,
232                name: _,
233                range: _,
234            } => Some(callee_name),
235            Hover::Type { .. } => None,
236        };
237        let callee_args = maybe_callee.and_then(|fn_name| self.stdlib_args.get(&fn_name))?;
238
239        let arg_label_completions = callee_args
240            .iter()
241            // Don't suggest labels for unlabelled args!
242            .filter(|(_arg_name, arg_data)| arg_data.props.is_labelled())
243            .map(|(arg_name, arg_data)| CompletionItem {
244                label: arg_name.to_owned(),
245                label_details: None,
246                kind: Some(CompletionItemKind::PROPERTY),
247                detail: arg_data.props.ty.clone(),
248                documentation: arg_data.props.docs.clone().map(|docs| {
249                    Documentation::MarkupContent(MarkupContent {
250                        kind: MarkupKind::Markdown,
251                        value: docs,
252                    })
253                }),
254                deprecated: None,
255                preselect: None,
256                sort_text: Some(arg_name.to_owned()),
257                filter_text: Some(arg_name.to_owned()),
258                insert_text: {
259                    // let snippet = "${0:x}";
260                    if let Some(snippet) = arg_data.props.get_autocomplete_snippet(0).map(|(_i, snippet)| snippet) {
261                        Some(snippet)
262                    } else {
263                        Some(format!("{arg_name} = "))
264                    }
265                },
266                insert_text_format: Some(InsertTextFormat::SNIPPET),
267                insert_text_mode: None,
268                text_edit: None,
269                additional_text_edits: None,
270                command: None,
271                commit_characters: None,
272                data: None,
273                tags: None,
274            });
275        Some(arg_label_completions)
276    }
277}
278
279// Implement the shared backend trait for the language server.
280#[async_trait::async_trait]
281impl crate::lsp::backend::Backend for Backend {
282    fn client(&self) -> &Client {
283        &self.client
284    }
285
286    fn fs(&self) -> &Arc<crate::fs::FileManager> {
287        &self.fs
288    }
289
290    async fn is_initialized(&self) -> bool {
291        *self.is_initialized.read().await
292    }
293
294    async fn set_is_initialized(&self, is_initialized: bool) {
295        *self.is_initialized.write().await = is_initialized;
296    }
297
298    async fn workspace_folders(&self) -> Vec<WorkspaceFolder> {
299        // TODO: fix clone
300        self.workspace_folders.iter().map(|i| i.clone()).collect()
301    }
302
303    async fn add_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
304        for folder in folders {
305            self.workspace_folders.insert(folder.name.to_string(), folder);
306        }
307    }
308
309    async fn remove_workspace_folders(&self, folders: Vec<WorkspaceFolder>) {
310        for folder in folders {
311            self.workspace_folders.remove(&folder.name);
312        }
313    }
314
315    fn code_map(&self) -> &DashMap<String, Vec<u8>> {
316        &self.code_map
317    }
318
319    async fn insert_code_map(&self, uri: String, text: Vec<u8>) {
320        self.code_map.insert(uri, text);
321    }
322
323    async fn remove_from_code_map(&self, uri: String) -> Option<Vec<u8>> {
324        self.code_map.remove(&uri).map(|x| x.1)
325    }
326
327    async fn clear_code_state(&self) {
328        self.code_map.clear();
329        self.token_map.clear();
330        self.ast_map.clear();
331        self.diagnostics_map.clear();
332        self.symbols_map.clear();
333        self.semantic_tokens_map.clear();
334    }
335
336    fn current_diagnostics_map(&self) -> &DashMap<String, Vec<Diagnostic>> {
337        &self.diagnostics_map
338    }
339
340    async fn inner_on_change(&self, params: TextDocumentItem, force: bool) {
341        if force {
342            crate::bust_cache().await;
343        }
344
345        let filename = params.uri.to_string();
346        // We already updated the code map in the shared backend.
347
348        // Lets update the tokens.
349        let module_id = ModuleId::default();
350        let tokens = match crate::parsing::token::lex(&params.text, module_id) {
351            Ok(tokens) => tokens,
352            Err(err) => {
353                self.add_to_diagnostics(&params, &[err], true).await;
354                self.token_map.remove(&filename);
355                self.remove_from_ast_maps(&filename);
356                self.semantic_tokens_map.remove(&filename);
357                return;
358            }
359        };
360
361        // Get the previous tokens.
362        let tokens_changed = match self.token_map.get(&filename) {
363            Some(previous_tokens) => *previous_tokens != tokens,
364            _ => true,
365        };
366
367        let had_diagnostics = self.has_diagnostics(params.uri.as_ref()).await;
368
369        // Check if the tokens are the same.
370        if !tokens_changed && !force && !had_diagnostics {
371            // We return early here because the tokens are the same.
372            return;
373        }
374
375        if tokens_changed {
376            // Update our token map.
377            self.token_map.insert(params.uri.to_string(), tokens.clone());
378            // Update our semantic tokens.
379            self.update_semantic_tokens(&tokens, &params).await;
380        }
381
382        // Lets update the ast.
383
384        let (ast, errs) = match crate::parsing::parse_tokens(tokens.clone()).0 {
385            Ok(result) => result,
386            Err(err) => {
387                self.add_to_diagnostics(&params, &[err], true).await;
388                self.remove_from_ast_maps(&filename);
389                return;
390            }
391        };
392
393        self.add_to_diagnostics(&params, &errs, true).await;
394
395        if errs.iter().any(|e| e.severity == crate::errors::Severity::Fatal) {
396            self.remove_from_ast_maps(&filename);
397            return;
398        }
399
400        let Some(mut ast) = ast else {
401            self.remove_from_ast_maps(&filename);
402            return;
403        };
404
405        // Here we will want to store the digest and compare, but for now
406        // we're doing this in a non-load-bearing capacity so we can remove
407        // this if it backfires and only hork the LSP.
408        ast.compute_digest();
409
410        // Save it as a program.
411        let ast = crate::Program {
412            ast,
413            original_file_contents: params.text.clone(),
414        };
415
416        // Check if the ast changed.
417        let ast_changed = match self.ast_map.get(&filename) {
418            Some(old_ast) => {
419                // Check if the ast changed.
420                *old_ast.ast != *ast.ast
421            }
422            None => true,
423        };
424
425        if !ast_changed && !force && !had_diagnostics {
426            // Return early if the ast did not change and we don't need to force.
427            return;
428        }
429
430        if ast_changed {
431            self.ast_map.insert(params.uri.to_string(), ast.clone());
432            // Update the symbols map.
433            self.symbols_map.insert(
434                params.uri.to_string(),
435                ast.ast.get_lsp_symbols(&params.text).unwrap_or_default(),
436            );
437
438            // Update our semantic tokens.
439            self.update_semantic_tokens(&tokens, &params).await;
440
441            let mut discovered_findings: Vec<_> = ast.lint_all().into_iter().flatten().collect();
442            // Filter out Z0005 (old sketch syntax) from LSP diagnostics
443            // TODO: Remove this filter once the transpiler is complete and all tests are updated
444            discovered_findings.retain(|finding| finding.finding.code != "Z0005");
445            self.add_to_diagnostics(&params, &discovered_findings, false).await;
446        }
447
448        // Send the notification to the client that the ast was updated.
449        if self.can_execute().await || self.executor_ctx().await.is_none() {
450            // Only send the notification if we can execute.
451            // Otherwise it confuses the client.
452            self.client
453                .send_notification::<custom_notifications::AstUpdated>(ast.ast.clone())
454                .await;
455        }
456
457        // Execute the code if we have an executor context.
458        // This function automatically executes if we should & updates the diagnostics if we got
459        // errors.
460        if self.execute(&params, &ast).await.is_err() {
461            return;
462        }
463
464        // If we made it here we can clear the diagnostics.
465        self.clear_diagnostics_map(&params.uri, Some(DiagnosticSeverity::ERROR))
466            .await;
467    }
468}
469
470impl Backend {
471    pub async fn can_execute(&self) -> bool {
472        *self.can_execute.read().await
473    }
474
475    pub async fn executor_ctx(&self) -> tokio::sync::RwLockReadGuard<'_, Option<crate::execution::ExecutorContext>> {
476        self.executor_ctx.read().await
477    }
478
479    async fn update_semantic_tokens(&self, tokens: &TokenStream, params: &TextDocumentItem) {
480        // Update the semantic tokens map.
481        let mut semantic_tokens = vec![];
482        let mut last_position = Position::new(0, 0);
483        for token in tokens.as_slice() {
484            let Ok(token_type) = SemanticTokenType::try_from(token.token_type) else {
485                // We continue here because not all tokens can be converted this way, we will get
486                // the rest from the ast.
487                continue;
488            };
489
490            let mut token_type_index = match self.get_semantic_token_type_index(&token_type) {
491                Some(index) => index,
492                // This is actually bad this should not fail.
493                // The test for listing all semantic token types should make this never happen.
494                None => {
495                    self.client
496                        .log_message(
497                            MessageType::ERROR,
498                            format!("token type `{token_type:?}` not accounted for"),
499                        )
500                        .await;
501                    continue;
502                }
503            };
504
505            let source_range: SourceRange = token.into();
506            let position = source_range.start_to_lsp_position(&params.text);
507
508            // Calculate the token modifiers.
509            // Get the value at the current position.
510            let token_modifiers_bitset = match self.ast_map.get(params.uri.as_str()) {
511                Some(ast) => {
512                    let token_index = Arc::new(Mutex::new(token_type_index));
513                    let modifier_index: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
514                    crate::walk::walk(&ast.ast, |node: crate::walk::Node| {
515                        let Ok(node_range): Result<SourceRange, _> = (&node).try_into() else {
516                            return Ok(true);
517                        };
518
519                        if !node_range.contains(source_range.start()) {
520                            return Ok(true);
521                        }
522
523                        let get_modifier = |modifier: Vec<SemanticTokenModifier>| -> Result<bool> {
524                            let mut mods = modifier_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
525                            let Some(token_modifier_index) = self.get_semantic_token_modifier_index(modifier) else {
526                                return Ok(true);
527                            };
528                            if *mods == 0 {
529                                *mods = token_modifier_index;
530                            } else {
531                                *mods |= token_modifier_index;
532                            }
533                            Ok(false)
534                        };
535
536                        match node {
537                            crate::walk::Node::TagDeclarator(_) => {
538                                return get_modifier(vec![
539                                    SemanticTokenModifier::DEFINITION,
540                                    SemanticTokenModifier::STATIC,
541                                ]);
542                            }
543                            crate::walk::Node::VariableDeclarator(variable) => {
544                                let sr: SourceRange = (&variable.id).into();
545                                if sr.contains(source_range.start()) {
546                                    if let Expr::FunctionExpression(_) = &variable.init {
547                                        let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
548                                        *ti = match self.get_semantic_token_type_index(&SemanticTokenType::FUNCTION) {
549                                            Some(index) => index,
550                                            None => token_type_index,
551                                        };
552                                    }
553
554                                    return get_modifier(vec![
555                                        SemanticTokenModifier::DECLARATION,
556                                        SemanticTokenModifier::READONLY,
557                                    ]);
558                                }
559                            }
560                            crate::walk::Node::Parameter(_) => {
561                                let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
562                                *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PARAMETER) {
563                                    Some(index) => index,
564                                    None => token_type_index,
565                                };
566                                return Ok(false);
567                            }
568                            crate::walk::Node::MemberExpression(member_expression) => {
569                                let sr: SourceRange = (&member_expression.property).into();
570                                if sr.contains(source_range.start()) {
571                                    let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
572                                    *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PROPERTY) {
573                                        Some(index) => index,
574                                        None => token_type_index,
575                                    };
576                                    return Ok(false);
577                                }
578                            }
579                            crate::walk::Node::ObjectProperty(object_property) => {
580                                let sr: SourceRange = (&object_property.key).into();
581                                if sr.contains(source_range.start()) {
582                                    let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
583                                    *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PROPERTY) {
584                                        Some(index) => index,
585                                        None => token_type_index,
586                                    };
587                                }
588                                return get_modifier(vec![SemanticTokenModifier::DECLARATION]);
589                            }
590                            crate::walk::Node::CallExpressionKw(call_expr) => {
591                                let sr: SourceRange = (&call_expr.callee).into();
592                                if sr.contains(source_range.start()) {
593                                    let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
594                                    *ti = match self.get_semantic_token_type_index(&SemanticTokenType::FUNCTION) {
595                                        Some(index) => index,
596                                        None => token_type_index,
597                                    };
598
599                                    if self.stdlib_completions.contains_key(&call_expr.callee.name.name) {
600                                        // This is a stdlib function.
601                                        return get_modifier(vec![SemanticTokenModifier::DEFAULT_LIBRARY]);
602                                    }
603
604                                    return Ok(false);
605                                }
606                            }
607                            _ => {}
608                        }
609                        Ok(true)
610                    })
611                    .unwrap_or_default();
612
613                    let t = match token_index.lock() {
614                        Ok(guard) => *guard,
615                        _ => 0,
616                    };
617                    token_type_index = t;
618
619                    match modifier_index.lock() {
620                        Ok(guard) => *guard,
621                        _ => 0,
622                    }
623                }
624                _ => 0,
625            };
626
627            // We need to check if we are on the last token of the line.
628            // If we are starting from the end of the last line just add 1 to the line.
629            // Check if we are on the last token of the line.
630            if let Some(line) = params.text.lines().nth(position.line as usize)
631                && line.len() == position.character as usize
632            {
633                // We are on the last token of the line.
634                // We need to add a new line.
635                let semantic_token = SemanticToken {
636                    delta_line: position.line - last_position.line + 1,
637                    delta_start: 0,
638                    length: (token.end - token.start) as u32,
639                    token_type: token_type_index,
640                    token_modifiers_bitset,
641                };
642
643                semantic_tokens.push(semantic_token);
644
645                last_position = Position::new(position.line + 1, 0);
646                continue;
647            }
648
649            let semantic_token = SemanticToken {
650                delta_line: position.line - last_position.line,
651                delta_start: if position.line != last_position.line {
652                    position.character
653                } else {
654                    position.character - last_position.character
655                },
656                length: (token.end - token.start) as u32,
657                token_type: token_type_index,
658                token_modifiers_bitset,
659            };
660
661            semantic_tokens.push(semantic_token);
662
663            last_position = position;
664        }
665        self.semantic_tokens_map.insert(params.uri.to_string(), semantic_tokens);
666    }
667
668    async fn clear_diagnostics_map(&self, uri: &url::Url, severity: Option<DiagnosticSeverity>) {
669        let Some(mut items) = self.diagnostics_map.get_mut(uri.as_str()) else {
670            return;
671        };
672
673        // If we only want to clear a specific severity, do that.
674        if let Some(severity) = severity {
675            items.retain(|x| x.severity != Some(severity));
676        } else {
677            items.clear();
678        }
679
680        if items.is_empty() {
681            #[cfg(not(target_arch = "wasm32"))]
682            {
683                self.client.publish_diagnostics(uri.clone(), items.clone(), None).await;
684            }
685
686            // We need to drop the items here.
687            drop(items);
688
689            self.diagnostics_map.remove(uri.as_str());
690        } else {
691            // We don't need to update the map since we used get_mut.
692
693            #[cfg(not(target_arch = "wasm32"))]
694            {
695                self.client.publish_diagnostics(uri.clone(), items.clone(), None).await;
696            }
697        }
698    }
699
700    async fn add_to_diagnostics<DiagT: IntoDiagnostic + std::fmt::Debug>(
701        &self,
702        params: &TextDocumentItem,
703        diagnostics: &[DiagT],
704        clear_all_before_add: bool,
705    ) {
706        if diagnostics.is_empty() {
707            return;
708        }
709
710        if clear_all_before_add {
711            self.clear_diagnostics_map(&params.uri, None).await;
712        } else if diagnostics.iter().all(|x| x.severity() == DiagnosticSeverity::ERROR) {
713            // If the diagnostic is an error, it will be the only error we get since that halts
714            // execution.
715            // Clear the diagnostics before we add a new one.
716            self.clear_diagnostics_map(&params.uri, Some(DiagnosticSeverity::ERROR))
717                .await;
718        } else if diagnostics
719            .iter()
720            .all(|x| x.severity() == DiagnosticSeverity::INFORMATION)
721        {
722            // If the diagnostic is a lint, we will pass them all to add at once so we need to
723            // clear the old ones.
724            self.clear_diagnostics_map(&params.uri, Some(DiagnosticSeverity::INFORMATION))
725                .await;
726        }
727
728        let mut items = match self.diagnostics_map.get(params.uri.as_str()) {
729            Some(items) => {
730                // TODO: Would be awesome to fix the clone here.
731                items.clone()
732            }
733            _ => {
734                vec![]
735            }
736        };
737
738        for diagnostic in diagnostics {
739            let lsp_d = diagnostic.to_lsp_diagnostics(&params.text);
740            // Make sure we don't duplicate diagnostics.
741            for d in lsp_d {
742                if !items.iter().any(|x| x == &d) {
743                    items.push(d);
744                }
745            }
746        }
747
748        self.diagnostics_map.insert(params.uri.to_string(), items.clone());
749
750        self.client.publish_diagnostics(params.uri.clone(), items, None).await;
751    }
752
753    async fn execute(&self, params: &TextDocumentItem, ast: &Program) -> Result<()> {
754        // Check if we can execute.
755        if !self.can_execute().await {
756            return Ok(());
757        }
758
759        // Execute the code if we have an executor context.
760        let ctx = self.executor_ctx().await;
761        let Some(ref executor_ctx) = *ctx else {
762            return Ok(());
763        };
764
765        if !self.is_initialized().await {
766            // We are not initialized yet.
767            return Ok(());
768        }
769
770        // Use run_mock for mock contexts, run_with_caching for live contexts
771        let result = if executor_ctx.is_mock() {
772            executor_ctx
773                .run_mock(ast, &crate::execution::MockConfig::default())
774                .await
775        } else {
776            executor_ctx.run_with_caching(ast.clone()).await
777        };
778
779        match result {
780            Err(err) => {
781                self.add_to_diagnostics(params, &[err], false).await;
782
783                // Since we already published the diagnostics we don't really care about the error
784                // string.
785                Err(anyhow::anyhow!("failed to execute code"))
786            }
787            Ok(_) => Ok(()),
788        }
789    }
790
791    pub fn get_semantic_token_type_index(&self, token_type: &SemanticTokenType) -> Option<u32> {
792        SEMANTIC_TOKEN_TYPES
793            .iter()
794            .position(|x| *x == *token_type)
795            .map(|y| y as u32)
796    }
797
798    pub fn get_semantic_token_modifier_index(&self, token_types: Vec<SemanticTokenModifier>) -> Option<u32> {
799        if token_types.is_empty() {
800            return None;
801        }
802
803        let mut modifier = None;
804        for token_type in token_types {
805            if let Some(index) = SEMANTIC_TOKEN_MODIFIERS
806                .iter()
807                .position(|x| *x == token_type)
808                .map(|y| y as u32)
809            {
810                modifier = match modifier {
811                    Some(modifier) => Some(modifier | index),
812                    None => Some(index),
813                };
814            }
815        }
816        modifier
817    }
818
819    pub async fn create_zip(&self) -> Result<Vec<u8>> {
820        // Collect all the file data we know.
821        let mut buf = vec![];
822        let mut zip = zip::ZipWriter::new(std::io::Cursor::new(&mut buf));
823        for code in self.code_map.iter() {
824            let entry = code.key();
825            let value = code.value();
826            let file_name = entry.replace("file://", "").to_string();
827
828            let options = zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
829            zip.start_file(file_name, options)?;
830            zip.write_all(value)?;
831        }
832        // Apply the changes you've made.
833        // Dropping the `ZipWriter` will have the same effect, but may silently fail
834        zip.finish()?;
835
836        Ok(buf)
837    }
838
839    pub async fn send_telemetry(&self) -> Result<()> {
840        // Get information about the user.
841        let user = self
842            .zoo_client
843            .users()
844            .get_self()
845            .await
846            .map_err(|e| anyhow::anyhow!(e.to_string()))?;
847
848        // Hash the user's id.
849        // Create a SHA-256 object
850        let mut hasher = sha2::Sha256::new();
851        // Write input message
852        hasher.update(user.id);
853        // Read hash digest and consume hasher
854        let result = hasher.finalize();
855        // Get the hash as a string.
856        let user_id_hash = format!("{result:x}");
857
858        // Get the workspace folders.
859        // The key of the workspace folder is the project name.
860        let workspace_folders = self.workspace_folders().await;
861        let project_names: Vec<&str> = workspace_folders.iter().map(|v| v.name.as_str()).collect::<Vec<_>>();
862        // Get the first name.
863        let project_name = project_names
864            .first()
865            .ok_or_else(|| anyhow::anyhow!("no project names"))?
866            .to_string();
867
868        // Send the telemetry data.
869        self.zoo_client
870            .meta()
871            .create_event(
872                vec![kittycad::types::multipart::Attachment {
873                    // Clean the URI part.
874                    name: "attachment".to_string(),
875                    filepath: Some("attachment.zip".into()),
876                    content_type: Some("application/x-zip".to_string()),
877                    data: self.create_zip().await?,
878                }],
879                &kittycad::types::Event {
880                    // This gets generated server side so leave empty for now.
881                    attachment_uri: None,
882                    created_at: chrono::Utc::now(),
883                    event_type: kittycad::types::ModelingAppEventType::SuccessfulCompileBeforeClose,
884                    last_compiled_at: Some(chrono::Utc::now()),
885                    // We do not have project descriptions yet.
886                    project_description: None,
887                    project_name,
888                    // The UUID for the Design Studio.
889                    // We can unwrap here because we know it will not panic.
890                    source_id: uuid::Uuid::from_str("70178592-dfca-47b3-bd2d-6fce2bcaee04").unwrap(),
891                    type_: kittycad::types::Type::ModelingAppEvent,
892                    user_id: user_id_hash,
893                },
894            )
895            .await
896            .map_err(|e| anyhow::anyhow!(e.to_string()))?;
897
898        Ok(())
899    }
900
901    pub async fn update_can_execute(
902        &self,
903        params: custom_notifications::UpdateCanExecuteParams,
904    ) -> RpcResult<custom_notifications::UpdateCanExecuteResponse> {
905        let mut can_execute = self.can_execute.write().await;
906
907        if *can_execute == params.can_execute {
908            return Ok(custom_notifications::UpdateCanExecuteResponse {});
909        }
910
911        *can_execute = params.can_execute;
912
913        Ok(custom_notifications::UpdateCanExecuteResponse {})
914    }
915
916    /// Returns the new string for the code after rename.
917    pub fn inner_prepare_rename(
918        &self,
919        params: &TextDocumentPositionParams,
920        new_name: &str,
921    ) -> RpcResult<Option<(String, String)>> {
922        let filename = params.text_document.uri.to_string();
923
924        let Some(current_code) = self.code_map.get(&filename) else {
925            return Ok(None);
926        };
927        let Ok(current_code) = std::str::from_utf8(&current_code) else {
928            return Ok(None);
929        };
930
931        // Parse the ast.
932        // I don't know if we need to do this again since it should be updated in the context.
933        // But I figure better safe than sorry since this will write back out to the file.
934        let module_id = ModuleId::default();
935        let Ok(mut ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
936            return Ok(None);
937        };
938
939        // Let's convert the position to a character index.
940        let pos = position_to_char_index(params.position, current_code);
941        // Now let's perform the rename on the ast.
942        ast.rename_symbol(new_name, pos);
943        // Now recast it.
944        let recast = ast.recast_top(&Default::default(), 0);
945
946        Ok(Some((current_code.to_string(), recast)))
947    }
948}
949
950#[tower_lsp::async_trait]
951impl LanguageServer for Backend {
952    async fn initialize(&self, params: InitializeParams) -> RpcResult<InitializeResult> {
953        self.client
954            .log_message(MessageType::INFO, format!("initialize: {params:?}"))
955            .await;
956
957        Ok(InitializeResult {
958            capabilities: ServerCapabilities {
959                color_provider: Some(ColorProviderCapability::Simple(true)),
960                code_action_provider: Some(CodeActionProviderCapability::Options(CodeActionOptions {
961                    code_action_kinds: Some(vec![CodeActionKind::QUICKFIX]),
962                    resolve_provider: Some(false),
963                    work_done_progress_options: WorkDoneProgressOptions::default(),
964                })),
965                completion_provider: Some(CompletionOptions {
966                    resolve_provider: Some(false),
967                    trigger_characters: Some(vec![".".to_string()]),
968                    work_done_progress_options: Default::default(),
969                    all_commit_characters: None,
970                    ..Default::default()
971                }),
972                diagnostic_provider: Some(DiagnosticServerCapabilities::Options(DiagnosticOptions {
973                    ..Default::default()
974                })),
975                document_formatting_provider: Some(OneOf::Left(true)),
976                folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
977                hover_provider: Some(HoverProviderCapability::Simple(true)),
978                inlay_hint_provider: Some(OneOf::Left(true)),
979                rename_provider: Some(OneOf::Left(true)),
980                semantic_tokens_provider: Some(SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(
981                    SemanticTokensRegistrationOptions {
982                        text_document_registration_options: {
983                            TextDocumentRegistrationOptions {
984                                document_selector: Some(vec![DocumentFilter {
985                                    language: Some("kcl".to_string()),
986                                    scheme: Some("file".to_string()),
987                                    pattern: None,
988                                }]),
989                            }
990                        },
991                        semantic_tokens_options: SemanticTokensOptions {
992                            work_done_progress_options: WorkDoneProgressOptions::default(),
993                            legend: SemanticTokensLegend {
994                                token_types: SEMANTIC_TOKEN_TYPES.to_vec(),
995                                token_modifiers: SEMANTIC_TOKEN_MODIFIERS.to_vec(),
996                            },
997                            range: Some(false),
998                            full: Some(SemanticTokensFullOptions::Bool(true)),
999                        },
1000                        static_registration_options: StaticRegistrationOptions::default(),
1001                    },
1002                )),
1003                signature_help_provider: Some(SignatureHelpOptions {
1004                    trigger_characters: None,
1005                    retrigger_characters: None,
1006                    ..Default::default()
1007                }),
1008                text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
1009                    open_close: Some(true),
1010                    change: Some(TextDocumentSyncKind::FULL),
1011                    ..Default::default()
1012                })),
1013                workspace: Some(WorkspaceServerCapabilities {
1014                    workspace_folders: Some(WorkspaceFoldersServerCapabilities {
1015                        supported: Some(true),
1016                        change_notifications: Some(OneOf::Left(true)),
1017                    }),
1018                    file_operations: None,
1019                }),
1020                ..Default::default()
1021            },
1022            ..Default::default()
1023        })
1024    }
1025
1026    async fn initialized(&self, params: InitializedParams) {
1027        self.do_initialized(params).await
1028    }
1029
1030    async fn shutdown(&self) -> RpcResult<()> {
1031        self.do_shutdown().await
1032    }
1033
1034    async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
1035        self.do_did_change_workspace_folders(params).await
1036    }
1037
1038    async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
1039        self.do_did_change_configuration(params).await
1040    }
1041
1042    async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
1043        self.do_did_change_watched_files(params).await
1044    }
1045
1046    async fn did_create_files(&self, params: CreateFilesParams) {
1047        self.do_did_create_files(params).await
1048    }
1049
1050    async fn did_rename_files(&self, params: RenameFilesParams) {
1051        self.do_did_rename_files(params).await
1052    }
1053
1054    async fn did_delete_files(&self, params: DeleteFilesParams) {
1055        self.do_did_delete_files(params).await
1056    }
1057
1058    async fn did_open(&self, params: DidOpenTextDocumentParams) {
1059        self.do_did_open(params).await
1060    }
1061
1062    async fn did_change(&self, params: DidChangeTextDocumentParams) {
1063        self.do_did_change(params).await;
1064    }
1065
1066    async fn did_save(&self, params: DidSaveTextDocumentParams) {
1067        self.do_did_save(params).await
1068    }
1069
1070    async fn did_close(&self, params: DidCloseTextDocumentParams) {
1071        self.do_did_close(params).await;
1072
1073        // Inject telemetry if we can train on the user's code.
1074        // Return early if we cannot.
1075        if !self.can_send_telemetry {
1076            return;
1077        }
1078
1079        // In wasm this needs to be spawn_local since fucking reqwests doesn't implement Send for wasm.
1080        #[cfg(target_arch = "wasm32")]
1081        {
1082            let be = self.clone();
1083            wasm_bindgen_futures::spawn_local(async move {
1084                if let Err(err) = be.send_telemetry().await {
1085                    be.client
1086                        .log_message(MessageType::WARNING, format!("failed to send telemetry: {}", err))
1087                        .await;
1088                }
1089            });
1090        }
1091        #[cfg(not(target_arch = "wasm32"))]
1092        if let Err(err) = self.send_telemetry().await {
1093            self.client
1094                .log_message(MessageType::WARNING, format!("failed to send telemetry: {err}"))
1095                .await;
1096        }
1097    }
1098
1099    async fn hover(&self, params: HoverParams) -> RpcResult<Option<LspHover>> {
1100        let filename = params.text_document_position_params.text_document.uri.to_string();
1101
1102        let Some(current_code) = self.code_map.get(&filename) else {
1103            return Ok(None);
1104        };
1105        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1106            return Ok(None);
1107        };
1108
1109        let pos = position_to_char_index(params.text_document_position_params.position, current_code);
1110
1111        // Let's iterate over the AST and find the node that contains the cursor.
1112        let Some(ast) = self.ast_map.get(&filename) else {
1113            return Ok(None);
1114        };
1115
1116        let Some(hover) = ast
1117            .ast
1118            .get_hover_value_for_position(pos, current_code, &HoverOpts::default_for_hover())
1119        else {
1120            return Ok(None);
1121        };
1122
1123        match hover {
1124            Hover::Function { name, range } => {
1125                let (sig, docs) = if let Some(Some(result)) = with_cached_var(&name, |value| {
1126                    match value {
1127                        // User-defined function
1128                        KclValue::Function { value, .. } if !value.is_std => {
1129                            // TODO get docs from comments
1130                            Some((value.ast.signature(), ""))
1131                        }
1132                        _ => None,
1133                    }
1134                })
1135                .await
1136                {
1137                    result
1138                } else {
1139                    // Get the docs for this function.
1140                    let Some(completion) = self.stdlib_completions.get(&name) else {
1141                        return Ok(None);
1142                    };
1143                    let Some(docs) = &completion.documentation else {
1144                        return Ok(None);
1145                    };
1146
1147                    let docs = match docs {
1148                        Documentation::String(docs) => docs,
1149                        Documentation::MarkupContent(MarkupContent { value, .. }) => value,
1150                    };
1151
1152                    let docs = if docs.len() > 320 {
1153                        let end = docs.find("\n\n").or_else(|| docs.find("\n\r\n")).unwrap_or(320);
1154                        &docs[..end]
1155                    } else {
1156                        &**docs
1157                    };
1158
1159                    let Some(label_details) = &completion.label_details else {
1160                        return Ok(None);
1161                    };
1162
1163                    let sig = if let Some(detail) = &label_details.detail {
1164                        detail.clone()
1165                    } else {
1166                        String::new()
1167                    };
1168
1169                    (sig, docs)
1170                };
1171
1172                Ok(Some(LspHover {
1173                    contents: HoverContents::Markup(MarkupContent {
1174                        kind: MarkupKind::Markdown,
1175                        value: format!("```\n{name}{sig}\n```\n\n{docs}"),
1176                    }),
1177                    range: Some(range),
1178                }))
1179            }
1180            Hover::Type { name, range } => {
1181                let Some(completion) = self.stdlib_completions.get(&name) else {
1182                    return Ok(None);
1183                };
1184                let Some(docs) = &completion.documentation else {
1185                    return Ok(None);
1186                };
1187
1188                let docs = match docs {
1189                    Documentation::String(docs) => docs,
1190                    Documentation::MarkupContent(MarkupContent { value, .. }) => value,
1191                };
1192
1193                let docs = if docs.len() > 320 {
1194                    let end = docs.find("\n\n").or_else(|| docs.find("\n\r\n")).unwrap_or(320);
1195                    &docs[..end]
1196                } else {
1197                    &**docs
1198                };
1199
1200                Ok(Some(LspHover {
1201                    contents: HoverContents::Markup(MarkupContent {
1202                        kind: MarkupKind::Markdown,
1203                        value: format!("```\n{name}\n```\n\n{docs}"),
1204                    }),
1205                    range: Some(range),
1206                }))
1207            }
1208            Hover::KwArg {
1209                name,
1210                callee_name,
1211                range,
1212            } => {
1213                // TODO handle user-defined functions too
1214
1215                let Some(arg_map) = self.stdlib_args.get(&callee_name) else {
1216                    return Ok(None);
1217                };
1218
1219                let Some(arg_entry) = arg_map.get(&name) else {
1220                    return Ok(None);
1221                };
1222
1223                Ok(Some(LspHover {
1224                    contents: HoverContents::Markup(MarkupContent {
1225                        kind: MarkupKind::Markdown,
1226                        value: arg_entry.tip.clone(),
1227                    }),
1228                    range: Some(range),
1229                }))
1230            }
1231            Hover::Variable {
1232                name,
1233                ty: Some(ty),
1234                range,
1235            } => Ok(Some(LspHover {
1236                contents: HoverContents::Markup(MarkupContent {
1237                    kind: MarkupKind::Markdown,
1238                    value: format!("```\n{name}: {ty}\n```"),
1239                }),
1240                range: Some(range),
1241            })),
1242            Hover::Variable { name, ty: None, range } => Ok(with_cached_var(&name, |value| {
1243                let mut text: String = format!("```\n{name}");
1244                if let Some(ty) = value.principal_type() {
1245                    text.push_str(&format!(": {}", ty.human_friendly_type()));
1246                }
1247                if let Some(v) = value.value_str() {
1248                    text.push_str(&format!(" = {v}"));
1249                }
1250                text.push_str("\n```");
1251
1252                LspHover {
1253                    contents: HoverContents::Markup(MarkupContent {
1254                        kind: MarkupKind::Markdown,
1255                        value: text,
1256                    }),
1257                    range: Some(range),
1258                }
1259            })
1260            .await),
1261            Hover::Signature { .. } => Ok(None),
1262            Hover::Comment { value, range } => Ok(Some(LspHover {
1263                contents: HoverContents::Markup(MarkupContent {
1264                    kind: MarkupKind::Markdown,
1265                    value,
1266                }),
1267                range: Some(range),
1268            })),
1269        }
1270    }
1271
1272    async fn completion(&self, params: CompletionParams) -> RpcResult<Option<CompletionResponse>> {
1273        let mut completions = vec![CompletionItem {
1274            label: PIPE_OPERATOR.to_string(),
1275            label_details: None,
1276            kind: Some(CompletionItemKind::OPERATOR),
1277            detail: Some("A pipe operator.".to_string()),
1278            documentation: Some(Documentation::MarkupContent(MarkupContent {
1279                kind: MarkupKind::Markdown,
1280                value: "A pipe operator.".to_string(),
1281            })),
1282            deprecated: Some(false),
1283            preselect: None,
1284            sort_text: None,
1285            filter_text: None,
1286            insert_text: Some("|> ".to_string()),
1287            insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
1288            insert_text_mode: None,
1289            text_edit: None,
1290            additional_text_edits: None,
1291            command: None,
1292            commit_characters: None,
1293            data: None,
1294            tags: None,
1295        }];
1296
1297        // Get the current line up to cursor
1298        let Some(current_code) = self
1299            .code_map
1300            .get(params.text_document_position.text_document.uri.as_ref())
1301        else {
1302            return Ok(Some(CompletionResponse::Array(completions)));
1303        };
1304        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1305            return Ok(Some(CompletionResponse::Array(completions)));
1306        };
1307
1308        // Get the current line up to cursor, with bounds checking
1309        if let Some(line) = current_code
1310            .lines()
1311            .nth(params.text_document_position.position.line as usize)
1312        {
1313            let char_pos = params.text_document_position.position.character as usize;
1314            if char_pos <= line.len() {
1315                let line_prefix = &line[..char_pos];
1316                // Get last word
1317                let last_word = line_prefix
1318                    .split(|c: char| c.is_whitespace() || c.is_ascii_punctuation())
1319                    .next_back()
1320                    .unwrap_or("");
1321
1322                // If the last word starts with a digit, return no completions
1323                if !last_word.is_empty() && last_word.chars().next().unwrap().is_ascii_digit() {
1324                    return Ok(None);
1325                }
1326            }
1327        }
1328
1329        completions.extend(self.stdlib_completions.values().cloned());
1330
1331        // Add more to the completions if we have more.
1332        let Some(ast) = self
1333            .ast_map
1334            .get(params.text_document_position.text_document.uri.as_ref())
1335        else {
1336            return Ok(Some(CompletionResponse::Array(completions)));
1337        };
1338
1339        let position = position_to_char_index(params.text_document_position.position, current_code);
1340        if ast.ast.in_comment(position) {
1341            // If we are in a code comment we don't want to show completions.
1342            return Ok(None);
1343        }
1344
1345        // If we're inside a CallExpression or something where a function parameter label could be completed,
1346        // then complete it.
1347        // Let's find the AST node that the user's cursor is in.
1348        if let Some(arg_label_completions) = self.try_arg_completions(&ast.ast, position, current_code) {
1349            completions.extend(arg_label_completions);
1350        }
1351
1352        // Get the completion items for the ast.
1353        let Ok(variables) = ast.ast.completion_items(position) else {
1354            return Ok(Some(CompletionResponse::Array(completions)));
1355        };
1356
1357        // Get our variables from our AST to include in our completions.
1358        completions.extend(variables);
1359
1360        Ok(Some(CompletionResponse::Array(completions)))
1361    }
1362
1363    async fn diagnostic(&self, params: DocumentDiagnosticParams) -> RpcResult<DocumentDiagnosticReportResult> {
1364        let filename = params.text_document.uri.to_string();
1365
1366        // Get the current diagnostics for this file.
1367        let Some(items) = self.diagnostics_map.get(&filename) else {
1368            // Send an empty report.
1369            return Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1370                RelatedFullDocumentDiagnosticReport {
1371                    related_documents: None,
1372                    full_document_diagnostic_report: FullDocumentDiagnosticReport {
1373                        result_id: None,
1374                        items: vec![],
1375                    },
1376                },
1377            )));
1378        };
1379
1380        Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1381            RelatedFullDocumentDiagnosticReport {
1382                related_documents: None,
1383                full_document_diagnostic_report: FullDocumentDiagnosticReport {
1384                    result_id: None,
1385                    items: items.clone(),
1386                },
1387            },
1388        )))
1389    }
1390
1391    async fn signature_help(&self, params: SignatureHelpParams) -> RpcResult<Option<SignatureHelp>> {
1392        let filename = params.text_document_position_params.text_document.uri.to_string();
1393
1394        let Some(current_code) = self.code_map.get(&filename) else {
1395            return Ok(None);
1396        };
1397        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1398            return Ok(None);
1399        };
1400
1401        let pos = position_to_char_index(params.text_document_position_params.position, current_code);
1402
1403        // Get the character at the position.
1404        let Some(ch) = current_code.chars().nth(pos) else {
1405            return Ok(None);
1406        };
1407
1408        let check_char = |ch: char| {
1409            // If  we are on a (, then get the string in front of the (
1410            // and try to get the signature.
1411            // We do these before the ast check because we might not have a valid ast.
1412            if ch == '(' {
1413                // If the current character is not a " " then get the next space after
1414                // our position so we can split on that.
1415                // Find the next space after the current position.
1416                let next_space = if ch != ' ' {
1417                    if let Some(next_space) = current_code[pos..].find(' ') {
1418                        pos + next_space
1419                    } else if let Some(next_space) = current_code[pos..].find('(') {
1420                        pos + next_space
1421                    } else {
1422                        pos
1423                    }
1424                } else {
1425                    pos
1426                };
1427                let p2 = std::cmp::max(pos, next_space);
1428
1429                let last_word = current_code[..p2].split_whitespace().last()?;
1430
1431                // Get the function name.
1432                return self.stdlib_signatures.get(last_word);
1433            } else if ch == ',' {
1434                // If we have a comma, then get the string in front of
1435                // the closest ( and try to get the signature.
1436
1437                // Find the last ( before the comma.
1438                let last_paren = current_code[..pos].rfind('(')?;
1439                // Get the string in front of the (.
1440                let last_word = current_code[..last_paren].split_whitespace().last()?;
1441                // Get the function name.
1442                return self.stdlib_signatures.get(last_word);
1443            }
1444
1445            None
1446        };
1447
1448        if let Some(signature) = check_char(ch) {
1449            return Ok(Some(signature.clone()));
1450        }
1451
1452        // Check if we have context.
1453        if let Some(context) = params.context
1454            && let Some(character) = context.trigger_character
1455        {
1456            for character in character.chars() {
1457                // Check if we are on a ( or a ,.
1458                if (character == '(' || character == ',')
1459                    && let Some(signature) = check_char(character)
1460                {
1461                    return Ok(Some(signature.clone()));
1462                }
1463            }
1464        }
1465
1466        // Let's iterate over the AST and find the node that contains the cursor.
1467        let Some(ast) = self.ast_map.get(&filename) else {
1468            return Ok(None);
1469        };
1470
1471        let Some(value) = ast.ast.get_expr_for_position(pos) else {
1472            return Ok(None);
1473        };
1474
1475        let Some(hover) =
1476            value.get_hover_value_for_position(pos, current_code, &HoverOpts::default_for_signature_help())
1477        else {
1478            return Ok(None);
1479        };
1480
1481        match hover {
1482            Hover::Function { name, range: _ } => {
1483                // Get the docs for this function.
1484                let Some(signature) = self.stdlib_signatures.get(&name) else {
1485                    return Ok(None);
1486                };
1487
1488                Ok(Some(signature.clone()))
1489            }
1490            Hover::Signature {
1491                name,
1492                parameter_index,
1493                range: _,
1494            } => {
1495                let Some(signature) = self.stdlib_signatures.get(&name) else {
1496                    return Ok(None);
1497                };
1498
1499                let mut signature = signature.clone();
1500
1501                signature.active_parameter = Some(parameter_index);
1502
1503                Ok(Some(signature))
1504            }
1505            _ => {
1506                return Ok(None);
1507            }
1508        }
1509    }
1510
1511    async fn inlay_hint(&self, _params: InlayHintParams) -> RpcResult<Option<Vec<InlayHint>>> {
1512        // TODO: do this
1513
1514        Ok(None)
1515    }
1516
1517    async fn semantic_tokens_full(&self, params: SemanticTokensParams) -> RpcResult<Option<SemanticTokensResult>> {
1518        let filename = params.text_document.uri.to_string();
1519
1520        let Some(semantic_tokens) = self.semantic_tokens_map.get(&filename) else {
1521            return Ok(None);
1522        };
1523
1524        Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
1525            result_id: None,
1526            data: semantic_tokens.clone(),
1527        })))
1528    }
1529
1530    async fn document_symbol(&self, params: DocumentSymbolParams) -> RpcResult<Option<DocumentSymbolResponse>> {
1531        let filename = params.text_document.uri.to_string();
1532
1533        let Some(symbols) = self.symbols_map.get(&filename) else {
1534            return Ok(None);
1535        };
1536
1537        Ok(Some(DocumentSymbolResponse::Nested(symbols.clone())))
1538    }
1539
1540    async fn formatting(&self, params: DocumentFormattingParams) -> RpcResult<Option<Vec<TextEdit>>> {
1541        let filename = params.text_document.uri.to_string();
1542
1543        let Some(current_code) = self.code_map.get(&filename) else {
1544            return Ok(None);
1545        };
1546        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1547            return Ok(None);
1548        };
1549
1550        // Parse the ast.
1551        // I don't know if we need to do this again since it should be updated in the context.
1552        // But I figure better safe than sorry since this will write back out to the file.
1553        let module_id = ModuleId::default();
1554        let Ok(ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
1555            return Ok(None);
1556        };
1557        // Now recast it.
1558        let recast = ast.recast_top(
1559            &crate::parsing::ast::types::FormatOptions {
1560                tab_size: params.options.tab_size as usize,
1561                insert_final_newline: params.options.insert_final_newline.unwrap_or(false),
1562                use_tabs: !params.options.insert_spaces,
1563            },
1564            0,
1565        );
1566        let source_range = SourceRange::new(0, current_code.len(), module_id);
1567        let range = source_range.to_lsp_range(current_code);
1568        Ok(Some(vec![TextEdit {
1569            new_text: recast,
1570            range,
1571        }]))
1572    }
1573
1574    async fn rename(&self, params: RenameParams) -> RpcResult<Option<WorkspaceEdit>> {
1575        let Some((current_code, new_code)) =
1576            self.inner_prepare_rename(&params.text_document_position, &params.new_name)?
1577        else {
1578            return Ok(None);
1579        };
1580
1581        let source_range = SourceRange::new(0, current_code.len(), ModuleId::default());
1582        let range = source_range.to_lsp_range(&current_code);
1583        Ok(Some(WorkspaceEdit {
1584            changes: Some(HashMap::from([(
1585                params.text_document_position.text_document.uri,
1586                vec![TextEdit {
1587                    new_text: new_code,
1588                    range,
1589                }],
1590            )])),
1591            document_changes: None,
1592            change_annotations: None,
1593        }))
1594    }
1595
1596    async fn prepare_rename(&self, params: TextDocumentPositionParams) -> RpcResult<Option<PrepareRenameResponse>> {
1597        if self
1598            .inner_prepare_rename(&params, "someNameNoOneInTheirRightMindWouldEverUseForTesting")?
1599            .is_none()
1600        {
1601            return Ok(None);
1602        }
1603
1604        // Return back to the client, that it is safe to use the rename behavior.
1605        Ok(Some(PrepareRenameResponse::DefaultBehavior { default_behavior: true }))
1606    }
1607
1608    async fn folding_range(&self, params: FoldingRangeParams) -> RpcResult<Option<Vec<FoldingRange>>> {
1609        let filename = params.text_document.uri.to_string();
1610
1611        // Get the ast.
1612        let Some(ast) = self.ast_map.get(&filename) else {
1613            return Ok(None);
1614        };
1615
1616        // Get the folding ranges.
1617        let folding_ranges = ast.ast.get_lsp_folding_ranges();
1618
1619        if folding_ranges.is_empty() {
1620            return Ok(None);
1621        }
1622
1623        Ok(Some(folding_ranges))
1624    }
1625
1626    async fn code_action(&self, params: CodeActionParams) -> RpcResult<Option<CodeActionResponse>> {
1627        // Get the actual diagnostics from our map to ensure we have the full diagnostic info
1628        let filename = params.text_document.uri.to_string();
1629        let stored_diagnostics = self
1630            .diagnostics_map
1631            .get(&filename)
1632            .map(|d| d.clone())
1633            .unwrap_or_default();
1634
1635        // Use stored diagnostics if available, otherwise fall back to params.context.diagnostics
1636        let diagnostics_to_check: Vec<_> = if stored_diagnostics.is_empty() {
1637            params.context.diagnostics.clone()
1638        } else {
1639            // Match params.context.diagnostics with stored diagnostics by range
1640            params
1641                .context
1642                .diagnostics
1643                .iter()
1644                .filter_map(|param_diag| {
1645                    stored_diagnostics
1646                        .iter()
1647                        .find(|stored_diag| stored_diag.range == param_diag.range)
1648                        .cloned()
1649                })
1650                .collect()
1651        };
1652
1653        let actions = diagnostics_to_check
1654            .into_iter()
1655            .filter_map(|diagnostic| {
1656                // Handle regular suggestions (like camelCase)
1657                let (suggestion, range) = diagnostic
1658                    .data
1659                    .as_ref()
1660                    .and_then(|data| serde_json::from_value::<LspSuggestion>(data.clone()).ok())?;
1661                let edit = TextEdit {
1662                    range,
1663                    new_text: suggestion.insert,
1664                };
1665                let changes = HashMap::from([(params.text_document.uri.clone(), vec![edit])]);
1666
1667                // If you add more code action kinds, make sure you add it to the server
1668                // capabilities on initialization!
1669                Some(CodeActionOrCommand::CodeAction(CodeAction {
1670                    title: suggestion.title,
1671                    kind: Some(CodeActionKind::QUICKFIX),
1672                    diagnostics: Some(vec![diagnostic]),
1673                    edit: Some(WorkspaceEdit {
1674                        changes: Some(changes),
1675                        document_changes: None,
1676                        change_annotations: None,
1677                    }),
1678                    command: None,
1679                    is_preferred: Some(true),
1680                    disabled: None,
1681                    data: None,
1682                }))
1683            })
1684            .collect();
1685
1686        Ok(Some(actions))
1687    }
1688
1689    async fn document_color(&self, params: DocumentColorParams) -> RpcResult<Vec<ColorInformation>> {
1690        let filename = params.text_document.uri.to_string();
1691
1692        let Some(current_code) = self.code_map.get(&filename) else {
1693            return Ok(vec![]);
1694        };
1695        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1696            return Ok(vec![]);
1697        };
1698
1699        // Get the ast from our map.
1700        let Some(ast) = self.ast_map.get(&filename) else {
1701            return Ok(vec![]);
1702        };
1703
1704        // Get the colors from the ast.
1705        let Ok(colors) = ast.ast.document_color(current_code) else {
1706            return Ok(vec![]);
1707        };
1708
1709        Ok(colors)
1710    }
1711
1712    async fn color_presentation(&self, params: ColorPresentationParams) -> RpcResult<Vec<ColorPresentation>> {
1713        let filename = params.text_document.uri.to_string();
1714
1715        let Some(current_code) = self.code_map.get(&filename) else {
1716            return Ok(vec![]);
1717        };
1718        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1719            return Ok(vec![]);
1720        };
1721
1722        // Get the ast from our map.
1723        let Some(ast) = self.ast_map.get(&filename) else {
1724            return Ok(vec![]);
1725        };
1726
1727        let pos_start = position_to_char_index(params.range.start, current_code);
1728        let pos_end = position_to_char_index(params.range.end, current_code);
1729
1730        // Get the colors from the ast.
1731        let Ok(Some(presentation)) = ast.ast.color_presentation(&params.color, pos_start, pos_end) else {
1732            return Ok(vec![]);
1733        };
1734
1735        Ok(vec![presentation])
1736    }
1737}
1738
1739/// Get completions from our stdlib.
1740pub fn get_completions_from_stdlib(kcl_std: &ModData) -> Result<HashMap<String, CompletionItem>> {
1741    let mut completions = HashMap::new();
1742
1743    for d in kcl_std.all_docs() {
1744        if let Some(ci) = d.to_completion_item() {
1745            completions.insert(d.name().to_owned(), ci);
1746        }
1747    }
1748
1749    let variable_kinds = VariableKind::to_completion_items();
1750    for variable_kind in variable_kinds {
1751        completions.insert(variable_kind.label.clone(), variable_kind);
1752    }
1753
1754    Ok(completions)
1755}
1756
1757/// Get signatures from our stdlib.
1758pub fn get_signatures_from_stdlib(kcl_std: &ModData) -> HashMap<String, SignatureHelp> {
1759    let mut signatures = HashMap::new();
1760
1761    for d in kcl_std.all_docs() {
1762        if let Some(sig) = d.to_signature_help() {
1763            signatures.insert(d.name().to_owned(), sig);
1764        }
1765    }
1766
1767    signatures
1768}
1769
1770#[derive(Clone, Debug)]
1771pub struct LspArgData {
1772    pub tip: String,
1773    pub props: ArgData,
1774}
1775
1776/// Get signatures from our stdlib.
1777pub fn get_arg_maps_from_stdlib(kcl_std: &ModData) -> HashMap<String, HashMap<String, LspArgData>> {
1778    let mut result = HashMap::new();
1779
1780    for d in kcl_std.all_docs() {
1781        let crate::docs::kcl_doc::DocData::Fn(f) = d else {
1782            continue;
1783        };
1784        let arg_map: HashMap<String, _> = f
1785            .args
1786            .iter()
1787            .map(|data| {
1788                let mut tip = "```\n".to_owned();
1789                tip.push_str(&data.to_string());
1790                tip.push_str("\n```");
1791                if let Some(docs) = &data.docs {
1792                    tip.push_str("\n\n");
1793                    tip.push_str(docs);
1794                }
1795                let arg_data = LspArgData {
1796                    tip,
1797                    props: data.clone(),
1798                };
1799                (data.name.clone(), arg_data)
1800            })
1801            .collect();
1802        if !arg_map.is_empty() {
1803            result.insert(f.name.clone(), arg_map);
1804        }
1805    }
1806
1807    result
1808}
1809
1810/// Convert a position to a character index from the start of the file.
1811fn position_to_char_index(position: Position, code: &str) -> usize {
1812    // Get the character position from the start of the file.
1813    let mut char_position = 0;
1814    for (index, line) in code.lines().enumerate() {
1815        if index == position.line as usize {
1816            char_position += position.character as usize;
1817            break;
1818        } else {
1819            char_position += line.len() + 1;
1820        }
1821    }
1822
1823    let end_of_file = if code.is_empty() { 0 } else { code.len() - 1 };
1824    std::cmp::min(char_position, end_of_file)
1825}
1826
1827async fn with_cached_var<T>(name: &str, f: impl Fn(&KclValue) -> T) -> Option<T> {
1828    let mem = cache::read_old_memory().await?;
1829    let value = mem.stack.get(name, SourceRange::default()).ok()?;
1830
1831    Some(f(value))
1832}
1833
1834#[cfg(test)]
1835mod tests {
1836    use pretty_assertions::assert_eq;
1837
1838    use super::*;
1839
1840    #[test]
1841    fn test_position_to_char_index_first_line() {
1842        let code = r#"def foo():
1843return 42"#;
1844        let position = Position::new(0, 3);
1845        let index = position_to_char_index(position, code);
1846        assert_eq!(index, 3);
1847    }
1848
1849    #[test]
1850    fn test_position_to_char_index() {
1851        let code = r#"def foo():
1852return 42"#;
1853        let position = Position::new(1, 4);
1854        let index = position_to_char_index(position, code);
1855        assert_eq!(index, 15);
1856    }
1857
1858    #[test]
1859    fn test_position_to_char_index_with_newline() {
1860        let code = r#"def foo():
1861
1862return 42"#;
1863        let position = Position::new(2, 0);
1864        let index = position_to_char_index(position, code);
1865        assert_eq!(index, 12);
1866    }
1867
1868    #[test]
1869    fn test_position_to_char_at_end() {
1870        let code = r#"def foo():
1871return 42"#;
1872
1873        let position = Position::new(1, 8);
1874        let index = position_to_char_index(position, code);
1875        assert_eq!(index, 19);
1876    }
1877
1878    #[test]
1879    fn test_position_to_char_empty() {
1880        let code = r#""#;
1881        let position = Position::new(0, 0);
1882        let index = position_to_char_index(position, code);
1883        assert_eq!(index, 0);
1884    }
1885}