Skip to main content

kcl_lib/lsp/kcl/
mod.rs

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