From 8a71d78a3c40169470fb82c7544c70f23aab6e4f Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin Date: Tue, 19 Nov 2024 00:16:31 +0800 Subject: [PATCH 1/5] feat: UFCS completion on content types --- crates/tinymist-query/src/completion.rs | 1 + .../completion/snaps/test@func_args.typ.snap | 2 +- .../snaps/test@func_builtin_args.typ.snap | 2 +- .../snaps/test@func_with_args.typ.snap | 2 +- .../snaps/test@touying-core-slides.typ-2.snap | 2 +- .../snaps/test@touying-core-slides.typ.snap | 2 +- ...est@touying-utils-cover-with-rect.typ.snap | 2 +- ...t@touying-utils-current-heading.typ-2.snap | 2 +- .../test@touying-utils-markup-text.typ.snap | 2 +- .../tinymist-query/src/upstream/complete.rs | 23 +- .../src/upstream/complete/ext.rs | 315 +++++++++++++----- 11 files changed, 255 insertions(+), 100 deletions(-) diff --git a/crates/tinymist-query/src/completion.rs b/crates/tinymist-query/src/completion.rs index 8638e9628..67b563274 100644 --- a/crates/tinymist-query/src/completion.rs +++ b/crates/tinymist-query/src/completion.rs @@ -242,6 +242,7 @@ impl StatefulRequest for CompletionRequest { } }), text_edit: Some(text_edit), + additional_text_edits: typst_completion.additional_text_edits.clone(), insert_text_format: Some(InsertTextFormat::SNIPPET), commit_characters: typst_completion .commit_char diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@func_args.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@func_args.typ.snap index ff18d1b4b..48f0a6277 100644 --- a/crates/tinymist-query/src/fixtures/completion/snaps/test@func_args.typ.snap +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@func_args.typ.snap @@ -77,7 +77,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_args.typ "labelDetails": { "description": "type" }, - "sortText": "053", + "sortText": "052", "textEdit": { "newText": "content", "range": { diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@func_builtin_args.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@func_builtin_args.typ.snap index 21f9db406..bdd8e4ce6 100644 --- a/crates/tinymist-query/src/fixtures/completion/snaps/test@func_builtin_args.typ.snap +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@func_builtin_args.typ.snap @@ -35,7 +35,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_builtin_args.typ "labelDetails": { "description": "(int, content, gutter: relative) => columns" }, - "sortText": "057", + "sortText": "056", "textEdit": { "newText": "columns(${1:})", "range": { diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@func_with_args.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@func_with_args.typ.snap index da4c927eb..b9803f7f5 100644 --- a/crates/tinymist-query/src/fixtures/completion/snaps/test@func_with_args.typ.snap +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@func_with_args.typ.snap @@ -77,7 +77,7 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_with_args.typ "labelDetails": { "description": "type" }, - "sortText": "053", + "sortText": "052", "textEdit": { "newText": "content", "range": { diff --git a/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-core-slides.typ-2.snap b/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-core-slides.typ-2.snap index 550ce214a..1a6c90dee 100644 --- a/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-core-slides.typ-2.snap +++ b/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-core-slides.typ-2.snap @@ -14,7 +14,7 @@ input_file: crates/tinymist-query/src/fixtures/pkgs/touying-core-slides.typ "labelDetails": { "description": "() => any" }, - "sortText": "051", + "sortText": "050", "textEdit": { "newText": "config-xxx()${1:}", "range": { diff --git a/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-core-slides.typ.snap b/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-core-slides.typ.snap index ea80d711f..8e062fcfa 100644 --- a/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-core-slides.typ.snap +++ b/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-core-slides.typ.snap @@ -56,7 +56,7 @@ input_file: crates/tinymist-query/src/fixtures/pkgs/touying-core-slides.typ "labelDetails": { "description": "(content, gap: length, justify: bool) => repeat" }, - "sortText": "250", + "sortText": "249", "textEdit": { "newText": "repeat[${1:}]", "range": { diff --git a/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-utils-cover-with-rect.typ.snap b/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-utils-cover-with-rect.typ.snap index c1ec150f0..373a28a15 100644 --- a/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-utils-cover-with-rect.typ.snap +++ b/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-utils-cover-with-rect.typ.snap @@ -35,7 +35,7 @@ input_file: crates/tinymist-query/src/fixtures/pkgs/touying-utils-cover-with-rec "labelDetails": { "description": "type" }, - "sortText": "296", + "sortText": "295", "textEdit": { "newText": "stroke(${1:})", "range": { diff --git a/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-utils-current-heading.typ-2.snap b/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-utils-current-heading.typ-2.snap index 168dda0d3..067652027 100644 --- a/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-utils-current-heading.typ-2.snap +++ b/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-utils-current-heading.typ-2.snap @@ -32,7 +32,7 @@ input_file: crates/tinymist-query/src/fixtures/pkgs/touying-utils-current-headin "labelDetails": { "description": "type" }, - "sortText": "134", + "sortText": "133", "textEdit": { "newText": "int(${1:})", "range": { diff --git a/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-utils-markup-text.typ.snap b/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-utils-markup-text.typ.snap index 5f238d825..51053d43f 100644 --- a/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-utils-markup-text.typ.snap +++ b/crates/tinymist-query/src/fixtures/pkgs/snaps/test@touying-utils-markup-text.typ.snap @@ -68,7 +68,7 @@ input_file: crates/tinymist-query/src/fixtures/pkgs/touying-utils-markup-text.ty "labelDetails": { "description": "type" }, - "sortText": "288", + "sortText": "287", "textEdit": { "newText": "str(${1:})", "range": { diff --git a/crates/tinymist-query/src/upstream/complete.rs b/crates/tinymist-query/src/upstream/complete.rs index 9efd51936..7619bab5c 100644 --- a/crates/tinymist-query/src/upstream/complete.rs +++ b/crates/tinymist-query/src/upstream/complete.rs @@ -4,6 +4,7 @@ use std::ops::Range; use ecow::{eco_format, EcoString}; use if_chain::if_chain; +use lsp_types::TextEdit; use serde::{Deserialize, Serialize}; use typst::foundations::{fields_on, format_str, repr, Repr, StyleChain, Styles, Value}; use typst::model::Document; @@ -15,8 +16,7 @@ use unscanny::Scanner; use super::{plain_docs_sentence, summarize_font_family}; use crate::adt::interner::Interned; -use crate::analysis::{analyze_labels, DynLabel, Ty}; -use crate::LocalContext; +use crate::analysis::{analyze_labels, DynLabel, LocalContext, Ty}; mod ext; pub use ext::complete_path; @@ -73,6 +73,10 @@ pub struct Completion { pub apply: Option, /// An optional short description, at most one sentence. pub detail: Option, + /// An optional array of additional text edits that are applied when + /// selecting this completion. Edits must not overlap with the main edit + /// nor with themselves. + pub additional_text_edits: Option>, /// An optional command to run when the completion is selected. pub command: Option<&'static str>, } @@ -382,7 +386,7 @@ fn complete_field_accesses(ctx: &mut CompletionContext) -> bool { if let Some((value, styles)) = ctx.ctx.analyze_expr(&prev).into_iter().next(); then { ctx.from = ctx.cursor; - field_access_completions(ctx, &value, &styles); + field_access_completions(ctx, &prev, &value, &styles); return true; } } @@ -397,7 +401,7 @@ fn complete_field_accesses(ctx: &mut CompletionContext) -> bool { if let Some((value, styles)) = ctx.ctx.analyze_expr(&prev_prev).into_iter().next(); then { ctx.from = ctx.leaf.offset(); - field_access_completions(ctx, &value, &styles); + field_access_completions(ctx,&prev_prev, &value, &styles); return true; } } @@ -406,7 +410,12 @@ fn complete_field_accesses(ctx: &mut CompletionContext) -> bool { } /// Add completions for all fields on a value. -fn field_access_completions(ctx: &mut CompletionContext, value: &Value, styles: &Option) { +fn field_access_completions( + ctx: &mut CompletionContext, + node: &LinkedNode, + value: &Value, + styles: &Option, +) { for (name, value, _) in value.ty().scope().iter() { ctx.value_completion(Some(name.clone()), value, true, None); } @@ -443,11 +452,15 @@ fn field_access_completions(ctx: &mut CompletionContext, value: &Value, styles: }); } } + + ctx.ufcs_completions(node, value); } Value::Content(content) => { for (name, value) in content.fields() { ctx.value_completion(Some(name.into()), &value, false, None); } + + ctx.ufcs_completions(node, value); } Value::Dict(dict) => { for (name, value) in dict.iter() { diff --git a/crates/tinymist-query/src/upstream/complete/ext.rs b/crates/tinymist-query/src/upstream/complete/ext.rs index a9023e0d3..7227ab4e0 100644 --- a/crates/tinymist-query/src/upstream/complete/ext.rs +++ b/crates/tinymist-query/src/upstream/complete/ext.rs @@ -33,17 +33,14 @@ impl<'a> CompletionContext<'a> { !self.seen_fields.insert(field) } - /// Add completions for definitions that are available at the cursor. - /// - /// Filters the global/math scope with the given filter. - pub fn scope_completions_(&mut self, parens: bool, filter: impl Fn(Option<&Value>) -> bool) { - log::debug!("scope_completions: {parens}"); - let Some(fid) = self.root.span().id() else { - return; - }; - let Ok(src) = self.ctx.source_by_id(fid) else { - return; - }; + fn surrounding_syntax(&mut self) -> SurroundingSyntax { + check_surrounding_syntax(&self.leaf) + .or_else(|| check_previous_syntax(&self.leaf)) + .unwrap_or(SurroundingSyntax::Regular) + } + + fn defines(&mut self) -> Option<(Source, Defines)> { + let src = self.ctx.source_by_id(self.root.span().id()?).ok()?; let mut defines = Defines { types: self.ctx.type_check(&src), @@ -84,17 +81,129 @@ impl<'a> CompletionContext<'a> { None }); - enum SurroundingSyntax { - Regular, - Selector, - SetRule, + Some((src, defines)) + } + + pub fn ufcs_completions(&mut self, node: &LinkedNode, value: &Value) { + let _ = value; + let surrounding_syntax = self.surrounding_syntax(); + if !matches!(surrounding_syntax, SurroundingSyntax::Regular) { + return; + } + + let Some((src, defines)) = self.defines() else { + return; + }; + + log::debug!("defines: {:?}", defines.defines.len()); + let mut kind_checker = CompletionKindChecker { + symbols: HashSet::default(), + functions: HashSet::default(), + }; + + let rng = node.range(); + + let is_content_block = node.kind() == SyntaxKind::ContentBlock; + + let lb = if is_content_block { "" } else { "(" }; + let rb = if is_content_block { "" } else { ")" }; + + // we don't check literal type here for faster completion + for (name, ty) in defines.defines { + // todo: filter ty + if name.is_empty() { + continue; + } + + kind_checker.check(&ty); + + if kind_checker.symbols.iter().min().copied().is_some() { + continue; + } + if kind_checker.functions.is_empty() { + continue; + } + + let label_detail = ty.describe().map(From::from).or_else(|| Some("any".into())); + let base = Completion { + kind: CompletionKind::Func, + label_detail, + apply: Some("".into()), + // range: Some(range), + command: self + .trigger_parameter_hints + .then_some("editor.action.triggerParameterHints"), + ..Default::default() + }; + let fn_feat = FnCompletionFeat::default().check(kind_checker.functions.iter()); + + log::debug!("fn_feat: {name} {ty:?} -> {fn_feat:?}"); + + if fn_feat.min_pos() < 1 || !fn_feat.next_arg_is_content { + continue; + } + log::debug!("checked ufcs: {ty:?}"); + if fn_feat.min_pos() == 1 { + let before = TextEdit { + range: self.ctx.to_lsp_range(rng.start..rng.start, &src), + new_text: format!("{name}{lb}"), + }; + let after = TextEdit { + range: self.ctx.to_lsp_range(rng.end..self.from, &src), + new_text: rb.into(), + }; + + self.completions.push(Completion { + label: name.clone(), + additional_text_edits: Some(vec![before, after]), + ..base.clone() + }); + } + if fn_feat.min_pos() > 1 || fn_feat.min_named() > 0 { + let node_content = node.get().clone().into_text(); + let before = TextEdit { + range: self.ctx.to_lsp_range(rng.start..self.from, &src), + new_text: format!("{name}{lb}"), + }; + self.completions.push(Completion { + apply: if is_content_block { + Some(eco_format!("(${{}}){node_content}")) + } else { + Some(eco_format!("${{}}, {node_content})")) + }, + label: eco_format!("{name}("), + additional_text_edits: Some(vec![before]), + ..base.clone() + }); + let before = TextEdit { + range: self.ctx.to_lsp_range(rng.start..rng.start, &src), + new_text: format!("{name}("), + }; + let after = TextEdit { + range: self.ctx.to_lsp_range(rng.end..self.from, &src), + new_text: "".into(), + }; + self.completions.push(Completion { + apply: Some(eco_format!("${{}})")), + label: eco_format!("{name})"), + additional_text_edits: Some(vec![before, after]), + ..base + }); + } } + } + + /// Add completions for definitions that are available at the cursor. + /// + /// Filters the global/math scope with the given filter. + pub fn scope_completions_(&mut self, parens: bool, filter: impl Fn(Option<&Value>) -> bool) { + let Some((_, defines)) = self.defines() else { + return; + }; let defines = defines.defines; - let surrounding_syntax = check_surrounding_syntax(&self.leaf) - .or_else(|| check_previous_syntax(&self.leaf)) - .unwrap_or(SurroundingSyntax::Regular); + let surrounding_syntax = self.surrounding_syntax(); let mut kind_checker = CompletionKindChecker { symbols: HashSet::default(), @@ -142,7 +251,9 @@ impl<'a> CompletionContext<'a> { log::debug!("fn_feat: {name} {ty:?} -> {fn_feat:?}"); - if !fn_feat.zero_args && matches!(surrounding_syntax, SurroundingSyntax::Regular) { + if matches!(surrounding_syntax, SurroundingSyntax::Regular) + && (fn_feat.min_pos() > 0 || fn_feat.min_named() > 0) + { self.completions.push(Completion { label: eco_format!("{}.with", name), apply: Some(eco_format!("{}.with(${{}})", name)), @@ -167,14 +278,14 @@ impl<'a> CompletionContext<'a> { label: name, ..base }); - } else if fn_feat.zero_args { + } else if fn_feat.min_pos() < 1 && !fn_feat.has_rest { self.completions.push(Completion { apply: Some(eco_format!("{}()${{}}", name)), label: name, ..base }); } else { - let apply = if fn_feat.prefer_content_bracket { + let apply = if fn_feat.next_arg_is_content && !fn_feat.has_rest { eco_format!("{name}[${{}}]") } else { eco_format!("{name}(${{}})") @@ -198,71 +309,76 @@ impl<'a> CompletionContext<'a> { ..Completion::default() }); } + } +} - fn check_surrounding_syntax(mut leaf: &LinkedNode) -> Option { - use SurroundingSyntax::*; - let mut met_args = false; - while let Some(parent) = leaf.parent() { - log::debug!( - "check_surrounding_syntax: {:?}::{:?}", - parent.kind(), - leaf.kind() - ); - match parent.kind() { - SyntaxKind::CodeBlock | SyntaxKind::ContentBlock | SyntaxKind::Equation => { - return Some(Regular); - } - SyntaxKind::Named => { - return Some(Regular); - } - SyntaxKind::Args => { - met_args = true; - } - SyntaxKind::SetRule => { - let rule = parent.get().cast::()?; - if met_args || encolsed_by(parent, rule.condition().map(|s| s.span()), leaf) - { - return Some(Regular); - } else { - return Some(SetRule); - } - } - SyntaxKind::ShowRule => { - let rule = parent.get().cast::()?; - if encolsed_by(parent, Some(rule.transform().span()), leaf) { - return Some(Regular); - } else { - return Some(Selector); // query's first argument - } - } - _ => {} - } +enum SurroundingSyntax { + Regular, + Selector, + SetRule, +} - leaf = parent; +fn check_surrounding_syntax(mut leaf: &LinkedNode) -> Option { + use SurroundingSyntax::*; + let mut met_args = false; + while let Some(parent) = leaf.parent() { + log::debug!( + "check_surrounding_syntax: {:?}::{:?}", + parent.kind(), + leaf.kind() + ); + match parent.kind() { + SyntaxKind::CodeBlock | SyntaxKind::ContentBlock | SyntaxKind::Equation => { + return Some(Regular); } - - None - } - - fn check_previous_syntax(leaf: &LinkedNode) -> Option { - let mut leaf = leaf.clone(); - if leaf.kind().is_trivia() { - leaf = leaf.prev_sibling()?; + SyntaxKind::Named => { + return Some(Regular); } - if matches!(leaf.kind(), SyntaxKind::ShowRule | SyntaxKind::SetRule) { - return check_surrounding_syntax(&leaf.rightmost_leaf()?); + SyntaxKind::Args => { + met_args = true; } - - if matches!(leaf.kind(), SyntaxKind::Show) { - return Some(SurroundingSyntax::Selector); + SyntaxKind::SetRule => { + let rule = parent.get().cast::()?; + if met_args || encolsed_by(parent, rule.condition().map(|s| s.span()), leaf) { + return Some(Regular); + } else { + return Some(SetRule); + } } - if matches!(leaf.kind(), SyntaxKind::Set) { - return Some(SurroundingSyntax::SetRule); + SyntaxKind::ShowRule => { + let rule = parent.get().cast::()?; + if encolsed_by(parent, Some(rule.transform().span()), leaf) { + return Some(Regular); + } else { + return Some(Selector); // query's first argument + } } - - None + _ => {} } + + leaf = parent; + } + + None +} + +fn check_previous_syntax(leaf: &LinkedNode) -> Option { + let mut leaf = leaf.clone(); + if leaf.kind().is_trivia() { + leaf = leaf.prev_sibling()?; + } + if matches!(leaf.kind(), SyntaxKind::ShowRule | SyntaxKind::SetRule) { + return check_surrounding_syntax(&leaf.rightmost_leaf()?); + } + + if matches!(leaf.kind(), SyntaxKind::Show) { + return Some(SurroundingSyntax::Selector); } + if matches!(leaf.kind(), SyntaxKind::Set) { + return Some(SurroundingSyntax::SetRule); + } + + None } #[derive(BindTyCtx)] @@ -396,8 +512,10 @@ impl CompletionKindChecker { #[derive(Default, Debug)] struct FnCompletionFeat { - zero_args: bool, - prefer_content_bracket: bool, + min_pos: Option, + min_named: Option, + has_rest: bool, + next_arg_is_content: bool, is_element: bool, } @@ -410,10 +528,20 @@ impl FnCompletionFeat { self } + fn min_pos(&self) -> usize { + self.min_pos.unwrap_or_default() + } + + fn min_named(&self) -> usize { + self.min_named.unwrap_or_default() + } + fn check_one(&mut self, ty: &Ty, pos: usize) { match ty { Ty::Value(val) => match &val.val { - Value::Type(..) => {} + Value::Type(ty) => { + self.check_one(&Ty::Builtin(BuiltinTy::Type(*ty)), pos); + } Value::Func(func) => { if func.element().is_some() { self.is_element = true; @@ -429,10 +557,17 @@ impl FnCompletionFeat { } Ty::Builtin(BuiltinTy::Element(func)) => { self.is_element = true; - let sig = (*func).into(); - let sig = func_signature(sig).type_sig(); + let func = (*func).into(); + let sig = func_signature(func).type_sig(); self.check_sig(&sig, pos); } + Ty::Builtin(BuiltinTy::Type(ty)) => { + let func = ty.constructor().ok(); + if let Some(func) = func { + let sig = func_signature(func).type_sig(); + self.check_sig(&sig, pos); + } + } Ty::Builtin(BuiltinTy::TypeType(..)) => {} _ => panic!("FnCompletionFeat check_one {ty:?}"), } @@ -441,11 +576,17 @@ impl FnCompletionFeat { // todo: sig is element fn check_sig(&mut self, sig: &SigTy, idx: usize) { let pos_size = sig.positional_params().len(); - let prefer_content_bracket = - sig.rest_param().is_none() && sig.pos(idx).map_or(false, |ty| ty.is_content(&())); - self.prefer_content_bracket = self.prefer_content_bracket || prefer_content_bracket; + self.has_rest = self.has_rest || sig.rest_param().is_some(); + self.next_arg_is_content = + self.next_arg_is_content || sig.pos(idx).map_or(false, |ty| ty.is_content(&())); let name_size = sig.named_params().len(); - self.zero_args = pos_size <= idx && name_size == 0; + let left_pos = pos_size.saturating_sub(idx); + self.min_pos = self + .min_pos + .map_or(Some(left_pos), |v| Some(v.min(left_pos))); + self.min_named = self + .min_named + .map_or(Some(name_size), |v| Some(v.min(name_size))); } } @@ -521,7 +662,7 @@ pub fn ty_to_completion_kind(ty: &Ty) -> CompletionKind { Ty::Func(..) | Ty::With(..) => CompletionKind::Func, Ty::Any => CompletionKind::Variable, Ty::Builtin(BuiltinTy::Module(..)) => CompletionKind::Module, - Ty::Builtin(BuiltinTy::TypeType(..)) => CompletionKind::Type, + Ty::Builtin(BuiltinTy::Type(..) | BuiltinTy::TypeType(..)) => CompletionKind::Type, Ty::Builtin(..) => CompletionKind::Variable, Ty::Let(l) => l .ubs From 084c0dcd1d386e25cdaf2541cdc09b7cb35a423e Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin Date: Tue, 19 Nov 2024 00:32:29 +0800 Subject: [PATCH 2/5] dev: cleanup panics --- .../src/upstream/complete/ext.rs | 220 +++++++++++++----- 1 file changed, 165 insertions(+), 55 deletions(-) diff --git a/crates/tinymist-query/src/upstream/complete/ext.rs b/crates/tinymist-query/src/upstream/complete/ext.rs index 7227ab4e0..be3b5f097 100644 --- a/crates/tinymist-query/src/upstream/complete/ext.rs +++ b/crates/tinymist-query/src/upstream/complete/ext.rs @@ -504,8 +504,21 @@ impl CompletionKindChecker { self.check(ty); } } - Ty::Any | Ty::Builtin(..) => {} - _ => panic!("check kind {ty:?}"), + Ty::Any + | Ty::Builtin(..) + | Ty::Boolean(..) + | Ty::Param(..) + | Ty::Union(..) + | Ty::Var(..) + | Ty::Dict(..) + | Ty::Array(..) + | Ty::Tuple(..) + | Ty::Args(..) + | Ty::Pattern(..) + | Ty::Select(..) + | Ty::Unary(..) + | Ty::Binary(..) + | Ty::If(..) => {} } } } @@ -549,27 +562,101 @@ impl FnCompletionFeat { let sig = func_signature(func.clone()).type_sig(); self.check_sig(&sig, pos); } - _ => panic!("FnCompletionFeat check_one {val:?}"), + Value::None + | Value::Auto + | Value::Bool(_) + | Value::Int(_) + | Value::Float(..) + | Value::Length(..) + | Value::Angle(..) + | Value::Ratio(..) + | Value::Relative(..) + | Value::Fraction(..) + | Value::Color(..) + | Value::Gradient(..) + | Value::Pattern(..) + | Value::Symbol(..) + | Value::Version(..) + | Value::Str(..) + | Value::Bytes(..) + | Value::Label(..) + | Value::Datetime(..) + | Value::Decimal(..) + | Value::Duration(..) + | Value::Content(..) + | Value::Styles(..) + | Value::Array(..) + | Value::Dict(..) + | Value::Args(..) + | Value::Module(..) + | Value::Plugin(..) + | Value::Dyn(..) => {} }, Ty::Func(sig) => self.check_sig(sig, pos), Ty::With(w) => { self.check_one(&w.sig, pos + w.with.positional_params().len()); } - Ty::Builtin(BuiltinTy::Element(func)) => { - self.is_element = true; - let func = (*func).into(); - let sig = func_signature(func).type_sig(); - self.check_sig(&sig, pos); - } - Ty::Builtin(BuiltinTy::Type(ty)) => { - let func = ty.constructor().ok(); - if let Some(func) = func { + Ty::Builtin(b) => match b { + BuiltinTy::Element(func) => { + self.is_element = true; + let func = (*func).into(); let sig = func_signature(func).type_sig(); self.check_sig(&sig, pos); } - } - Ty::Builtin(BuiltinTy::TypeType(..)) => {} - _ => panic!("FnCompletionFeat check_one {ty:?}"), + BuiltinTy::Type(ty) => { + let func = ty.constructor().ok(); + if let Some(func) = func { + let sig = func_signature(func).type_sig(); + self.check_sig(&sig, pos); + } + } + BuiltinTy::TypeType(..) => {} + BuiltinTy::Clause + | BuiltinTy::Undef + | BuiltinTy::Content + | BuiltinTy::Space + | BuiltinTy::None + | BuiltinTy::Break + | BuiltinTy::Continue + | BuiltinTy::Infer + | BuiltinTy::FlowNone + | BuiltinTy::Auto + | BuiltinTy::Args + | BuiltinTy::Color + | BuiltinTy::TextSize + | BuiltinTy::TextFont + | BuiltinTy::TextLang + | BuiltinTy::TextRegion + | BuiltinTy::Label + | BuiltinTy::CiteLabel + | BuiltinTy::RefLabel + | BuiltinTy::Dir + | BuiltinTy::Length + | BuiltinTy::Float + | BuiltinTy::Stroke + | BuiltinTy::Margin + | BuiltinTy::Inset + | BuiltinTy::Outset + | BuiltinTy::Radius + | BuiltinTy::Tag(..) + | BuiltinTy::Module(..) + | BuiltinTy::Path(..) => {} + }, + Ty::Any + | Ty::Boolean(..) + | Ty::Param(..) + | Ty::Union(..) + | Ty::Let(..) + | Ty::Var(..) + | Ty::Dict(..) + | Ty::Array(..) + | Ty::Tuple(..) + | Ty::Args(..) + | Ty::Pattern(..) + | Ty::Select(..) + | Ty::Unary(..) + | Ty::Binary(..) + | Ty::If(..) => {} } } @@ -646,8 +733,7 @@ fn sort_and_explicit_code_completion(ctx: &mut CompletionContext) { } log::debug!( - "sort_and_explicit_code_completion after: {:#?} {:#?}", - completions, + "sort_and_explicit_code_completion after: {completions:#?} {:#?}", ctx.completions ); @@ -661,56 +747,80 @@ pub fn ty_to_completion_kind(ty: &Ty) -> CompletionKind { Ty::Value(ty) => value_to_completion_kind(&ty.val), Ty::Func(..) | Ty::With(..) => CompletionKind::Func, Ty::Any => CompletionKind::Variable, - Ty::Builtin(BuiltinTy::Module(..)) => CompletionKind::Module, - Ty::Builtin(BuiltinTy::Type(..) | BuiltinTy::TypeType(..)) => CompletionKind::Type, - Ty::Builtin(..) => CompletionKind::Variable, - Ty::Let(l) => l - .ubs - .iter() - .chain(l.lbs.iter()) - .fold(None, |acc, ty| match acc { - Some(CompletionKind::Variable) => Some(CompletionKind::Variable), - Some(acc) => { - let kind = ty_to_completion_kind(ty); - if acc == kind { - Some(acc) - } else { - Some(CompletionKind::Variable) - } - } - None => Some(ty_to_completion_kind(ty)), - }) - .unwrap_or(CompletionKind::Variable), - _ => panic!("ty_to_completion_kind {ty:?}"), + Ty::Builtin(b) => match b { + BuiltinTy::Module(..) => CompletionKind::Module, + BuiltinTy::Type(..) | BuiltinTy::TypeType(..) => CompletionKind::Type, + _ => CompletionKind::Variable, + }, + Ty::Let(l) => fold_ty_kind(l.ubs.iter().chain(l.lbs.iter())), + Ty::Union(u) => fold_ty_kind(u.iter()), + Ty::Boolean(..) + | Ty::Param(..) + | Ty::Var(..) + | Ty::Dict(..) + | Ty::Array(..) + | Ty::Tuple(..) + | Ty::Args(..) + | Ty::Pattern(..) + | Ty::Select(..) + | Ty::Unary(..) + | Ty::Binary(..) + | Ty::If(..) => CompletionKind::Constant, } } +fn fold_ty_kind<'a>(tys: impl Iterator) -> CompletionKind { + tys.fold(None, |acc, ty| match acc { + Some(CompletionKind::Variable) => Some(CompletionKind::Variable), + Some(acc) => { + let kind = ty_to_completion_kind(ty); + if acc == kind { + Some(acc) + } else { + Some(CompletionKind::Variable) + } + } + None => Some(ty_to_completion_kind(ty)), + }) + .unwrap_or(CompletionKind::Variable) +} + pub fn value_to_completion_kind(value: &Value) -> CompletionKind { match value { Value::Func(..) => CompletionKind::Func, - Value::Module(..) => CompletionKind::Module, + Value::Plugin(..) | Value::Module(..) => CompletionKind::Module, Value::Type(..) => CompletionKind::Type, Value::Symbol(s) => CompletionKind::Symbol(s.get()), - _ => CompletionKind::Variable, + Value::None + | Value::Auto + | Value::Bool(..) + | Value::Int(..) + | Value::Float(..) + | Value::Length(..) + | Value::Angle(..) + | Value::Ratio(..) + | Value::Relative(..) + | Value::Fraction(..) + | Value::Color(..) + | Value::Gradient(..) + | Value::Pattern(..) + | Value::Version(..) + | Value::Str(..) + | Value::Bytes(..) + | Value::Label(..) + | Value::Datetime(..) + | Value::Decimal(..) + | Value::Duration(..) + | Value::Content(..) + | Value::Styles(..) + | Value::Array(..) + | Value::Dict(..) + | Value::Args(..) + | Value::Dyn(..) => CompletionKind::Variable, } } -// if ctx.before.ends_with(',') { -// ctx.enrich(" ", ""); -// } - // if param.attrs.named { -// let compl = Completion { -// kind: CompletionKind::Field, -// label: param.name.as_ref().into(), -// apply: Some(eco_format!("{}: ${{}}", param.name)), -// detail: docs(), -// label_detail: None, -// command: ctx -// .trigger_named_completion -// .then_some("tinymist.triggerNamedCompletion"), -// ..Completion::default() -// }; // match param.ty { // Ty::Builtin(BuiltinTy::TextSize) => { // for size_template in &[ From 34a5fa336009d51e475e23b2bcb3a1e4bd56e5a0 Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin Date: Tue, 19 Nov 2024 10:43:59 +0800 Subject: [PATCH 3/5] feat: add configuration about postfix completion --- crates/tinymist-query/src/analysis/global.rs | 4 +- crates/tinymist-query/src/lib.rs | 2 +- .../tinymist-query/src/upstream/complete.rs | 2 +- .../src/upstream/complete/ext.rs | 52 ++++++++++++++++++- crates/tinymist/src/actor/mod.rs | 3 +- crates/tinymist/src/init.rs | 36 +++++++------ editors/neovim/Configuration.md | 28 ++++++++++ editors/vscode/Configuration.md | 28 ++++++++++ editors/vscode/package.json | 24 +++++++++ editors/vscode/scripts/config-man.cjs | 3 +- 10 files changed, 160 insertions(+), 22 deletions(-) diff --git a/crates/tinymist-query/src/analysis/global.rs b/crates/tinymist-query/src/analysis/global.rs index 0c2efbbf7..f565252e5 100644 --- a/crates/tinymist-query/src/analysis/global.rs +++ b/crates/tinymist-query/src/analysis/global.rs @@ -36,7 +36,7 @@ use crate::syntax::{ scan_workspace_files, Decl, DefKind, DerefTarget, ExprInfo, ExprRoute, LexicalScope, ModuleDependency, }; -use crate::upstream::{tooltip_, Tooltip}; +use crate::upstream::{tooltip_, CompletionFeat, Tooltip}; use crate::{ lsp_to_typst, typst_to_lsp, ColorTheme, CompilerQueryRequest, LspPosition, LspRange, LspWorldExt, PositionEncoding, TypstRange, VersionedDocument, @@ -55,6 +55,8 @@ pub struct Analysis { pub allow_multiline_token: bool, /// Whether to remove html from markup content in responses. pub remove_html: bool, + /// Tinymist's completion features. + pub completion_feat: CompletionFeat, /// The editor's color theme. pub color_theme: ColorTheme, /// The periscope provider. diff --git a/crates/tinymist-query/src/lib.rs b/crates/tinymist-query/src/lib.rs index f038346b4..65f843482 100644 --- a/crates/tinymist-query/src/lib.rs +++ b/crates/tinymist-query/src/lib.rs @@ -16,7 +16,7 @@ pub mod ty; mod upstream; pub use analysis::{LocalContext, LocalContextGuard, LspWorldExt}; -pub use upstream::with_vm; +pub use upstream::{with_vm, CompletionFeat}; mod diagnostics; pub use diagnostics::*; diff --git a/crates/tinymist-query/src/upstream/complete.rs b/crates/tinymist-query/src/upstream/complete.rs index 7619bab5c..828364288 100644 --- a/crates/tinymist-query/src/upstream/complete.rs +++ b/crates/tinymist-query/src/upstream/complete.rs @@ -19,8 +19,8 @@ use crate::adt::interner::Interned; use crate::analysis::{analyze_labels, DynLabel, LocalContext, Ty}; mod ext; -pub use ext::complete_path; use ext::*; +pub use ext::{complete_path, CompletionFeat}; /// Autocomplete a cursor position in a source file. /// diff --git a/crates/tinymist-query/src/upstream/complete/ext.rs b/crates/tinymist-query/src/upstream/complete/ext.rs index be3b5f097..adfe6c830 100644 --- a/crates/tinymist-query/src/upstream/complete/ext.rs +++ b/crates/tinymist-query/src/upstream/complete/ext.rs @@ -4,6 +4,7 @@ use ecow::{eco_format, EcoString}; use hashbrown::HashSet; use lsp_types::{CompletionItem, CompletionTextEdit, InsertTextFormat, TextEdit}; use reflexo::path::unix_slash; +use serde::{Deserialize, Serialize}; use tinymist_derive::BindTyCtx; use tinymist_world::LspWorld; use typst::foundations::{AutoValue, Func, Label, NoneValue, Scope, Type, Value}; @@ -20,6 +21,46 @@ use crate::upstream::complete::complete_code; use crate::{completion_kind, prelude::*, LspCompletion}; +/// Tinymist's completion features. +#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CompletionFeat { + /// Whether to enable postfix completion. + pub postfix: bool, + /// Whether to enable ufcs completion. + pub postfix_ufcs: bool, + /// Whether to enable ufcs completion (left variant). + pub postfix_ufcs_left: bool, + /// Whether to enable ufcs completion (right variant). + pub postfix_ufcs_right: bool, +} + +impl Default for CompletionFeat { + fn default() -> Self { + Self { + postfix: true, + postfix_ufcs: true, + postfix_ufcs_left: true, + postfix_ufcs_right: true, + } + } +} + +impl CompletionFeat { + pub(crate) fn any_ufcs(&self) -> bool { + self.ufcs() || self.ufcs_left() || self.ufcs_right() + } + pub(crate) fn ufcs(&self) -> bool { + self.postfix && self.postfix_ufcs + } + pub(crate) fn ufcs_left(&self) -> bool { + self.postfix && self.postfix_ufcs_left + } + pub(crate) fn ufcs_right(&self) -> bool { + self.postfix && self.postfix_ufcs_right + } +} + impl<'a> CompletionContext<'a> { pub fn world(&self) -> &LspWorld { self.ctx.world() @@ -85,6 +126,10 @@ impl<'a> CompletionContext<'a> { } pub fn ufcs_completions(&mut self, node: &LinkedNode, value: &Value) { + if !self.ctx.analysis.completion_feat.any_ufcs() { + return; + } + let _ = value; let surrounding_syntax = self.surrounding_syntax(); if !matches!(surrounding_syntax, SurroundingSyntax::Regular) { @@ -143,7 +188,7 @@ impl<'a> CompletionContext<'a> { continue; } log::debug!("checked ufcs: {ty:?}"); - if fn_feat.min_pos() == 1 { + if self.ctx.analysis.completion_feat.ufcs() && fn_feat.min_pos() == 1 { let before = TextEdit { range: self.ctx.to_lsp_range(rng.start..rng.start, &src), new_text: format!("{name}{lb}"), @@ -159,7 +204,8 @@ impl<'a> CompletionContext<'a> { ..base.clone() }); } - if fn_feat.min_pos() > 1 || fn_feat.min_named() > 0 { + let more_args = fn_feat.min_pos() > 1 || fn_feat.min_named() > 0; + if self.ctx.analysis.completion_feat.ufcs_left() && more_args { let node_content = node.get().clone().into_text(); let before = TextEdit { range: self.ctx.to_lsp_range(rng.start..self.from, &src), @@ -175,6 +221,8 @@ impl<'a> CompletionContext<'a> { additional_text_edits: Some(vec![before]), ..base.clone() }); + } + if self.ctx.analysis.completion_feat.ufcs_right() && more_args { let before = TextEdit { range: self.ctx.to_lsp_range(rng.start..rng.start, &src), new_text: format!("{name}("), diff --git a/crates/tinymist/src/actor/mod.rs b/crates/tinymist/src/actor/mod.rs index f9be4f47c..68e0239c7 100644 --- a/crates/tinymist/src/actor/mod.rs +++ b/crates/tinymist/src/actor/mod.rs @@ -109,7 +109,8 @@ impl LanguageState { position_encoding: const_config.position_encoding, allow_overlapping_token: const_config.tokens_overlapping_token_support, allow_multiline_token: const_config.tokens_multiline_token_support, - remove_html: self.config.remove_html, + remove_html: !self.config.support_html_in_markdown, + completion_feat: self.config.completion, color_theme: match self.compile_config().color_theme.as_deref() { Some("dark") => tinymist_query::ColorTheme::Dark, _ => tinymist_query::ColorTheme::Light, diff --git a/crates/tinymist/src/init.rs b/crates/tinymist/src/init.rs index 44d1a36bd..dbb02cf0d 100644 --- a/crates/tinymist/src/init.rs +++ b/crates/tinymist/src/init.rs @@ -15,7 +15,7 @@ use serde_json::{json, Map, Value as JsonValue}; use strum::IntoEnumIterator; use task::FormatUserConfig; use tinymist_query::analysis::{Modifier, TokenType}; -use tinymist_query::PositionEncoding; +use tinymist_query::{CompletionFeat, PositionEncoding}; use tinymist_render::PeriscopeArgs; use typst::foundations::IntoValue; use typst::syntax::{FileId, VirtualPath}; @@ -268,6 +268,7 @@ const CONFIG_ITEMS: &[&str] = &[ "semanticTokens", "formatterMode", "formatterPrintWidth", + "completion", "fontPaths", "systemFonts", "typstExtraArgs", @@ -299,7 +300,9 @@ pub struct Config { /// Whether to trigger parameter hint, a.k.a. signature help. pub trigger_parameter_hints: bool, /// Whether to remove html from markup content in responses. - pub remove_html: bool, + pub support_html_in_markdown: bool, + /// Tinymist's completion features. + pub completion: CompletionFeat, } impl Config { @@ -357,22 +360,25 @@ impl Config { /// # Errors /// Errors if the update is invalid. pub fn update_by_map(&mut self, update: &Map) -> anyhow::Result<()> { - macro_rules! deser_or_default { - ($key:expr, $ty:ty) => { - try_or_default(|| <$ty>::deserialize(update.get($key)?).ok()) + macro_rules! assign_config { + ($( $field_path:ident ).+ := $bind:literal?: $ty:ty) => { + let v = try_(|| <$ty>::deserialize(update.get($bind)?).ok()); + self.$($field_path).+ = v.unwrap_or_default(); + }; + ($( $field_path:ident ).+ := $bind:literal: $ty:ty = $default_value:expr) => { + let v = try_(|| <$ty>::deserialize(update.get($bind)?).ok()); + self.$($field_path).+ = v.unwrap_or_else(|| $default_value); }; } - try_(|| SemanticTokensMode::deserialize(update.get("semanticTokens")?).ok()) - .inspect(|v| self.semantic_tokens = *v); - try_(|| FormatterMode::deserialize(update.get("formatterMode")?).ok()) - .inspect(|v| self.formatter_mode = *v); - try_(|| u32::deserialize(update.get("formatterPrintWidth")?).ok()) - .inspect(|v| self.formatter_print_width = Some(*v)); - self.trigger_suggest = deser_or_default!("triggerSuggest", bool); - self.trigger_parameter_hints = deser_or_default!("triggerParameterHints", bool); - self.trigger_named_completion = deser_or_default!("triggerNamedCompletion", bool); - self.remove_html = !deser_or_default!("supportHtmlInMarkdown", bool); + assign_config!(semantic_tokens := "semanticTokens"?: SemanticTokensMode); + assign_config!(formatter_mode := "formatterMode"?: FormatterMode); + assign_config!(formatter_print_width := "formatterPrintWidth"?: Option); + assign_config!(trigger_suggest := "triggerSuggest"?: bool); + assign_config!(trigger_named_completion := "triggerNamedCompletion"?: bool); + assign_config!(trigger_parameter_hints := "triggerParameterHints"?: bool); + assign_config!(support_html_in_markdown := "supportHtmlInMarkdown"?: bool); + assign_config!(completion := "completion"?: CompletionFeat); self.compile.update_by_map(update)?; self.compile.validate() } diff --git a/editors/neovim/Configuration.md b/editors/neovim/Configuration.md index 41fc8663d..28da87497 100644 --- a/editors/neovim/Configuration.md +++ b/editors/neovim/Configuration.md @@ -81,3 +81,31 @@ Set the print width for the formatter, which is a **soft limit** of characters p - **Type**: `number` - **Default**: `120` + +## `completion.postfix` + +Whether to enable postfix code completion. For example, `[A].box|` will be completed to `box[A]|`. Hint: Restarting the editor is required to change this setting. + +- **Type**: `boolean` +- **Default**: `true` + +## `completion.postfixUfcs` + +Whether to enable UFCS-style completion. For example, `[A].box|` will be completed to `box[A]|`. Hint: Restarting the editor is required to change this setting. + +- **Type**: `boolean` +- **Default**: `true` + +## `completion.postfixUfcsLeft` + +Whether to enable left-variant UFCS-style completion. For example, `[A].table|` will be completed to `table(|)[A]`. Hint: Restarting the editor is required to change this setting. + +- **Type**: `boolean` +- **Default**: `true` + +## `completion.postfixUfcsRight` + +Whether to enable right-variant UFCS-style completion. For example, `[A].table|` will be completed to `table([A], |)`. Hint: Restarting the editor is required to change this setting. + +- **Type**: `boolean` +- **Default**: `true` diff --git a/editors/vscode/Configuration.md b/editors/vscode/Configuration.md index 8ecb6a770..a59a598a4 100644 --- a/editors/vscode/Configuration.md +++ b/editors/vscode/Configuration.md @@ -138,6 +138,34 @@ Whether to handle drag-and-drop of resources into the editing typst document. No - `disable` - **Default**: `"enable"` +## `tinymist.completion.postfix` + +Whether to enable postfix code completion. For example, `[A].box|` will be completed to `box[A]|`. Hint: Restarting the editor is required to change this setting. + +- **Type**: `boolean` +- **Default**: `true` + +## `tinymist.completion.postfixUfcs` + +Whether to enable UFCS-style completion. For example, `[A].box|` will be completed to `box[A]|`. Hint: Restarting the editor is required to change this setting. + +- **Type**: `boolean` +- **Default**: `true` + +## `tinymist.completion.postfixUfcsLeft` + +Whether to enable left-variant UFCS-style completion. For example, `[A].table|` will be completed to `table(|)[A]`. Hint: Restarting the editor is required to change this setting. + +- **Type**: `boolean` +- **Default**: `true` + +## `tinymist.completion.postfixUfcsRight` + +Whether to enable right-variant UFCS-style completion. For example, `[A].table|` will be completed to `table([A], |)`. Hint: Restarting the editor is required to change this setting. + +- **Type**: `boolean` +- **Default**: `true` + ## `tinymist.previewFeature` Enable or disable preview features of Typst. Note: restarting the editor is required to change this setting. diff --git a/editors/vscode/package.json b/editors/vscode/package.json index 76c16a3a3..a36c67bbb 100644 --- a/editors/vscode/package.json +++ b/editors/vscode/package.json @@ -446,6 +446,30 @@ "disable" ] }, + "tinymist.completion.postfix": { + "title": "Enable Postfix Code Completion", + "description": "Whether to enable postfix code completion. For example, `[A].box|` will be completed to `box[A]|`. Hint: Restarting the editor is required to change this setting.", + "type": "boolean", + "default": true + }, + "tinymist.completion.postfixUfcs": { + "title": "Completion: Convert Field Access to Call", + "description": "Whether to enable UFCS-style completion. For example, `[A].box|` will be completed to `box[A]|`. Hint: Restarting the editor is required to change this setting.", + "type": "boolean", + "default": true + }, + "tinymist.completion.postfixUfcsLeft": { + "title": "Completion: Convert Field Access to Call (Left Variant)", + "description": "Whether to enable left-variant UFCS-style completion. For example, `[A].table|` will be completed to `table(|)[A]`. Hint: Restarting the editor is required to change this setting.", + "type": "boolean", + "default": true + }, + "tinymist.completion.postfixUfcsRight": { + "title": "Completion: Convert Field Access to Call (Right Variant)", + "description": "Whether to enable right-variant UFCS-style completion. For example, `[A].table|` will be completed to `table([A], |)`. Hint: Restarting the editor is required to change this setting.", + "type": "boolean", + "default": true + }, "tinymist.previewFeature": { "title": "Enable preview features", "description": "Enable or disable preview features of Typst. Note: restarting the editor is required to change this setting.", diff --git a/editors/vscode/scripts/config-man.cjs b/editors/vscode/scripts/config-man.cjs index 37a4e7639..870982575 100644 --- a/editors/vscode/scripts/config-man.cjs +++ b/editors/vscode/scripts/config-man.cjs @@ -59,7 +59,8 @@ const serverSideKeys = (() => { } return strings.map((x) => `tinymist.${x}`); })(); -const isServerSideConfig = (key) => serverSideKeys.includes(key); +const isServerSideConfig = (key) => serverSideKeys.includes(key) || serverSideKeys + .some((serverSideKey) => key.startsWith(`${serverSideKey}.`)); const configMd = (editor, prefix) => Object.keys(config) .map((key) => { From 535774f58c54aab282e3cd4a472e8b4e536e9bb7 Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin Date: Tue, 19 Nov 2024 11:04:41 +0800 Subject: [PATCH 4/5] test: update snapshot --- tests/e2e/main.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/e2e/main.rs b/tests/e2e/main.rs index 878203ab6..d8d93abdd 100644 --- a/tests/e2e/main.rs +++ b/tests/e2e/main.rs @@ -374,7 +374,7 @@ fn e2e() { }); let hash = replay_log(&tinymist_binary, &root.join("neovim")); - insta::assert_snapshot!(hash, @"siphash128_13:9bbc0892ae5974b0f43f50ef5a61ce2"); + insta::assert_snapshot!(hash, @"siphash128_13:1739b86d5e2de99b19db308496ff94ae"); } { @@ -385,7 +385,7 @@ fn e2e() { }); let hash = replay_log(&tinymist_binary, &root.join("vscode")); - insta::assert_snapshot!(hash, @"siphash128_13:164530d2511ec0c6da2bcad239727add"); + insta::assert_snapshot!(hash, @"siphash128_13:360f6d60de40f590e63ebf23521e3d50"); } } From 09a45b11148fe6538b39c51922c86159cd402d7f Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin Date: Tue, 19 Nov 2024 11:18:20 +0800 Subject: [PATCH 5/5] fix: lazily determine default values --- .../src/upstream/complete/ext.rs | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/crates/tinymist-query/src/upstream/complete/ext.rs b/crates/tinymist-query/src/upstream/complete/ext.rs index adfe6c830..580756f7a 100644 --- a/crates/tinymist-query/src/upstream/complete/ext.rs +++ b/crates/tinymist-query/src/upstream/complete/ext.rs @@ -22,28 +22,17 @@ use crate::upstream::complete::complete_code; use crate::{completion_kind, prelude::*, LspCompletion}; /// Tinymist's completion features. -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[derive(Default, Debug, Clone, Copy, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CompletionFeat { /// Whether to enable postfix completion. - pub postfix: bool, + pub postfix: Option, /// Whether to enable ufcs completion. - pub postfix_ufcs: bool, + pub postfix_ufcs: Option, /// Whether to enable ufcs completion (left variant). - pub postfix_ufcs_left: bool, + pub postfix_ufcs_left: Option, /// Whether to enable ufcs completion (right variant). - pub postfix_ufcs_right: bool, -} - -impl Default for CompletionFeat { - fn default() -> Self { - Self { - postfix: true, - postfix_ufcs: true, - postfix_ufcs_left: true, - postfix_ufcs_right: true, - } - } + pub postfix_ufcs_right: Option, } impl CompletionFeat { @@ -51,13 +40,13 @@ impl CompletionFeat { self.ufcs() || self.ufcs_left() || self.ufcs_right() } pub(crate) fn ufcs(&self) -> bool { - self.postfix && self.postfix_ufcs + self.postfix.unwrap_or(true) && self.postfix_ufcs.unwrap_or(true) } pub(crate) fn ufcs_left(&self) -> bool { - self.postfix && self.postfix_ufcs_left + self.postfix.unwrap_or(true) && self.postfix_ufcs_left.unwrap_or(true) } pub(crate) fn ufcs_right(&self) -> bool { - self.postfix && self.postfix_ufcs_right + self.postfix.unwrap_or(true) && self.postfix_ufcs_right.unwrap_or(true) } }