From 282b1d7b4d3db7f6f432d7b1e8b653cc80a8dd27 Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com> Date: Fri, 27 Dec 2024 16:55:07 +0800 Subject: [PATCH] refactor: merge some calculation stuff in completion worker (#1077) --- .../tinymist-query/src/analysis/completion.rs | 101 +++++++++++------- crates/tinymist-query/src/analysis/global.rs | 39 ++++--- crates/tinymist-query/src/completion.rs | 56 +--------- 3 files changed, 87 insertions(+), 109 deletions(-) diff --git a/crates/tinymist-query/src/analysis/completion.rs b/crates/tinymist-query/src/analysis/completion.rs index 00d83aed..695304cc 100644 --- a/crates/tinymist-query/src/analysis/completion.rs +++ b/crates/tinymist-query/src/analysis/completion.rs @@ -33,6 +33,7 @@ use crate::adt::interner::Interned; use crate::analysis::{ analyze_labels, func_signature, BuiltinTy, DynLabel, LocalContext, PathPreference, Ty, }; +use crate::prelude::*; use crate::snippet::{ CompletionCommand, CompletionContextKey, ParsedSnippet, PostfixSnippet, PostfixSnippetScope, PrefixSnippet, DEFAULT_POSTFIX_SNIPPET, DEFAULT_PREFIX_SNIPPET, @@ -46,7 +47,9 @@ use crate::ty::{ DynTypeBounds, Iface, IfaceChecker, InsTy, SigTy, TyCtx, TypeInfo, TypeInterface, TypeVar, }; use crate::upstream::{plain_docs_sentence, summarize_font_family}; -use crate::{prelude::*, LspCompletion, LspCompletionKind}; + +type LspCompletion = lsp_types::CompletionItem; +type LspCompletionKind = lsp_types::CompletionItemKind; /// Tinymist's completion features. #[derive(Default, Debug, Clone, Serialize, Deserialize)] @@ -205,10 +208,12 @@ pub struct CompletionWorker<'a> { pub before: &'a str, /// The text after the cursor. pub after: &'a str, - /// The root node of the source. - pub root: LinkedNode<'a>, /// The leaf node at the cursor. pub leaf: LinkedNode<'a>, + /// The syntax class at the cursor. + pub syntax: Option>, + /// The syntax context at the cursor. + pub syntax_context: Option>, /// The cursor position. pub cursor: usize, /// Whether the completion was explicitly requested. @@ -217,8 +222,6 @@ pub struct CompletionWorker<'a> { pub trigger_character: Option, /// The position from which the completions apply. pub from: usize, - /// The type of the expression before the cursor. - pub from_ty: Option, /// The completions. pub raw_completions: Vec, /// The (lsp_types) completions. @@ -247,6 +250,10 @@ impl<'a> CompletionWorker<'a> { let text = source.text(); let root = LinkedNode::new(source.root()); let leaf = root.leaf_at_compat(cursor)?; + let syntax = classify_syntax(leaf.clone(), cursor); + let syntax_context = classify_context(leaf.clone(), Some(cursor)); + + crate::log_debug_ct!("CompletionWorker: context {leaf:?} -> {syntax_context:#?}"); Some(Self { ctx, document, @@ -254,13 +261,13 @@ impl<'a> CompletionWorker<'a> { text, before: &text[..cursor], after: &text[cursor..], - root, leaf, + syntax, + syntax_context, cursor, trigger_character, explicit, from: cursor, - from_ty: None, incomplete: true, raw_completions: vec![], completions: vec![], @@ -305,6 +312,45 @@ impl<'a> CompletionWorker<'a> { /// Starts the completion process. pub(crate) fn work(mut self) -> Option<(bool, Vec)> { + // Skip if is the let binding item *directly* + if let Some(SyntaxClass::VarAccess(var)) = &self.syntax { + let node = var.node(); + match node.parent_kind() { + // complete the init part of the let binding + Some(SyntaxKind::LetBinding) => { + let parent = node.parent()?; + let parent_init = parent.cast::()?.init()?; + let parent_init = parent.find(parent_init.span())?; + parent_init.find(node.span())?; + } + Some(SyntaxKind::Closure) => { + let parent = node.parent()?; + let parent_body = parent.cast::()?.body(); + let parent_body = parent.find(parent_body.span())?; + parent_body.find(node.span())?; + } + _ => {} + } + } + + // Skip if an error node starts with number (e.g. `1pt`) + if matches!( + self.syntax, + Some(SyntaxClass::Callee(..) | SyntaxClass::VarAccess(..) | SyntaxClass::Normal(..)) + ) && self.leaf.erroneous() + { + let mut chars = self.leaf.text().chars(); + match chars.next() { + Some(ch) if ch.is_numeric() => return None, + Some('.') => { + if matches!(chars.next(), Some(ch) if ch.is_numeric()) { + return None; + } + } + _ => {} + } + } + // Exclude it self from auto completion // e.g. `#let x = (1.);` let self_ty = self.leaf.cast::().and_then(|leaf| { @@ -317,32 +363,25 @@ impl<'a> CompletionWorker<'a> { }; let _ = self.complete_root(); - let ctx = self.ctx; - let offset = self.from; let worker_incomplete = self.incomplete; let mut raw_completions = self.raw_completions; let mut completions_rest = self.completions; - // Todo: remove these repeatedly identified information - let source = self.source.clone(); - let syntax = classify_syntax(self.leaf.clone(), self.cursor); - let cursor = self.cursor; - // Filter and determine range to replace let mut from_ident = None; - let is_callee = matches!(syntax, Some(SyntaxClass::Callee(..))); + let is_callee = matches!(self.syntax, Some(SyntaxClass::Callee(..))); if matches!( - syntax, + self.syntax, Some(SyntaxClass::Callee(..) | SyntaxClass::VarAccess(..)) ) { - let node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?; - if is_ident_like(&node) && node.offset() == offset { + let node = LinkedNode::new(self.source.root()).leaf_at_compat(self.cursor)?; + if is_ident_like(&node) && node.offset() == self.from { from_ident = Some(node); } } let replace_range = if let Some(from_ident) = from_ident { let mut rng = from_ident.range(); - let ident_prefix = source.text()[rng.start..cursor].to_string(); + let ident_prefix = self.source.text()[rng.start..self.cursor].to_string(); raw_completions.retain(|item| { let mut prefix_matcher = item.label.chars(); @@ -360,7 +399,7 @@ impl<'a> CompletionWorker<'a> { }); // if modifying some arguments, we need to truncate and add a comma - if !is_callee && cursor != rng.end && is_arg_like_context(&from_ident) { + if !is_callee && self.cursor != rng.end && is_arg_like_context(&from_ident) { // extend comma for item in raw_completions.iter_mut() { let apply = match &mut item.apply { @@ -377,12 +416,12 @@ impl<'a> CompletionWorker<'a> { } // Truncate - rng.end = cursor; + rng.end = self.cursor; } - ctx.to_lsp_range(rng, &source) + self.ctx.to_lsp_range(rng, &self.source) } else { - ctx.to_lsp_range(offset..cursor, &source) + self.ctx.to_lsp_range(self.from..self.cursor, &self.source) }; let completions = raw_completions.iter().map(|typst_completion| { @@ -441,12 +480,9 @@ impl<'a> CompletionWorker<'a> { return self.complete_imports().then_some(()); } - let syntax_context = classify_context(self.leaf.clone(), Some(self.cursor)); - let syntax = classify_syntax(self.leaf.clone(), self.cursor); - crate::log_debug_ct!("complete_type: pos {:?} -> {syntax_context:#?}", self.leaf); let mut args_node = None; - match syntax_context { + match self.syntax_context.clone() { Some(SyntaxContext::Element { container, .. }) => { if let Some(container) = container.cast::() { for named in container.items() { @@ -525,8 +561,6 @@ impl<'a> CompletionWorker<'a> { | None => {} } - crate::log_debug_ct!("ctx.leaf {:?}", self.leaf); - let ty = self .ctx .post_type_of_node(self.leaf.clone()) @@ -534,16 +568,12 @@ impl<'a> CompletionWorker<'a> { crate::log_debug_ct!("complete_type: {:?} -> ({scope:?}, {ty:#?})", self.leaf); - // if matches!((scope, &ty), (Regular | StringContent, None)) { - // return None; - // } - // adjust the completion position // todo: syntax class seems not being considering `is_ident_like` // todo: merge ident_content_offset and label_content_offset if is_ident_like(&self.leaf) { self.from = self.leaf.offset(); - } else if let Some(offset) = syntax.as_ref().and_then(SyntaxClass::complete_offset) { + } else if let Some(offset) = self.syntax.as_ref().and_then(SyntaxClass::complete_offset) { self.from = offset; } @@ -567,8 +597,6 @@ impl<'a> CompletionWorker<'a> { } let mut completions = std::mem::take(&mut self.raw_completions); - let ty = Some(Ty::from_types(self.seen_types.iter().cloned())); - let from_ty = std::mem::replace(&mut self.from_ty, ty); match mode { InterpretMode::Code => { self.complete_code(); @@ -590,7 +618,6 @@ impl<'a> CompletionWorker<'a> { }, InterpretMode::Comment | InterpretMode::String => {} }; - self.from_ty = from_ty; match scope { Regular | StringContent | ImportList | SetRule => {} diff --git a/crates/tinymist-query/src/analysis/global.rs b/crates/tinymist-query/src/analysis/global.rs index 3a47de32..23bee2fd 100644 --- a/crates/tinymist-query/src/analysis/global.rs +++ b/crates/tinymist-query/src/analysis/global.rs @@ -489,27 +489,38 @@ impl SharedContext { self.analysis.position_encoding } - /// Convert a LSP position to a Typst position. + /// Convert an LSP position to a Typst position. pub fn to_typst_pos(&self, position: LspPosition, src: &Source) -> Option { crate::to_typst_position(position, self.analysis.position_encoding, src) } - /// Convert a Typst offset to a LSP position. + /// Converts an LSP position with some offset. + pub fn to_typst_pos_offset( + &self, + source: &Source, + position: LspPosition, + shift: usize, + ) -> Option { + let offset = self.to_typst_pos(position, source)?; + Some(ceil_char_boundary(source.text(), offset + shift)) + } + + /// Convert a Typst offset to an LSP position. pub fn to_lsp_pos(&self, typst_offset: usize, src: &Source) -> LspPosition { crate::to_lsp_position(typst_offset, self.analysis.position_encoding, src) } - /// Convert a LSP range to a Typst range. + /// Convert an LSP range to a Typst range. pub fn to_typst_range(&self, position: LspRange, src: &Source) -> Option> { crate::to_typst_range(position, self.analysis.position_encoding, src) } - /// Convert a Typst range to a LSP range. + /// Convert a Typst range to an LSP range. pub fn to_lsp_range(&self, position: Range, src: &Source) -> LspRange { crate::to_lsp_range(position, src, self.analysis.position_encoding) } - /// Convert a Typst range to a LSP range. + /// Convert a Typst range to an LSP range. pub fn to_lsp_range_(&self, position: Range, fid: TypstFileId) -> Option { let ext = fid .vpath() @@ -578,23 +589,9 @@ impl SharedContext { position: LspPosition, shift: usize, ) -> Option> { - let (_, syntax) = self.classify_pos_(source, position, shift)?; - syntax - } - - /// Classifies the syntax under position that can be operated on by IDE - /// functionality. - pub fn classify_pos_<'s>( - &self, - source: &'s Source, - position: LspPosition, - shift: usize, - ) -> Option<(usize, Option>)> { - let offset = self.to_typst_pos(position, source)?; - let cursor = ceil_char_boundary(source.text(), offset + shift); - + let cursor = self.to_typst_pos_offset(source, position, shift)?; let node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?; - Some((cursor, classify_syntax(node, cursor))) + classify_syntax(node, cursor) } /// Get the real definition of a compilation. diff --git a/crates/tinymist-query/src/completion.rs b/crates/tinymist-query/src/completion.rs index 81cfb252..88cafb44 100644 --- a/crates/tinymist-query/src/completion.rs +++ b/crates/tinymist-query/src/completion.rs @@ -1,10 +1,6 @@ use lsp_types::CompletionList; -use typst_shim::syntax::LinkedNodeExt; -use crate::{analysis::CompletionWorker, prelude::*, syntax::SyntaxClass, StatefulRequest}; - -pub(crate) type LspCompletion = lsp_types::CompletionItem; -pub(crate) type LspCompletionKind = lsp_types::CompletionItemKind; +use crate::{analysis::CompletionWorker, prelude::*, StatefulRequest}; pub(crate) mod snippet; @@ -57,52 +53,6 @@ impl StatefulRequest for CompletionRequest { return None; } - let doc = doc.as_ref().map(|doc| doc.document.as_ref()); - let source = ctx.source_by_path(&self.path).ok()?; - let (cursor, syntax) = ctx.classify_pos_(&source, self.position, 0)?; - - // Skip if is the let binding item *directly* - if let Some(SyntaxClass::VarAccess(var)) = &syntax { - let node = var.node(); - match node.parent_kind() { - // complete the init part of the let binding - Some(SyntaxKind::LetBinding) => { - let parent = node.parent()?; - let parent_init = parent.cast::()?.init()?; - let parent_init = parent.find(parent_init.span())?; - parent_init.find(node.span())?; - } - Some(SyntaxKind::Closure) => { - let parent = node.parent()?; - let parent_body = parent.cast::()?.body(); - let parent_body = parent.find(parent_body.span())?; - parent_body.find(node.span())?; - } - _ => {} - } - } - - // Skip if an error node starts with number (e.g. `1pt`) - if matches!( - syntax, - Some(SyntaxClass::Callee(..) | SyntaxClass::VarAccess(..) | SyntaxClass::Normal(..)) - ) { - let node = LinkedNode::new(source.root()).leaf_at_compat(cursor)?; - if node.erroneous() { - let mut chars = node.text().chars(); - - match chars.next() { - Some(ch) if ch.is_numeric() => return None, - Some('.') => { - if matches!(chars.next(), Some(ch) if ch.is_numeric()) { - return None; - } - } - _ => {} - } - } - } - // Please see // // FIXME: correctly identify a completion which is triggered @@ -118,6 +68,10 @@ impl StatefulRequest for CompletionRequest { // assume that the completion is not explicit. let explicit = false; + let doc = doc.as_ref().map(|doc| doc.document.as_ref()); + let source = ctx.source_by_path(&self.path).ok()?; + let cursor = ctx.to_typst_pos_offset(&source, self.position, 0)?; + let worker = CompletionWorker::new(ctx, doc, &source, cursor, explicit, self.trigger_character)?;