diff --git a/Cargo.lock b/Cargo.lock index d8419711..d790ac7e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3879,7 +3879,6 @@ dependencies = [ "indexmap 2.2.6", "insta", "itertools 0.12.1", - "lazy_static", "log", "lsp-types", "once_cell", diff --git a/crates/tinymist-query/Cargo.toml b/crates/tinymist-query/Cargo.toml index 9d620968..706e7c00 100644 --- a/crates/tinymist-query/Cargo.toml +++ b/crates/tinymist-query/Cargo.toml @@ -19,7 +19,6 @@ yaml-rust2.workspace = true biblatex.workspace = true serde_yaml.workspace = true itertools.workspace = true -lazy_static.workspace = true strum.workspace = true log.workspace = true serde.workspace = true diff --git a/crates/tinymist-query/src/completion.rs b/crates/tinymist-query/src/completion.rs index f63c7878..476b3b7f 100644 --- a/crates/tinymist-query/src/completion.rs +++ b/crates/tinymist-query/src/completion.rs @@ -1,4 +1,9 @@ -use lsp_types::CompletionList; +use lsp_types::{ + Command, CompletionItemLabelDetails, CompletionList, CompletionTextEdit, InsertTextFormat, + TextEdit, +}; +use once_cell::sync::Lazy; +use regex::{Captures, Regex}; use crate::{ analysis::{FlowBuiltinType, FlowType}, @@ -8,7 +13,9 @@ use crate::{ StatefulRequest, }; -use self::typst_to_lsp::completion; +pub(crate) type LspCompletion = lsp_types::CompletionItem; +pub(crate) type LspCompletionKind = lsp_types::CompletionItemKind; +pub(crate) type TypstCompletionKind = crate::upstream::CompletionKind; /// The [`textDocument/completion`] request is sent from the client to the /// server to compute completion items at a given cursor position. @@ -201,12 +208,36 @@ impl StatefulRequest for CompletionRequest { replace_range = LspRange::new(lsp_start_position, self.position); } - Some( - completions - .iter() - .map(|typst_completion| completion(typst_completion, replace_range)) - .collect_vec(), - ) + let completions = completions.iter().map(|typst_completion| { + let typst_snippet = typst_completion + .apply + .as_ref() + .unwrap_or(&typst_completion.label); + let lsp_snippet = to_lsp_snippet(typst_snippet); + let text_edit = CompletionTextEdit::Edit(TextEdit::new(replace_range, lsp_snippet)); + + LspCompletion { + label: typst_completion.label.to_string(), + kind: Some(completion_kind(typst_completion.kind.clone())), + detail: typst_completion.detail.as_ref().map(String::from), + sort_text: typst_completion.sort_text.as_ref().map(String::from), + label_details: typst_completion.label_detail.as_ref().map(|e| { + CompletionItemLabelDetails { + detail: None, + description: Some(e.to_string()), + } + }), + text_edit: Some(text_edit), + insert_text_format: Some(InsertTextFormat::SNIPPET), + command: typst_completion.command.as_ref().map(|c| Command { + command: c.to_string(), + ..Default::default() + }), + ..Default::default() + } + }); + + Some(completions.collect_vec()) })?; if let Some(items_rest) = completion_items_rest.as_mut() { @@ -223,15 +254,41 @@ impl StatefulRequest for CompletionRequest { } } +pub(crate) fn completion_kind(typst_completion_kind: TypstCompletionKind) -> LspCompletionKind { + match typst_completion_kind { + TypstCompletionKind::Syntax => LspCompletionKind::SNIPPET, + TypstCompletionKind::Func => LspCompletionKind::FUNCTION, + TypstCompletionKind::Param => LspCompletionKind::VARIABLE, + TypstCompletionKind::Field => LspCompletionKind::FIELD, + TypstCompletionKind::Variable => LspCompletionKind::VARIABLE, + TypstCompletionKind::Constant => LspCompletionKind::CONSTANT, + TypstCompletionKind::Symbol(_) => LspCompletionKind::FIELD, + TypstCompletionKind::Type => LspCompletionKind::CLASS, + TypstCompletionKind::Module => LspCompletionKind::MODULE, + TypstCompletionKind::File => LspCompletionKind::FILE, + TypstCompletionKind::Folder => LspCompletionKind::FOLDER, + } +} + +static TYPST_SNIPPET_PLACEHOLDER_RE: Lazy = + Lazy::new(|| Regex::new(r"\$\{(.*?)\}").unwrap()); + +/// Adds numbering to placeholders in snippets +fn to_lsp_snippet(typst_snippet: &EcoString) -> String { + let mut counter = 1; + let result = + TYPST_SNIPPET_PLACEHOLDER_RE.replace_all(typst_snippet.as_str(), |cap: &Captures| { + let substitution = format!("${{{}:{}}}", counter, &cap[1]); + counter += 1; + substitution + }); + + result.to_string() +} + fn is_arg_like_context(mut matching: &LinkedNode) -> bool { while let Some(parent) = matching.parent() { use SyntaxKind::*; - // if parent.kind() == SyntaxKind::Markup | SyntaxKind::Markup | - // SyntaxKind::Markup { return true; - // } - // if parent.kind() == SyntaxKind::Args { - // return true; - // } // todo: contextual match parent.kind() { diff --git a/crates/tinymist-query/src/lsp_typst_boundary.rs b/crates/tinymist-query/src/lsp_typst_boundary.rs index 84204833..6e3b6ac3 100644 --- a/crates/tinymist-query/src/lsp_typst_boundary.rs +++ b/crates/tinymist-query/src/lsp_typst_boundary.rs @@ -63,11 +63,6 @@ impl From for lsp_types::PositionEncodingKind { } } -pub type LspCompletion = lsp_types::CompletionItem; -pub type LspCompletionKind = lsp_types::CompletionItemKind; -pub type TypstCompletion = crate::upstream::Completion; -pub type TypstCompletionKind = crate::upstream::CompletionKind; - const UNTITLED_ROOT: &str = "/untitled"; static EMPTY_URL: Lazy = Lazy::new(|| Url::parse("file://").unwrap()); @@ -208,15 +203,7 @@ pub mod lsp_to_typst { pub mod typst_to_lsp { - use itertools::Itertools; - use lazy_static::lazy_static; - use lsp_types::{ - Command, CompletionItemLabelDetails, CompletionTextEdit, Documentation, InsertTextFormat, - LanguageString, MarkedString, MarkupContent, MarkupKind, TextEdit, - }; - use regex::{Captures, Regex}; - use typst::diag::EcoString; - use typst::foundations::{CastInfo, Repr}; + use lsp_types::{LanguageString, MarkedString}; use typst::syntax::Source; use super::*; @@ -267,68 +254,6 @@ pub mod typst_to_lsp { LspRange::new(lsp_start, lsp_end) } - pub fn completion_kind(typst_completion_kind: TypstCompletionKind) -> LspCompletionKind { - match typst_completion_kind { - TypstCompletionKind::Syntax => LspCompletionKind::SNIPPET, - TypstCompletionKind::Func => LspCompletionKind::FUNCTION, - TypstCompletionKind::Param => LspCompletionKind::VARIABLE, - TypstCompletionKind::Field => LspCompletionKind::FIELD, - TypstCompletionKind::Variable => LspCompletionKind::VARIABLE, - TypstCompletionKind::Constant => LspCompletionKind::CONSTANT, - TypstCompletionKind::Symbol(_) => LspCompletionKind::FIELD, - TypstCompletionKind::Type => LspCompletionKind::CLASS, - TypstCompletionKind::Module => LspCompletionKind::MODULE, - TypstCompletionKind::File => LspCompletionKind::FILE, - TypstCompletionKind::Folder => LspCompletionKind::FOLDER, - } - } - - lazy_static! { - static ref TYPST_SNIPPET_PLACEHOLDER_RE: Regex = Regex::new(r"\$\{(.*?)\}").unwrap(); - } - - /// Adds numbering to placeholders in snippets - fn snippet(typst_snippet: &EcoString) -> String { - let mut counter = 1; - let result = - TYPST_SNIPPET_PLACEHOLDER_RE.replace_all(typst_snippet.as_str(), |cap: &Captures| { - let substitution = format!("${{{}:{}}}", counter, &cap[1]); - counter += 1; - substitution - }); - - result.to_string() - } - - pub fn completion(typst_completion: &TypstCompletion, lsp_replace: LspRange) -> LspCompletion { - let typst_snippet = typst_completion - .apply - .as_ref() - .unwrap_or(&typst_completion.label); - let lsp_snippet = snippet(typst_snippet); - let text_edit = CompletionTextEdit::Edit(TextEdit::new(lsp_replace, lsp_snippet)); - - LspCompletion { - label: typst_completion.label.to_string(), - kind: Some(completion_kind(typst_completion.kind.clone())), - detail: typst_completion.detail.as_ref().map(String::from), - sort_text: typst_completion.sort_text.as_ref().map(String::from), - label_details: typst_completion.label_detail.as_ref().map(|e| { - CompletionItemLabelDetails { - detail: None, - description: Some(e.to_string()), - } - }), - text_edit: Some(text_edit), - insert_text_format: Some(InsertTextFormat::SNIPPET), - command: typst_completion.command.as_ref().map(|c| Command { - command: c.to_string(), - ..Default::default() - }), - ..Default::default() - } - } - pub fn tooltip(typst_tooltip: &TypstTooltip) -> LspHoverContents { let lsp_marked_string = match typst_tooltip { TypstTooltip::Text(text) => MarkedString::String(text.to_string()), @@ -339,41 +264,6 @@ pub mod typst_to_lsp { }; LspHoverContents::Scalar(lsp_marked_string) } - - pub fn param_info(typst_param_info: &TypstParamInfo) -> LspParamInfo { - LspParamInfo { - label: lsp_types::ParameterLabel::Simple(typst_param_info.name.to_owned()), - documentation: param_info_to_docs(typst_param_info), - } - } - - pub fn param_info_to_label(typst_param_info: &TypstParamInfo) -> String { - format!( - "{}: {}", - typst_param_info.name, - cast_info_to_label(&typst_param_info.input) - ) - } - - fn param_info_to_docs(typst_param_info: &TypstParamInfo) -> Option { - if !typst_param_info.docs.is_empty() { - Some(Documentation::MarkupContent(MarkupContent { - value: typst_param_info.docs.to_owned(), - kind: MarkupKind::Markdown, - })) - } else { - None - } - } - - pub fn cast_info_to_label(cast_info: &CastInfo) -> String { - match cast_info { - CastInfo::Any => "any".to_owned(), - CastInfo::Value(value, _) => value.repr().to_string(), - CastInfo::Type(ty) => ty.to_string(), - CastInfo::Union(options) => options.iter().map(cast_info_to_label).join(" "), - } - } } #[cfg(test)] diff --git a/crates/tinymist-query/src/upstream/complete/ext.rs b/crates/tinymist-query/src/upstream/complete/ext.rs index 02d79fb7..25b5b365 100644 --- a/crates/tinymist-query/src/upstream/complete/ext.rs +++ b/crates/tinymist-query/src/upstream/complete/ext.rs @@ -21,7 +21,7 @@ use crate::syntax::param_index_at_leaf; use crate::upstream::complete::complete_code; use crate::upstream::plain_docs_sentence; -use crate::{prelude::*, typst_to_lsp::completion_kind, LspCompletion}; +use crate::{completion_kind, prelude::*, LspCompletion}; impl<'a, 'w> CompletionContext<'a, 'w> { pub fn world(&self) -> &'w dyn typst::World {