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 discovered_findings = ast.lint_all().into_iter().flatten().collect::<Vec<_>>();
442            self.add_to_diagnostics(&params, &discovered_findings, false).await;
443        }
444
445        // Send the notification to the client that the ast was updated.
446        if self.can_execute().await || self.executor_ctx().await.is_none() {
447            // Only send the notification if we can execute.
448            // Otherwise it confuses the client.
449            self.client
450                .send_notification::<custom_notifications::AstUpdated>(ast.ast.clone())
451                .await;
452        }
453
454        // Execute the code if we have an executor context.
455        // This function automatically executes if we should & updates the diagnostics if we got
456        // errors.
457        if self.execute(&params, &ast).await.is_err() {
458            return;
459        }
460
461        // If we made it here we can clear the diagnostics.
462        self.clear_diagnostics_map(&params.uri, Some(DiagnosticSeverity::ERROR))
463            .await;
464    }
465}
466
467impl Backend {
468    pub async fn can_execute(&self) -> bool {
469        *self.can_execute.read().await
470    }
471
472    pub async fn executor_ctx(&self) -> tokio::sync::RwLockReadGuard<'_, Option<crate::execution::ExecutorContext>> {
473        self.executor_ctx.read().await
474    }
475
476    async fn update_semantic_tokens(&self, tokens: &TokenStream, params: &TextDocumentItem) {
477        // Update the semantic tokens map.
478        let mut semantic_tokens = vec![];
479        let mut last_position = Position::new(0, 0);
480        for token in tokens.as_slice() {
481            let Ok(token_type) = SemanticTokenType::try_from(token.token_type) else {
482                // We continue here because not all tokens can be converted this way, we will get
483                // the rest from the ast.
484                continue;
485            };
486
487            let mut token_type_index = match self.get_semantic_token_type_index(&token_type) {
488                Some(index) => index,
489                // This is actually bad this should not fail.
490                // The test for listing all semantic token types should make this never happen.
491                None => {
492                    self.client
493                        .log_message(
494                            MessageType::ERROR,
495                            format!("token type `{token_type:?}` not accounted for"),
496                        )
497                        .await;
498                    continue;
499                }
500            };
501
502            let source_range: SourceRange = token.into();
503            let position = source_range.start_to_lsp_position(&params.text);
504
505            // Calculate the token modifiers.
506            // Get the value at the current position.
507            let token_modifiers_bitset = match self.ast_map.get(params.uri.as_str()) {
508                Some(ast) => {
509                    let token_index = Arc::new(Mutex::new(token_type_index));
510                    let modifier_index: Arc<Mutex<u32>> = Arc::new(Mutex::new(0));
511                    crate::walk::walk(&ast.ast, |node: crate::walk::Node| {
512                        let Ok(node_range): Result<SourceRange, _> = (&node).try_into() else {
513                            return Ok(true);
514                        };
515
516                        if !node_range.contains(source_range.start()) {
517                            return Ok(true);
518                        }
519
520                        let get_modifier = |modifier: Vec<SemanticTokenModifier>| -> Result<bool> {
521                            let mut mods = modifier_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
522                            let Some(token_modifier_index) = self.get_semantic_token_modifier_index(modifier) else {
523                                return Ok(true);
524                            };
525                            if *mods == 0 {
526                                *mods = token_modifier_index;
527                            } else {
528                                *mods |= token_modifier_index;
529                            }
530                            Ok(false)
531                        };
532
533                        match node {
534                            crate::walk::Node::TagDeclarator(_) => {
535                                return get_modifier(vec![
536                                    SemanticTokenModifier::DEFINITION,
537                                    SemanticTokenModifier::STATIC,
538                                ]);
539                            }
540                            crate::walk::Node::VariableDeclarator(variable) => {
541                                let sr: SourceRange = (&variable.id).into();
542                                if sr.contains(source_range.start()) {
543                                    if let Expr::FunctionExpression(_) = &variable.init {
544                                        let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
545                                        *ti = match self.get_semantic_token_type_index(&SemanticTokenType::FUNCTION) {
546                                            Some(index) => index,
547                                            None => token_type_index,
548                                        };
549                                    }
550
551                                    return get_modifier(vec![
552                                        SemanticTokenModifier::DECLARATION,
553                                        SemanticTokenModifier::READONLY,
554                                    ]);
555                                }
556                            }
557                            crate::walk::Node::Parameter(_) => {
558                                let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
559                                *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PARAMETER) {
560                                    Some(index) => index,
561                                    None => token_type_index,
562                                };
563                                return Ok(false);
564                            }
565                            crate::walk::Node::MemberExpression(member_expression) => {
566                                let sr: SourceRange = (&member_expression.property).into();
567                                if sr.contains(source_range.start()) {
568                                    let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
569                                    *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PROPERTY) {
570                                        Some(index) => index,
571                                        None => token_type_index,
572                                    };
573                                    return Ok(false);
574                                }
575                            }
576                            crate::walk::Node::ObjectProperty(object_property) => {
577                                let sr: SourceRange = (&object_property.key).into();
578                                if sr.contains(source_range.start()) {
579                                    let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
580                                    *ti = match self.get_semantic_token_type_index(&SemanticTokenType::PROPERTY) {
581                                        Some(index) => index,
582                                        None => token_type_index,
583                                    };
584                                }
585                                return get_modifier(vec![SemanticTokenModifier::DECLARATION]);
586                            }
587                            crate::walk::Node::CallExpressionKw(call_expr) => {
588                                let sr: SourceRange = (&call_expr.callee).into();
589                                if sr.contains(source_range.start()) {
590                                    let mut ti = token_index.lock().map_err(|_| anyhow::anyhow!("mutex"))?;
591                                    *ti = match self.get_semantic_token_type_index(&SemanticTokenType::FUNCTION) {
592                                        Some(index) => index,
593                                        None => token_type_index,
594                                    };
595
596                                    if self.stdlib_completions.contains_key(&call_expr.callee.name.name) {
597                                        // This is a stdlib function.
598                                        return get_modifier(vec![SemanticTokenModifier::DEFAULT_LIBRARY]);
599                                    }
600
601                                    return Ok(false);
602                                }
603                            }
604                            _ => {}
605                        }
606                        Ok(true)
607                    })
608                    .unwrap_or_default();
609
610                    let t = match token_index.lock() {
611                        Ok(guard) => *guard,
612                        _ => 0,
613                    };
614                    token_type_index = t;
615
616                    match modifier_index.lock() {
617                        Ok(guard) => *guard,
618                        _ => 0,
619                    }
620                }
621                _ => 0,
622            };
623
624            // We need to check if we are on the last token of the line.
625            // If we are starting from the end of the last line just add 1 to the line.
626            // Check if we are on the last token of the line.
627            if let Some(line) = params.text.lines().nth(position.line as usize)
628                && line.len() == position.character as usize
629            {
630                // We are on the last token of the line.
631                // We need to add a new line.
632                let semantic_token = SemanticToken {
633                    delta_line: position.line - last_position.line + 1,
634                    delta_start: 0,
635                    length: (token.end - token.start) as u32,
636                    token_type: token_type_index,
637                    token_modifiers_bitset,
638                };
639
640                semantic_tokens.push(semantic_token);
641
642                last_position = Position::new(position.line + 1, 0);
643                continue;
644            }
645
646            let semantic_token = SemanticToken {
647                delta_line: position.line - last_position.line,
648                delta_start: if position.line != last_position.line {
649                    position.character
650                } else {
651                    position.character - last_position.character
652                },
653                length: (token.end - token.start) as u32,
654                token_type: token_type_index,
655                token_modifiers_bitset,
656            };
657
658            semantic_tokens.push(semantic_token);
659
660            last_position = position;
661        }
662        self.semantic_tokens_map.insert(params.uri.to_string(), semantic_tokens);
663    }
664
665    async fn clear_diagnostics_map(&self, uri: &url::Url, severity: Option<DiagnosticSeverity>) {
666        let Some(mut items) = self.diagnostics_map.get_mut(uri.as_str()) else {
667            return;
668        };
669
670        // If we only want to clear a specific severity, do that.
671        if let Some(severity) = severity {
672            items.retain(|x| x.severity != Some(severity));
673        } else {
674            items.clear();
675        }
676
677        if items.is_empty() {
678            #[cfg(not(target_arch = "wasm32"))]
679            {
680                self.client.publish_diagnostics(uri.clone(), items.clone(), None).await;
681            }
682
683            // We need to drop the items here.
684            drop(items);
685
686            self.diagnostics_map.remove(uri.as_str());
687        } else {
688            // We don't need to update the map since we used get_mut.
689
690            #[cfg(not(target_arch = "wasm32"))]
691            {
692                self.client.publish_diagnostics(uri.clone(), items.clone(), None).await;
693            }
694        }
695    }
696
697    async fn add_to_diagnostics<DiagT: IntoDiagnostic + std::fmt::Debug>(
698        &self,
699        params: &TextDocumentItem,
700        diagnostics: &[DiagT],
701        clear_all_before_add: bool,
702    ) {
703        if diagnostics.is_empty() {
704            return;
705        }
706
707        if clear_all_before_add {
708            self.clear_diagnostics_map(&params.uri, None).await;
709        } else if diagnostics.iter().all(|x| x.severity() == DiagnosticSeverity::ERROR) {
710            // If the diagnostic is an error, it will be the only error we get since that halts
711            // execution.
712            // Clear the diagnostics before we add a new one.
713            self.clear_diagnostics_map(&params.uri, Some(DiagnosticSeverity::ERROR))
714                .await;
715        } else if diagnostics
716            .iter()
717            .all(|x| x.severity() == DiagnosticSeverity::INFORMATION)
718        {
719            // If the diagnostic is a lint, we will pass them all to add at once so we need to
720            // clear the old ones.
721            self.clear_diagnostics_map(&params.uri, Some(DiagnosticSeverity::INFORMATION))
722                .await;
723        }
724
725        let mut items = match self.diagnostics_map.get(params.uri.as_str()) {
726            Some(items) => {
727                // TODO: Would be awesome to fix the clone here.
728                items.clone()
729            }
730            _ => {
731                vec![]
732            }
733        };
734
735        for diagnostic in diagnostics {
736            let lsp_d = diagnostic.to_lsp_diagnostics(&params.text);
737            // Make sure we don't duplicate diagnostics.
738            for d in lsp_d {
739                if !items.iter().any(|x| x == &d) {
740                    items.push(d);
741                }
742            }
743        }
744
745        self.diagnostics_map.insert(params.uri.to_string(), items.clone());
746
747        self.client.publish_diagnostics(params.uri.clone(), items, None).await;
748    }
749
750    async fn execute(&self, params: &TextDocumentItem, ast: &Program) -> Result<()> {
751        // Check if we can execute.
752        if !self.can_execute().await {
753            return Ok(());
754        }
755
756        // Execute the code if we have an executor context.
757        let ctx = self.executor_ctx().await;
758        let Some(ref executor_ctx) = *ctx else {
759            return Ok(());
760        };
761
762        if !self.is_initialized().await {
763            // We are not initialized yet.
764            return Ok(());
765        }
766
767        match executor_ctx.run_with_caching(ast.clone()).await {
768            Err(err) => {
769                self.add_to_diagnostics(params, &[err], false).await;
770
771                // Since we already published the diagnostics we don't really care about the error
772                // string.
773                Err(anyhow::anyhow!("failed to execute code"))
774            }
775            Ok(_) => Ok(()),
776        }
777    }
778
779    pub fn get_semantic_token_type_index(&self, token_type: &SemanticTokenType) -> Option<u32> {
780        SEMANTIC_TOKEN_TYPES
781            .iter()
782            .position(|x| *x == *token_type)
783            .map(|y| y as u32)
784    }
785
786    pub fn get_semantic_token_modifier_index(&self, token_types: Vec<SemanticTokenModifier>) -> Option<u32> {
787        if token_types.is_empty() {
788            return None;
789        }
790
791        let mut modifier = None;
792        for token_type in token_types {
793            if let Some(index) = SEMANTIC_TOKEN_MODIFIERS
794                .iter()
795                .position(|x| *x == token_type)
796                .map(|y| y as u32)
797            {
798                modifier = match modifier {
799                    Some(modifier) => Some(modifier | index),
800                    None => Some(index),
801                };
802            }
803        }
804        modifier
805    }
806
807    pub async fn create_zip(&self) -> Result<Vec<u8>> {
808        // Collect all the file data we know.
809        let mut buf = vec![];
810        let mut zip = zip::ZipWriter::new(std::io::Cursor::new(&mut buf));
811        for code in self.code_map.iter() {
812            let entry = code.key();
813            let value = code.value();
814            let file_name = entry.replace("file://", "").to_string();
815
816            let options = zip::write::SimpleFileOptions::default().compression_method(zip::CompressionMethod::Stored);
817            zip.start_file(file_name, options)?;
818            zip.write_all(value)?;
819        }
820        // Apply the changes you've made.
821        // Dropping the `ZipWriter` will have the same effect, but may silently fail
822        zip.finish()?;
823
824        Ok(buf)
825    }
826
827    pub async fn send_telemetry(&self) -> Result<()> {
828        // Get information about the user.
829        let user = self
830            .zoo_client
831            .users()
832            .get_self()
833            .await
834            .map_err(|e| anyhow::anyhow!(e.to_string()))?;
835
836        // Hash the user's id.
837        // Create a SHA-256 object
838        let mut hasher = sha2::Sha256::new();
839        // Write input message
840        hasher.update(user.id);
841        // Read hash digest and consume hasher
842        let result = hasher.finalize();
843        // Get the hash as a string.
844        let user_id_hash = format!("{result:x}");
845
846        // Get the workspace folders.
847        // The key of the workspace folder is the project name.
848        let workspace_folders = self.workspace_folders().await;
849        let project_names: Vec<&str> = workspace_folders.iter().map(|v| v.name.as_str()).collect::<Vec<_>>();
850        // Get the first name.
851        let project_name = project_names
852            .first()
853            .ok_or_else(|| anyhow::anyhow!("no project names"))?
854            .to_string();
855
856        // Send the telemetry data.
857        self.zoo_client
858            .meta()
859            .create_event(
860                vec![kittycad::types::multipart::Attachment {
861                    // Clean the URI part.
862                    name: "attachment".to_string(),
863                    filepath: Some("attachment.zip".into()),
864                    content_type: Some("application/x-zip".to_string()),
865                    data: self.create_zip().await?,
866                }],
867                &kittycad::types::Event {
868                    // This gets generated server side so leave empty for now.
869                    attachment_uri: None,
870                    created_at: chrono::Utc::now(),
871                    event_type: kittycad::types::ModelingAppEventType::SuccessfulCompileBeforeClose,
872                    last_compiled_at: Some(chrono::Utc::now()),
873                    // We do not have project descriptions yet.
874                    project_description: None,
875                    project_name,
876                    // The UUID for the Design Studio.
877                    // We can unwrap here because we know it will not panic.
878                    source_id: uuid::Uuid::from_str("70178592-dfca-47b3-bd2d-6fce2bcaee04").unwrap(),
879                    type_: kittycad::types::Type::ModelingAppEvent,
880                    user_id: user_id_hash,
881                },
882            )
883            .await
884            .map_err(|e| anyhow::anyhow!(e.to_string()))?;
885
886        Ok(())
887    }
888
889    pub async fn update_can_execute(
890        &self,
891        params: custom_notifications::UpdateCanExecuteParams,
892    ) -> RpcResult<custom_notifications::UpdateCanExecuteResponse> {
893        let mut can_execute = self.can_execute.write().await;
894
895        if *can_execute == params.can_execute {
896            return Ok(custom_notifications::UpdateCanExecuteResponse {});
897        }
898
899        *can_execute = params.can_execute;
900
901        Ok(custom_notifications::UpdateCanExecuteResponse {})
902    }
903
904    /// Returns the new string for the code after rename.
905    pub fn inner_prepare_rename(
906        &self,
907        params: &TextDocumentPositionParams,
908        new_name: &str,
909    ) -> RpcResult<Option<(String, String)>> {
910        let filename = params.text_document.uri.to_string();
911
912        let Some(current_code) = self.code_map.get(&filename) else {
913            return Ok(None);
914        };
915        let Ok(current_code) = std::str::from_utf8(&current_code) else {
916            return Ok(None);
917        };
918
919        // Parse the ast.
920        // I don't know if we need to do this again since it should be updated in the context.
921        // But I figure better safe than sorry since this will write back out to the file.
922        let module_id = ModuleId::default();
923        let Ok(mut ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
924            return Ok(None);
925        };
926
927        // Let's convert the position to a character index.
928        let pos = position_to_char_index(params.position, current_code);
929        // Now let's perform the rename on the ast.
930        ast.rename_symbol(new_name, pos);
931        // Now recast it.
932        let recast = ast.recast_top(&Default::default(), 0);
933
934        Ok(Some((current_code.to_string(), recast)))
935    }
936}
937
938#[tower_lsp::async_trait]
939impl LanguageServer for Backend {
940    async fn initialize(&self, params: InitializeParams) -> RpcResult<InitializeResult> {
941        self.client
942            .log_message(MessageType::INFO, format!("initialize: {params:?}"))
943            .await;
944
945        Ok(InitializeResult {
946            capabilities: ServerCapabilities {
947                color_provider: Some(ColorProviderCapability::Simple(true)),
948                code_action_provider: Some(CodeActionProviderCapability::Options(CodeActionOptions {
949                    code_action_kinds: Some(vec![CodeActionKind::QUICKFIX]),
950                    resolve_provider: Some(false),
951                    work_done_progress_options: WorkDoneProgressOptions::default(),
952                })),
953                completion_provider: Some(CompletionOptions {
954                    resolve_provider: Some(false),
955                    trigger_characters: Some(vec![".".to_string()]),
956                    work_done_progress_options: Default::default(),
957                    all_commit_characters: None,
958                    ..Default::default()
959                }),
960                diagnostic_provider: Some(DiagnosticServerCapabilities::Options(DiagnosticOptions {
961                    ..Default::default()
962                })),
963                document_formatting_provider: Some(OneOf::Left(true)),
964                folding_range_provider: Some(FoldingRangeProviderCapability::Simple(true)),
965                hover_provider: Some(HoverProviderCapability::Simple(true)),
966                inlay_hint_provider: Some(OneOf::Left(true)),
967                rename_provider: Some(OneOf::Left(true)),
968                semantic_tokens_provider: Some(SemanticTokensServerCapabilities::SemanticTokensRegistrationOptions(
969                    SemanticTokensRegistrationOptions {
970                        text_document_registration_options: {
971                            TextDocumentRegistrationOptions {
972                                document_selector: Some(vec![DocumentFilter {
973                                    language: Some("kcl".to_string()),
974                                    scheme: Some("file".to_string()),
975                                    pattern: None,
976                                }]),
977                            }
978                        },
979                        semantic_tokens_options: SemanticTokensOptions {
980                            work_done_progress_options: WorkDoneProgressOptions::default(),
981                            legend: SemanticTokensLegend {
982                                token_types: SEMANTIC_TOKEN_TYPES.to_vec(),
983                                token_modifiers: SEMANTIC_TOKEN_MODIFIERS.to_vec(),
984                            },
985                            range: Some(false),
986                            full: Some(SemanticTokensFullOptions::Bool(true)),
987                        },
988                        static_registration_options: StaticRegistrationOptions::default(),
989                    },
990                )),
991                signature_help_provider: Some(SignatureHelpOptions {
992                    trigger_characters: None,
993                    retrigger_characters: None,
994                    ..Default::default()
995                }),
996                text_document_sync: Some(TextDocumentSyncCapability::Options(TextDocumentSyncOptions {
997                    open_close: Some(true),
998                    change: Some(TextDocumentSyncKind::FULL),
999                    ..Default::default()
1000                })),
1001                workspace: Some(WorkspaceServerCapabilities {
1002                    workspace_folders: Some(WorkspaceFoldersServerCapabilities {
1003                        supported: Some(true),
1004                        change_notifications: Some(OneOf::Left(true)),
1005                    }),
1006                    file_operations: None,
1007                }),
1008                ..Default::default()
1009            },
1010            ..Default::default()
1011        })
1012    }
1013
1014    async fn initialized(&self, params: InitializedParams) {
1015        self.do_initialized(params).await
1016    }
1017
1018    async fn shutdown(&self) -> RpcResult<()> {
1019        self.do_shutdown().await
1020    }
1021
1022    async fn did_change_workspace_folders(&self, params: DidChangeWorkspaceFoldersParams) {
1023        self.do_did_change_workspace_folders(params).await
1024    }
1025
1026    async fn did_change_configuration(&self, params: DidChangeConfigurationParams) {
1027        self.do_did_change_configuration(params).await
1028    }
1029
1030    async fn did_change_watched_files(&self, params: DidChangeWatchedFilesParams) {
1031        self.do_did_change_watched_files(params).await
1032    }
1033
1034    async fn did_create_files(&self, params: CreateFilesParams) {
1035        self.do_did_create_files(params).await
1036    }
1037
1038    async fn did_rename_files(&self, params: RenameFilesParams) {
1039        self.do_did_rename_files(params).await
1040    }
1041
1042    async fn did_delete_files(&self, params: DeleteFilesParams) {
1043        self.do_did_delete_files(params).await
1044    }
1045
1046    async fn did_open(&self, params: DidOpenTextDocumentParams) {
1047        self.do_did_open(params).await
1048    }
1049
1050    async fn did_change(&self, params: DidChangeTextDocumentParams) {
1051        self.do_did_change(params).await;
1052    }
1053
1054    async fn did_save(&self, params: DidSaveTextDocumentParams) {
1055        self.do_did_save(params).await
1056    }
1057
1058    async fn did_close(&self, params: DidCloseTextDocumentParams) {
1059        self.do_did_close(params).await;
1060
1061        // Inject telemetry if we can train on the user's code.
1062        // Return early if we cannot.
1063        if !self.can_send_telemetry {
1064            return;
1065        }
1066
1067        // In wasm this needs to be spawn_local since fucking reqwests doesn't implement Send for wasm.
1068        #[cfg(target_arch = "wasm32")]
1069        {
1070            let be = self.clone();
1071            wasm_bindgen_futures::spawn_local(async move {
1072                if let Err(err) = be.send_telemetry().await {
1073                    be.client
1074                        .log_message(MessageType::WARNING, format!("failed to send telemetry: {}", err))
1075                        .await;
1076                }
1077            });
1078        }
1079        #[cfg(not(target_arch = "wasm32"))]
1080        if let Err(err) = self.send_telemetry().await {
1081            self.client
1082                .log_message(MessageType::WARNING, format!("failed to send telemetry: {err}"))
1083                .await;
1084        }
1085    }
1086
1087    async fn hover(&self, params: HoverParams) -> RpcResult<Option<LspHover>> {
1088        let filename = params.text_document_position_params.text_document.uri.to_string();
1089
1090        let Some(current_code) = self.code_map.get(&filename) else {
1091            return Ok(None);
1092        };
1093        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1094            return Ok(None);
1095        };
1096
1097        let pos = position_to_char_index(params.text_document_position_params.position, current_code);
1098
1099        // Let's iterate over the AST and find the node that contains the cursor.
1100        let Some(ast) = self.ast_map.get(&filename) else {
1101            return Ok(None);
1102        };
1103
1104        let Some(hover) = ast
1105            .ast
1106            .get_hover_value_for_position(pos, current_code, &HoverOpts::default_for_hover())
1107        else {
1108            return Ok(None);
1109        };
1110
1111        match hover {
1112            Hover::Function { name, range } => {
1113                let (sig, docs) = if let Some(Some(result)) = with_cached_var(&name, |value| {
1114                    match value {
1115                        // User-defined function
1116                        KclValue::Function { value, .. } if !value.is_std => {
1117                            // TODO get docs from comments
1118                            Some((value.ast.signature(), ""))
1119                        }
1120                        _ => None,
1121                    }
1122                })
1123                .await
1124                {
1125                    result
1126                } else {
1127                    // Get the docs for this function.
1128                    let Some(completion) = self.stdlib_completions.get(&name) else {
1129                        return Ok(None);
1130                    };
1131                    let Some(docs) = &completion.documentation else {
1132                        return Ok(None);
1133                    };
1134
1135                    let docs = match docs {
1136                        Documentation::String(docs) => docs,
1137                        Documentation::MarkupContent(MarkupContent { value, .. }) => value,
1138                    };
1139
1140                    let docs = if docs.len() > 320 {
1141                        let end = docs.find("\n\n").or_else(|| docs.find("\n\r\n")).unwrap_or(320);
1142                        &docs[..end]
1143                    } else {
1144                        &**docs
1145                    };
1146
1147                    let Some(label_details) = &completion.label_details else {
1148                        return Ok(None);
1149                    };
1150
1151                    let sig = if let Some(detail) = &label_details.detail {
1152                        detail.clone()
1153                    } else {
1154                        String::new()
1155                    };
1156
1157                    (sig, docs)
1158                };
1159
1160                Ok(Some(LspHover {
1161                    contents: HoverContents::Markup(MarkupContent {
1162                        kind: MarkupKind::Markdown,
1163                        value: format!("```\n{name}{sig}\n```\n\n{docs}"),
1164                    }),
1165                    range: Some(range),
1166                }))
1167            }
1168            Hover::Type { name, range } => {
1169                let Some(completion) = self.stdlib_completions.get(&name) else {
1170                    return Ok(None);
1171                };
1172                let Some(docs) = &completion.documentation else {
1173                    return Ok(None);
1174                };
1175
1176                let docs = match docs {
1177                    Documentation::String(docs) => docs,
1178                    Documentation::MarkupContent(MarkupContent { value, .. }) => value,
1179                };
1180
1181                let docs = if docs.len() > 320 {
1182                    let end = docs.find("\n\n").or_else(|| docs.find("\n\r\n")).unwrap_or(320);
1183                    &docs[..end]
1184                } else {
1185                    &**docs
1186                };
1187
1188                Ok(Some(LspHover {
1189                    contents: HoverContents::Markup(MarkupContent {
1190                        kind: MarkupKind::Markdown,
1191                        value: format!("```\n{name}\n```\n\n{docs}"),
1192                    }),
1193                    range: Some(range),
1194                }))
1195            }
1196            Hover::KwArg {
1197                name,
1198                callee_name,
1199                range,
1200            } => {
1201                // TODO handle user-defined functions too
1202
1203                let Some(arg_map) = self.stdlib_args.get(&callee_name) else {
1204                    return Ok(None);
1205                };
1206
1207                let Some(arg_entry) = arg_map.get(&name) else {
1208                    return Ok(None);
1209                };
1210
1211                Ok(Some(LspHover {
1212                    contents: HoverContents::Markup(MarkupContent {
1213                        kind: MarkupKind::Markdown,
1214                        value: arg_entry.tip.clone(),
1215                    }),
1216                    range: Some(range),
1217                }))
1218            }
1219            Hover::Variable {
1220                name,
1221                ty: Some(ty),
1222                range,
1223            } => Ok(Some(LspHover {
1224                contents: HoverContents::Markup(MarkupContent {
1225                    kind: MarkupKind::Markdown,
1226                    value: format!("```\n{name}: {ty}\n```"),
1227                }),
1228                range: Some(range),
1229            })),
1230            Hover::Variable { name, ty: None, range } => Ok(with_cached_var(&name, |value| {
1231                let mut text: String = format!("```\n{name}");
1232                if let Some(ty) = value.principal_type() {
1233                    text.push_str(&format!(": {}", ty.human_friendly_type()));
1234                }
1235                if let Some(v) = value.value_str() {
1236                    text.push_str(&format!(" = {v}"));
1237                }
1238                text.push_str("\n```");
1239
1240                LspHover {
1241                    contents: HoverContents::Markup(MarkupContent {
1242                        kind: MarkupKind::Markdown,
1243                        value: text,
1244                    }),
1245                    range: Some(range),
1246                }
1247            })
1248            .await),
1249            Hover::Signature { .. } => Ok(None),
1250            Hover::Comment { value, range } => Ok(Some(LspHover {
1251                contents: HoverContents::Markup(MarkupContent {
1252                    kind: MarkupKind::Markdown,
1253                    value,
1254                }),
1255                range: Some(range),
1256            })),
1257        }
1258    }
1259
1260    async fn completion(&self, params: CompletionParams) -> RpcResult<Option<CompletionResponse>> {
1261        let mut completions = vec![CompletionItem {
1262            label: PIPE_OPERATOR.to_string(),
1263            label_details: None,
1264            kind: Some(CompletionItemKind::OPERATOR),
1265            detail: Some("A pipe operator.".to_string()),
1266            documentation: Some(Documentation::MarkupContent(MarkupContent {
1267                kind: MarkupKind::Markdown,
1268                value: "A pipe operator.".to_string(),
1269            })),
1270            deprecated: Some(false),
1271            preselect: None,
1272            sort_text: None,
1273            filter_text: None,
1274            insert_text: Some("|> ".to_string()),
1275            insert_text_format: Some(InsertTextFormat::PLAIN_TEXT),
1276            insert_text_mode: None,
1277            text_edit: None,
1278            additional_text_edits: None,
1279            command: None,
1280            commit_characters: None,
1281            data: None,
1282            tags: None,
1283        }];
1284
1285        // Get the current line up to cursor
1286        let Some(current_code) = self
1287            .code_map
1288            .get(params.text_document_position.text_document.uri.as_ref())
1289        else {
1290            return Ok(Some(CompletionResponse::Array(completions)));
1291        };
1292        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1293            return Ok(Some(CompletionResponse::Array(completions)));
1294        };
1295
1296        // Get the current line up to cursor, with bounds checking
1297        if let Some(line) = current_code
1298            .lines()
1299            .nth(params.text_document_position.position.line as usize)
1300        {
1301            let char_pos = params.text_document_position.position.character as usize;
1302            if char_pos <= line.len() {
1303                let line_prefix = &line[..char_pos];
1304                // Get last word
1305                let last_word = line_prefix
1306                    .split(|c: char| c.is_whitespace() || c.is_ascii_punctuation())
1307                    .next_back()
1308                    .unwrap_or("");
1309
1310                // If the last word starts with a digit, return no completions
1311                if !last_word.is_empty() && last_word.chars().next().unwrap().is_ascii_digit() {
1312                    return Ok(None);
1313                }
1314            }
1315        }
1316
1317        completions.extend(self.stdlib_completions.values().cloned());
1318
1319        // Add more to the completions if we have more.
1320        let Some(ast) = self
1321            .ast_map
1322            .get(params.text_document_position.text_document.uri.as_ref())
1323        else {
1324            return Ok(Some(CompletionResponse::Array(completions)));
1325        };
1326
1327        let position = position_to_char_index(params.text_document_position.position, current_code);
1328        if ast.ast.in_comment(position) {
1329            // If we are in a code comment we don't want to show completions.
1330            return Ok(None);
1331        }
1332
1333        // If we're inside a CallExpression or something where a function parameter label could be completed,
1334        // then complete it.
1335        // Let's find the AST node that the user's cursor is in.
1336        if let Some(arg_label_completions) = self.try_arg_completions(&ast.ast, position, current_code) {
1337            completions.extend(arg_label_completions);
1338        }
1339
1340        // Get the completion items for the ast.
1341        let Ok(variables) = ast.ast.completion_items(position) else {
1342            return Ok(Some(CompletionResponse::Array(completions)));
1343        };
1344
1345        // Get our variables from our AST to include in our completions.
1346        completions.extend(variables);
1347
1348        Ok(Some(CompletionResponse::Array(completions)))
1349    }
1350
1351    async fn diagnostic(&self, params: DocumentDiagnosticParams) -> RpcResult<DocumentDiagnosticReportResult> {
1352        let filename = params.text_document.uri.to_string();
1353
1354        // Get the current diagnostics for this file.
1355        let Some(items) = self.diagnostics_map.get(&filename) else {
1356            // Send an empty report.
1357            return Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1358                RelatedFullDocumentDiagnosticReport {
1359                    related_documents: None,
1360                    full_document_diagnostic_report: FullDocumentDiagnosticReport {
1361                        result_id: None,
1362                        items: vec![],
1363                    },
1364                },
1365            )));
1366        };
1367
1368        Ok(DocumentDiagnosticReportResult::Report(DocumentDiagnosticReport::Full(
1369            RelatedFullDocumentDiagnosticReport {
1370                related_documents: None,
1371                full_document_diagnostic_report: FullDocumentDiagnosticReport {
1372                    result_id: None,
1373                    items: items.clone(),
1374                },
1375            },
1376        )))
1377    }
1378
1379    async fn signature_help(&self, params: SignatureHelpParams) -> RpcResult<Option<SignatureHelp>> {
1380        let filename = params.text_document_position_params.text_document.uri.to_string();
1381
1382        let Some(current_code) = self.code_map.get(&filename) else {
1383            return Ok(None);
1384        };
1385        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1386            return Ok(None);
1387        };
1388
1389        let pos = position_to_char_index(params.text_document_position_params.position, current_code);
1390
1391        // Get the character at the position.
1392        let Some(ch) = current_code.chars().nth(pos) else {
1393            return Ok(None);
1394        };
1395
1396        let check_char = |ch: char| {
1397            // If  we are on a (, then get the string in front of the (
1398            // and try to get the signature.
1399            // We do these before the ast check because we might not have a valid ast.
1400            if ch == '(' {
1401                // If the current character is not a " " then get the next space after
1402                // our position so we can split on that.
1403                // Find the next space after the current position.
1404                let next_space = if ch != ' ' {
1405                    if let Some(next_space) = current_code[pos..].find(' ') {
1406                        pos + next_space
1407                    } else if let Some(next_space) = current_code[pos..].find('(') {
1408                        pos + next_space
1409                    } else {
1410                        pos
1411                    }
1412                } else {
1413                    pos
1414                };
1415                let p2 = std::cmp::max(pos, next_space);
1416
1417                let last_word = current_code[..p2].split_whitespace().last()?;
1418
1419                // Get the function name.
1420                return self.stdlib_signatures.get(last_word);
1421            } else if ch == ',' {
1422                // If we have a comma, then get the string in front of
1423                // the closest ( and try to get the signature.
1424
1425                // Find the last ( before the comma.
1426                let last_paren = current_code[..pos].rfind('(')?;
1427                // Get the string in front of the (.
1428                let last_word = current_code[..last_paren].split_whitespace().last()?;
1429                // Get the function name.
1430                return self.stdlib_signatures.get(last_word);
1431            }
1432
1433            None
1434        };
1435
1436        if let Some(signature) = check_char(ch) {
1437            return Ok(Some(signature.clone()));
1438        }
1439
1440        // Check if we have context.
1441        if let Some(context) = params.context
1442            && let Some(character) = context.trigger_character
1443        {
1444            for character in character.chars() {
1445                // Check if we are on a ( or a ,.
1446                if (character == '(' || character == ',')
1447                    && let Some(signature) = check_char(character)
1448                {
1449                    return Ok(Some(signature.clone()));
1450                }
1451            }
1452        }
1453
1454        // Let's iterate over the AST and find the node that contains the cursor.
1455        let Some(ast) = self.ast_map.get(&filename) else {
1456            return Ok(None);
1457        };
1458
1459        let Some(value) = ast.ast.get_expr_for_position(pos) else {
1460            return Ok(None);
1461        };
1462
1463        let Some(hover) =
1464            value.get_hover_value_for_position(pos, current_code, &HoverOpts::default_for_signature_help())
1465        else {
1466            return Ok(None);
1467        };
1468
1469        match hover {
1470            Hover::Function { name, range: _ } => {
1471                // Get the docs for this function.
1472                let Some(signature) = self.stdlib_signatures.get(&name) else {
1473                    return Ok(None);
1474                };
1475
1476                Ok(Some(signature.clone()))
1477            }
1478            Hover::Signature {
1479                name,
1480                parameter_index,
1481                range: _,
1482            } => {
1483                let Some(signature) = self.stdlib_signatures.get(&name) else {
1484                    return Ok(None);
1485                };
1486
1487                let mut signature = signature.clone();
1488
1489                signature.active_parameter = Some(parameter_index);
1490
1491                Ok(Some(signature))
1492            }
1493            _ => {
1494                return Ok(None);
1495            }
1496        }
1497    }
1498
1499    async fn inlay_hint(&self, _params: InlayHintParams) -> RpcResult<Option<Vec<InlayHint>>> {
1500        // TODO: do this
1501
1502        Ok(None)
1503    }
1504
1505    async fn semantic_tokens_full(&self, params: SemanticTokensParams) -> RpcResult<Option<SemanticTokensResult>> {
1506        let filename = params.text_document.uri.to_string();
1507
1508        let Some(semantic_tokens) = self.semantic_tokens_map.get(&filename) else {
1509            return Ok(None);
1510        };
1511
1512        Ok(Some(SemanticTokensResult::Tokens(SemanticTokens {
1513            result_id: None,
1514            data: semantic_tokens.clone(),
1515        })))
1516    }
1517
1518    async fn document_symbol(&self, params: DocumentSymbolParams) -> RpcResult<Option<DocumentSymbolResponse>> {
1519        let filename = params.text_document.uri.to_string();
1520
1521        let Some(symbols) = self.symbols_map.get(&filename) else {
1522            return Ok(None);
1523        };
1524
1525        Ok(Some(DocumentSymbolResponse::Nested(symbols.clone())))
1526    }
1527
1528    async fn formatting(&self, params: DocumentFormattingParams) -> RpcResult<Option<Vec<TextEdit>>> {
1529        let filename = params.text_document.uri.to_string();
1530
1531        let Some(current_code) = self.code_map.get(&filename) else {
1532            return Ok(None);
1533        };
1534        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1535            return Ok(None);
1536        };
1537
1538        // Parse the ast.
1539        // I don't know if we need to do this again since it should be updated in the context.
1540        // But I figure better safe than sorry since this will write back out to the file.
1541        let module_id = ModuleId::default();
1542        let Ok(ast) = crate::parsing::parse_str(current_code, module_id).parse_errs_as_err() else {
1543            return Ok(None);
1544        };
1545        // Now recast it.
1546        let recast = ast.recast_top(
1547            &crate::parsing::ast::types::FormatOptions {
1548                tab_size: params.options.tab_size as usize,
1549                insert_final_newline: params.options.insert_final_newline.unwrap_or(false),
1550                use_tabs: !params.options.insert_spaces,
1551            },
1552            0,
1553        );
1554        let source_range = SourceRange::new(0, current_code.len(), module_id);
1555        let range = source_range.to_lsp_range(current_code);
1556        Ok(Some(vec![TextEdit {
1557            new_text: recast,
1558            range,
1559        }]))
1560    }
1561
1562    async fn rename(&self, params: RenameParams) -> RpcResult<Option<WorkspaceEdit>> {
1563        let Some((current_code, new_code)) =
1564            self.inner_prepare_rename(&params.text_document_position, &params.new_name)?
1565        else {
1566            return Ok(None);
1567        };
1568
1569        let source_range = SourceRange::new(0, current_code.len(), ModuleId::default());
1570        let range = source_range.to_lsp_range(&current_code);
1571        Ok(Some(WorkspaceEdit {
1572            changes: Some(HashMap::from([(
1573                params.text_document_position.text_document.uri,
1574                vec![TextEdit {
1575                    new_text: new_code,
1576                    range,
1577                }],
1578            )])),
1579            document_changes: None,
1580            change_annotations: None,
1581        }))
1582    }
1583
1584    async fn prepare_rename(&self, params: TextDocumentPositionParams) -> RpcResult<Option<PrepareRenameResponse>> {
1585        if self
1586            .inner_prepare_rename(&params, "someNameNoOneInTheirRightMindWouldEverUseForTesting")?
1587            .is_none()
1588        {
1589            return Ok(None);
1590        }
1591
1592        // Return back to the client, that it is safe to use the rename behavior.
1593        Ok(Some(PrepareRenameResponse::DefaultBehavior { default_behavior: true }))
1594    }
1595
1596    async fn folding_range(&self, params: FoldingRangeParams) -> RpcResult<Option<Vec<FoldingRange>>> {
1597        let filename = params.text_document.uri.to_string();
1598
1599        // Get the ast.
1600        let Some(ast) = self.ast_map.get(&filename) else {
1601            return Ok(None);
1602        };
1603
1604        // Get the folding ranges.
1605        let folding_ranges = ast.ast.get_lsp_folding_ranges();
1606
1607        if folding_ranges.is_empty() {
1608            return Ok(None);
1609        }
1610
1611        Ok(Some(folding_ranges))
1612    }
1613
1614    async fn code_action(&self, params: CodeActionParams) -> RpcResult<Option<CodeActionResponse>> {
1615        let actions = params
1616            .context
1617            .diagnostics
1618            .into_iter()
1619            .filter_map(|diagnostic| {
1620                let (suggestion, range) = diagnostic
1621                    .data
1622                    .as_ref()
1623                    .and_then(|data| serde_json::from_value::<LspSuggestion>(data.clone()).ok())?;
1624                let edit = TextEdit {
1625                    range,
1626                    new_text: suggestion.insert,
1627                };
1628                let changes = HashMap::from([(params.text_document.uri.clone(), vec![edit])]);
1629
1630                // If you add more code action kinds, make sure you add it to the server
1631                // capabilities on initialization!
1632                Some(CodeActionOrCommand::CodeAction(CodeAction {
1633                    title: suggestion.title,
1634                    kind: Some(CodeActionKind::QUICKFIX),
1635                    diagnostics: Some(vec![diagnostic]),
1636                    edit: Some(WorkspaceEdit {
1637                        changes: Some(changes),
1638                        document_changes: None,
1639                        change_annotations: None,
1640                    }),
1641                    command: None,
1642                    is_preferred: Some(true),
1643                    disabled: None,
1644                    data: None,
1645                }))
1646            })
1647            .collect();
1648
1649        Ok(Some(actions))
1650    }
1651
1652    async fn document_color(&self, params: DocumentColorParams) -> RpcResult<Vec<ColorInformation>> {
1653        let filename = params.text_document.uri.to_string();
1654
1655        let Some(current_code) = self.code_map.get(&filename) else {
1656            return Ok(vec![]);
1657        };
1658        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1659            return Ok(vec![]);
1660        };
1661
1662        // Get the ast from our map.
1663        let Some(ast) = self.ast_map.get(&filename) else {
1664            return Ok(vec![]);
1665        };
1666
1667        // Get the colors from the ast.
1668        let Ok(colors) = ast.ast.document_color(current_code) else {
1669            return Ok(vec![]);
1670        };
1671
1672        Ok(colors)
1673    }
1674
1675    async fn color_presentation(&self, params: ColorPresentationParams) -> RpcResult<Vec<ColorPresentation>> {
1676        let filename = params.text_document.uri.to_string();
1677
1678        let Some(current_code) = self.code_map.get(&filename) else {
1679            return Ok(vec![]);
1680        };
1681        let Ok(current_code) = std::str::from_utf8(&current_code) else {
1682            return Ok(vec![]);
1683        };
1684
1685        // Get the ast from our map.
1686        let Some(ast) = self.ast_map.get(&filename) else {
1687            return Ok(vec![]);
1688        };
1689
1690        let pos_start = position_to_char_index(params.range.start, current_code);
1691        let pos_end = position_to_char_index(params.range.end, current_code);
1692
1693        // Get the colors from the ast.
1694        let Ok(Some(presentation)) = ast.ast.color_presentation(&params.color, pos_start, pos_end) else {
1695            return Ok(vec![]);
1696        };
1697
1698        Ok(vec![presentation])
1699    }
1700}
1701
1702/// Get completions from our stdlib.
1703pub fn get_completions_from_stdlib(kcl_std: &ModData) -> Result<HashMap<String, CompletionItem>> {
1704    let mut completions = HashMap::new();
1705
1706    for d in kcl_std.all_docs() {
1707        if let Some(ci) = d.to_completion_item() {
1708            completions.insert(d.name().to_owned(), ci);
1709        }
1710    }
1711
1712    let variable_kinds = VariableKind::to_completion_items();
1713    for variable_kind in variable_kinds {
1714        completions.insert(variable_kind.label.clone(), variable_kind);
1715    }
1716
1717    Ok(completions)
1718}
1719
1720/// Get signatures from our stdlib.
1721pub fn get_signatures_from_stdlib(kcl_std: &ModData) -> HashMap<String, SignatureHelp> {
1722    let mut signatures = HashMap::new();
1723
1724    for d in kcl_std.all_docs() {
1725        if let Some(sig) = d.to_signature_help() {
1726            signatures.insert(d.name().to_owned(), sig);
1727        }
1728    }
1729
1730    signatures
1731}
1732
1733#[derive(Clone, Debug)]
1734pub struct LspArgData {
1735    pub tip: String,
1736    pub props: ArgData,
1737}
1738
1739/// Get signatures from our stdlib.
1740pub fn get_arg_maps_from_stdlib(kcl_std: &ModData) -> HashMap<String, HashMap<String, LspArgData>> {
1741    let mut result = HashMap::new();
1742
1743    for d in kcl_std.all_docs() {
1744        let crate::docs::kcl_doc::DocData::Fn(f) = d else {
1745            continue;
1746        };
1747        let arg_map: HashMap<String, _> = f
1748            .args
1749            .iter()
1750            .map(|data| {
1751                let mut tip = "```\n".to_owned();
1752                tip.push_str(&data.to_string());
1753                tip.push_str("\n```");
1754                if let Some(docs) = &data.docs {
1755                    tip.push_str("\n\n");
1756                    tip.push_str(docs);
1757                }
1758                let arg_data = LspArgData {
1759                    tip,
1760                    props: data.clone(),
1761                };
1762                (data.name.clone(), arg_data)
1763            })
1764            .collect();
1765        if !arg_map.is_empty() {
1766            result.insert(f.name.clone(), arg_map);
1767        }
1768    }
1769
1770    result
1771}
1772
1773/// Convert a position to a character index from the start of the file.
1774fn position_to_char_index(position: Position, code: &str) -> usize {
1775    // Get the character position from the start of the file.
1776    let mut char_position = 0;
1777    for (index, line) in code.lines().enumerate() {
1778        if index == position.line as usize {
1779            char_position += position.character as usize;
1780            break;
1781        } else {
1782            char_position += line.len() + 1;
1783        }
1784    }
1785
1786    std::cmp::min(char_position, code.len() - 1)
1787}
1788
1789async fn with_cached_var<T>(name: &str, f: impl Fn(&KclValue) -> T) -> Option<T> {
1790    let mem = cache::read_old_memory().await?;
1791    let value = mem.0.get(name, SourceRange::default()).ok()?;
1792
1793    Some(f(value))
1794}
1795
1796#[cfg(test)]
1797mod tests {
1798    use pretty_assertions::assert_eq;
1799
1800    use super::*;
1801
1802    #[test]
1803    fn test_position_to_char_index_first_line() {
1804        let code = r#"def foo():
1805return 42"#;
1806        let position = Position::new(0, 3);
1807        let index = position_to_char_index(position, code);
1808        assert_eq!(index, 3);
1809    }
1810
1811    #[test]
1812    fn test_position_to_char_index() {
1813        let code = r#"def foo():
1814return 42"#;
1815        let position = Position::new(1, 4);
1816        let index = position_to_char_index(position, code);
1817        assert_eq!(index, 15);
1818    }
1819
1820    #[test]
1821    fn test_position_to_char_index_with_newline() {
1822        let code = r#"def foo():
1823
1824return 42"#;
1825        let position = Position::new(2, 0);
1826        let index = position_to_char_index(position, code);
1827        assert_eq!(index, 12);
1828    }
1829
1830    #[test]
1831    fn test_position_to_char_at_end() {
1832        let code = r#"def foo():
1833return 42"#;
1834
1835        let position = Position::new(1, 8);
1836        let index = position_to_char_index(position, code);
1837        assert_eq!(index, 19);
1838    }
1839}