Skip to main content

kcl_lib/lsp/kcl/
mod.rs

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