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