From 68bcc2b5717f59b16d1d839618da0d27f3f7bf73 Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com> Date: Sun, 5 May 2024 20:19:29 +0800 Subject: [PATCH] feat: provide label details by type, symbol, and labels (#237) * feat: label details by type * fix: symbol's details and label details * dev: update snapshot * fix: make signature stable --- .../tinymist-query/src/analysis/signature.rs | 25 ++- crates/tinymist-query/src/analysis/ty.rs | 168 +++++++++++++++++- .../tinymist-query/src/analysis/ty/builtin.rs | 34 ++++ crates/tinymist-query/src/analysis/ty/def.rs | 21 ++- .../completion/snaps/test@base.typ.snap | 18 ++ .../completion/snaps/test@func_args.typ.snap | 3 + .../snaps/test@func_params.typ.snap | 18 ++ .../snaps/test@func_with_args.typ.snap | 3 + .../completion/snaps/test@import.typ.snap | 9 + .../snaps/test@import_self.typ.snap | 3 + .../snaps/test@import_self3.typ.snap | 3 + .../snaps/test@import_self4.typ.snap | 3 + .../snaps/test@import_star.typ.snap | 21 +++ .../snaps/test@item_shadow.typ.snap | 9 + .../completion/snaps/test@let.typ.snap | 18 ++ .../tinymist-query/src/upstream/complete.rs | 61 +++---- .../src/upstream/complete/ext.rs | 118 +++++++++++- tests/e2e/main.rs | 4 +- 18 files changed, 493 insertions(+), 46 deletions(-) diff --git a/crates/tinymist-query/src/analysis/signature.rs b/crates/tinymist-query/src/analysis/signature.rs index e8995182..0f92ac94 100644 --- a/crates/tinymist-query/src/analysis/signature.rs +++ b/crates/tinymist-query/src/analysis/signature.rs @@ -15,7 +15,7 @@ use typst::{ util::LazyHash, }; -use crate::analysis::resolve_callee; +use crate::analysis::{resolve_callee, FlowSignature}; use crate::syntax::{get_def_target, get_deref_target, DefTarget}; use crate::AnalysisContext; @@ -111,6 +111,8 @@ pub struct PrimarySignature { pub rest: Option>, /// The return type. pub(crate) ret_ty: Option, + /// The signature type. + pub(crate) sig_ty: Option, _broken: bool, } @@ -367,12 +369,33 @@ fn analyze_dyn_signature_inner(func: Func) -> Arc { } } + let mut named_vec: Vec<(EcoString, FlowType)> = named + .iter() + .map(|e| { + ( + e.0.as_ref().into(), + e.1.infer_type.clone().unwrap_or(FlowType::Any), + ) + }) + .collect::>(); + + named_vec.sort_by(|a, b| a.0.cmp(&b.0)); + + let sig_ty = FlowSignature::new( + pos.iter() + .map(|e| e.infer_type.clone().unwrap_or(FlowType::Any)), + named_vec.into_iter(), + rest.as_ref() + .map(|e| e.infer_type.clone().unwrap_or(FlowType::Any)), + ret_ty.clone(), + ); Arc::new(PrimarySignature { pos, named, rest, ret_ty, has_fill_or_size_or_stroke: has_fill || has_stroke || has_size, + sig_ty: Some(FlowType::Func(Box::new(sig_ty))), _broken: broken, }) } diff --git a/crates/tinymist-query/src/analysis/ty.rs b/crates/tinymist-query/src/analysis/ty.rs index 0da6984f..29613aa4 100644 --- a/crates/tinymist-query/src/analysis/ty.rs +++ b/crates/tinymist-query/src/analysis/ty.rs @@ -10,7 +10,7 @@ use once_cell::sync::Lazy; use parking_lot::{Mutex, RwLock}; use reflexo::{hash::hash128, vector::ir::DefId}; use typst::{ - foundations::{Func, Value}, + foundations::{Func, Repr, Value}, syntax::{ ast::{self, AstNode}, LinkedNode, Source, Span, SyntaxKind, @@ -86,6 +86,11 @@ impl TypeCheckInfo { worker.simplify(ty, principal) } + pub fn describe(&self, ty: &FlowType) -> Option { + let mut worker = TypeDescriber::default(); + worker.describe_root(ty) + } + // todo: distinguish at least, at most pub fn witness_at_least(&mut self, site: Span, ty: FlowType) { Self::witness_(site, ty, &mut self.mapping); @@ -1260,6 +1265,167 @@ struct TypeCanoStore { positives: HashSet, } +#[derive(Default)] +struct TypeDescriber { + described: HashSet, + results: HashSet, + functions: Vec, +} + +impl TypeDescriber { + fn describe_root(&mut self, ty: &FlowType) -> Option { + // recursive structure + if self.described.contains(&hash128(ty)) { + return Some("$self".to_string()); + } + + let res = self.describe(ty); + if !res.is_empty() { + return Some(res); + } + self.described.insert(hash128(ty)); + + let mut results = std::mem::take(&mut self.results) + .into_iter() + .collect::>(); + let functions = std::mem::take(&mut self.functions); + if !functions.is_empty() { + // todo: union signature + // only first function is described + let f = functions[0].clone(); + + let mut res = String::new(); + res.push('('); + let mut not_first = false; + for ty in f.pos.iter() { + if not_first { + res.push_str(", "); + } else { + not_first = true; + } + res.push_str(self.describe_root(ty).as_deref().unwrap_or("any")); + } + for (k, ty) in f.named.iter() { + if not_first { + res.push_str(", "); + } else { + not_first = true; + } + res.push_str(k); + res.push_str(": "); + res.push_str(self.describe_root(ty).as_deref().unwrap_or("any")); + } + if let Some(r) = &f.rest { + if not_first { + res.push_str(", "); + } + res.push_str("..: "); + res.push_str(self.describe_root(r).as_deref().unwrap_or("")); + res.push_str("[]"); + } + res.push_str(") => "); + res.push_str(self.describe_root(&f.ret).as_deref().unwrap_or("any")); + results.push(res); + } + + if results.is_empty() { + return None; + } + + results.sort(); + results.dedup(); + Some(results.join(" | ")) + } + + fn describe_iter(&mut self, ty: &[FlowType]) { + for ty in ty.iter() { + let desc = self.describe(ty); + self.results.insert(desc); + } + } + + fn describe(&mut self, ty: &FlowType) -> String { + match ty { + FlowType::Var(..) => {} + FlowType::Union(tys) => { + self.describe_iter(tys); + } + FlowType::Let(lb) => { + self.describe_iter(&lb.lbs); + self.describe_iter(&lb.ubs); + } + FlowType::Func(f) => { + self.functions.push(*f.clone()); + } + FlowType::Dict(..) => { + return "dict".to_string(); + } + FlowType::Tuple(..) => { + return "array".to_string(); + } + FlowType::Array(..) => { + return "array".to_string(); + } + FlowType::With(w) => { + return self.describe(&w.0); + } + FlowType::Clause => {} + FlowType::Undef => {} + FlowType::Content => { + return "content".to_string(); + } + FlowType::Any => { + return "any".to_string(); + } + FlowType::Space => {} + FlowType::None => { + return "none".to_string(); + } + FlowType::Infer => {} + FlowType::FlowNone => { + return "none".to_string(); + } + FlowType::Auto => { + return "auto".to_string(); + } + FlowType::Boolean(None) => { + return "boolean".to_string(); + } + FlowType::Boolean(Some(b)) => { + return b.to_string(); + } + FlowType::Builtin(b) => { + return b.describe().to_string(); + } + FlowType::Value(v) => return v.0.repr().to_string(), + FlowType::ValueDoc(v) => return v.0.repr().to_string(), + FlowType::Field(..) => { + return "field".to_string(); + } + FlowType::Element(..) => { + return "element".to_string(); + } + FlowType::Args(..) => { + return "args".to_string(); + } + FlowType::At(..) => { + return "any".to_string(); + } + FlowType::Unary(..) => { + return "any".to_string(); + } + FlowType::Binary(..) => { + return "any".to_string(); + } + FlowType::If(..) => { + return "any".to_string(); + } + } + + String::new() + } +} + struct TypeSimplifier<'a, 'b> { principal: bool, diff --git a/crates/tinymist-query/src/analysis/ty/builtin.rs b/crates/tinymist-query/src/analysis/ty/builtin.rs index 05dfaec3..948e52ec 100644 --- a/crates/tinymist-query/src/analysis/ty/builtin.rs +++ b/crates/tinymist-query/src/analysis/ty/builtin.rs @@ -102,6 +102,40 @@ pub(crate) enum FlowBuiltinType { Path(PathPreference), } +impl FlowBuiltinType { + pub(crate) fn describe(&self) -> &'static str { + match self { + FlowBuiltinType::Args => "args", + FlowBuiltinType::Color => "color", + FlowBuiltinType::TextSize => "text.size", + FlowBuiltinType::TextFont => "text.font", + FlowBuiltinType::TextLang => "text.lang", + FlowBuiltinType::TextRegion => "text.region", + FlowBuiltinType::Dir => "dir", + FlowBuiltinType::Length => "length", + FlowBuiltinType::Float => "float", + FlowBuiltinType::Stroke => "stroke", + FlowBuiltinType::Margin => "margin", + FlowBuiltinType::Inset => "inset", + FlowBuiltinType::Outset => "outset", + FlowBuiltinType::Radius => "radius", + FlowBuiltinType::Path(s) => match s { + PathPreference::None => "[any]", + PathPreference::Special => "[any]", + PathPreference::Source => "[source]", + PathPreference::Csv => "[csv]", + PathPreference::Image => "[image]", + PathPreference::Json => "[json]", + PathPreference::Yaml => "[yaml]", + PathPreference::Xml => "[xml]", + PathPreference::Toml => "[toml]", + PathPreference::Bibliography => "[bib]", + PathPreference::RawTheme => "[theme]", + PathPreference::RawSyntax => "[syntax]", + }, + } + } +} use FlowBuiltinType::*; diff --git a/crates/tinymist-query/src/analysis/ty/def.rs b/crates/tinymist-query/src/analysis/ty/def.rs index f812597c..38f3df87 100644 --- a/crates/tinymist-query/src/analysis/ty/def.rs +++ b/crates/tinymist-query/src/analysis/ty/def.rs @@ -11,7 +11,7 @@ use typst::{ use crate::analysis::ty::param_mapping; -use super::FlowBuiltinType; +use super::{FlowBuiltinType, TypeDescriber}; struct RefDebug<'a>(&'a FlowType); @@ -171,6 +171,11 @@ impl FlowType { Some(ty) } + pub fn describe(&self) -> Option { + let mut worker = TypeDescriber::default(); + worker.describe_root(self) + } + pub(crate) fn is_dict(&self) -> bool { matches!(self, FlowType::Dict(..)) } @@ -412,6 +417,20 @@ impl FlowSignature { ret, } } + + pub(crate) fn new( + pos: impl Iterator, + named: impl Iterator, + rest: Option, + ret_ty: Option, + ) -> Self { + FlowSignature { + pos: pos.collect(), + named: named.collect(), + rest, + ret: ret_ty.unwrap_or(FlowType::Any), + } + } } impl fmt::Debug for FlowSignature { diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@base.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@base.typ.snap index 05411ce0..67dece8c 100644 --- a/crates/tinymist-query/src/fixtures/completion/snaps/test@base.typ.snap +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@base.typ.snap @@ -11,6 +11,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/base.typ { "kind": 3, "label": "aa", + "labelDetails": { + "description": "() => 1" + }, "textEdit": { "newText": "aa(${1:})", "range": { @@ -28,6 +31,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/base.typ { "kind": 3, "label": "aab", + "labelDetails": { + "description": "() => 1" + }, "textEdit": { "newText": "aab(${1:})", "range": { @@ -45,6 +51,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/base.typ { "kind": 3, "label": "aabc", + "labelDetails": { + "description": "() => 1" + }, "textEdit": { "newText": "aabc(${1:})", "range": { @@ -62,6 +71,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/base.typ { "kind": 3, "label": "aac", + "labelDetails": { + "description": "() => 1" + }, "textEdit": { "newText": "aac(${1:})", "range": { @@ -84,6 +96,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/base.typ { "kind": 3, "label": "aabc", + "labelDetails": { + "description": "() => 1" + }, "textEdit": { "newText": "aabc(${1:})", "range": { @@ -101,6 +116,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/base.typ { "kind": 3, "label": "aac", + "labelDetails": { + "description": "() => 1" + }, "textEdit": { "newText": "aac(${1:})", "range": { 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 ab880230..47caa3bc 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 @@ -65,6 +65,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_args.typ { "kind": 7, "label": "content", + "labelDetails": { + "description": "type" + }, "sortText": "050", "textEdit": { "newText": "content", diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@func_params.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@func_params.typ.snap index 1d95b612..c68d1ed4 100644 --- a/crates/tinymist-query/src/fixtures/completion/snaps/test@func_params.typ.snap +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@func_params.typ.snap @@ -11,6 +11,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_params.typ { "kind": 3, "label": "aa", + "labelDetails": { + "description": "(any, any, any) => none" + }, "textEdit": { "newText": "aa(${1:})", "range": { @@ -28,6 +31,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_params.typ { "kind": 6, "label": "aab", + "labelDetails": { + "description": "any" + }, "textEdit": { "newText": "aab", "range": { @@ -45,6 +51,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_params.typ { "kind": 6, "label": "aabc", + "labelDetails": { + "description": "any" + }, "textEdit": { "newText": "aabc", "range": { @@ -62,6 +71,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_params.typ { "kind": 6, "label": "aac", + "labelDetails": { + "description": "any" + }, "textEdit": { "newText": "aac", "range": { @@ -84,6 +96,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_params.typ { "kind": 6, "label": "aabc", + "labelDetails": { + "description": "any" + }, "textEdit": { "newText": "aabc", "range": { @@ -101,6 +116,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_params.typ { "kind": 6, "label": "aac", + "labelDetails": { + "description": "any" + }, "textEdit": { "newText": "aac", "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 85d736ca..4e232d1a 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 @@ -65,6 +65,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/func_with_args.typ { "kind": 7, "label": "content", + "labelDetails": { + "description": "type" + }, "sortText": "050", "textEdit": { "newText": "content", diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@import.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@import.typ.snap index 28886aa7..8846b005 100644 --- a/crates/tinymist-query/src/fixtures/completion/snaps/test@import.typ.snap +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@import.typ.snap @@ -11,6 +11,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import.typ { "kind": 3, "label": "aab", + "labelDetails": { + "description": "() => any" + }, "textEdit": { "newText": "aab(${1:})", "range": { @@ -28,6 +31,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import.typ { "kind": 3, "label": "aac", + "labelDetails": { + "description": "() => any" + }, "textEdit": { "newText": "aac(${1:})", "range": { @@ -50,6 +56,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import.typ { "kind": 3, "label": "aac", + "labelDetails": { + "description": "() => any" + }, "textEdit": { "newText": "aac(${1:})", "range": { diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@import_self.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@import_self.typ.snap index e8319491..4748138d 100644 --- a/crates/tinymist-query/src/fixtures/completion/snaps/test@import_self.typ.snap +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@import_self.typ.snap @@ -11,6 +11,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_self.typ { "kind": 9, "label": "base", + "labelDetails": { + "description": "base.typ" + }, "textEdit": { "newText": "base", "range": { diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@import_self3.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@import_self3.typ.snap index eb797694..b5211735 100644 --- a/crates/tinymist-query/src/fixtures/completion/snaps/test@import_self3.typ.snap +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@import_self3.typ.snap @@ -11,6 +11,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_self3.typ { "kind": 9, "label": "baz", + "labelDetails": { + "description": "base.typ" + }, "textEdit": { "newText": "baz", "range": { diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@import_self4.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@import_self4.typ.snap index 56d7a4f7..19f80ac3 100644 --- a/crates/tinymist-query/src/fixtures/completion/snaps/test@import_self4.typ.snap +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@import_self4.typ.snap @@ -11,6 +11,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_self4.typ { "kind": 9, "label": "baz", + "labelDetails": { + "description": "base.typ" + }, "textEdit": { "newText": "baz", "range": { diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@import_star.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@import_star.typ.snap index 645eb4a3..391812f8 100644 --- a/crates/tinymist-query/src/fixtures/completion/snaps/test@import_star.typ.snap +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@import_star.typ.snap @@ -11,6 +11,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ { "kind": 3, "label": "aa", + "labelDetails": { + "description": "() => any" + }, "textEdit": { "newText": "aa(${1:})", "range": { @@ -28,6 +31,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ { "kind": 3, "label": "aa.with", + "labelDetails": { + "description": "() => any" + }, "textEdit": { "newText": "aa.with(${1:})", "range": { @@ -45,6 +51,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ { "kind": 3, "label": "aab", + "labelDetails": { + "description": "() => any" + }, "textEdit": { "newText": "aab(${1:})", "range": { @@ -62,6 +71,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ { "kind": 3, "label": "aabc", + "labelDetails": { + "description": "() => any" + }, "textEdit": { "newText": "aabc(${1:})", "range": { @@ -79,6 +91,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ { "kind": 3, "label": "aac", + "labelDetails": { + "description": "() => any" + }, "textEdit": { "newText": "aac(${1:})", "range": { @@ -101,6 +116,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ { "kind": 3, "label": "aabc", + "labelDetails": { + "description": "() => any" + }, "textEdit": { "newText": "aabc(${1:})", "range": { @@ -118,6 +136,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/import_star.typ { "kind": 3, "label": "aac", + "labelDetails": { + "description": "() => any" + }, "textEdit": { "newText": "aac(${1:})", "range": { diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@item_shadow.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@item_shadow.typ.snap index f0672cb5..fae406f8 100644 --- a/crates/tinymist-query/src/fixtures/completion/snaps/test@item_shadow.typ.snap +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@item_shadow.typ.snap @@ -11,6 +11,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/item_shadow.typ { "kind": 3, "label": "aa", + "labelDetails": { + "description": "() => 1" + }, "textEdit": { "newText": "aa(${1:})", "range": { @@ -28,6 +31,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/item_shadow.typ { "kind": 3, "label": "aac", + "labelDetails": { + "description": "() => 1" + }, "textEdit": { "newText": "aac(${1:})", "range": { @@ -50,6 +56,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/item_shadow.typ { "kind": 3, "label": "aac", + "labelDetails": { + "description": "() => 1" + }, "textEdit": { "newText": "aac(${1:})", "range": { diff --git a/crates/tinymist-query/src/fixtures/completion/snaps/test@let.typ.snap b/crates/tinymist-query/src/fixtures/completion/snaps/test@let.typ.snap index 5a3467db..4bed62f1 100644 --- a/crates/tinymist-query/src/fixtures/completion/snaps/test@let.typ.snap +++ b/crates/tinymist-query/src/fixtures/completion/snaps/test@let.typ.snap @@ -11,6 +11,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/let.typ { "kind": 3, "label": "aa", + "labelDetails": { + "description": "() => 1" + }, "textEdit": { "newText": "aa(${1:})", "range": { @@ -28,6 +31,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/let.typ { "kind": 6, "label": "aab", + "labelDetails": { + "description": "1" + }, "textEdit": { "newText": "aab", "range": { @@ -45,6 +51,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/let.typ { "kind": 6, "label": "aabc", + "labelDetails": { + "description": "1" + }, "textEdit": { "newText": "aabc", "range": { @@ -62,6 +71,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/let.typ { "kind": 3, "label": "aac", + "labelDetails": { + "description": "() => 1" + }, "textEdit": { "newText": "aac(${1:})", "range": { @@ -84,6 +96,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/let.typ { "kind": 6, "label": "aabc", + "labelDetails": { + "description": "1" + }, "textEdit": { "newText": "aabc", "range": { @@ -101,6 +116,9 @@ input_file: crates/tinymist-query/src/fixtures/completion/let.typ { "kind": 3, "label": "aac", + "labelDetails": { + "description": "() => 1" + }, "textEdit": { "newText": "aac(${1:})", "range": { diff --git a/crates/tinymist-query/src/upstream/complete.rs b/crates/tinymist-query/src/upstream/complete.rs index 569a1e35..ff51dc31 100644 --- a/crates/tinymist-query/src/upstream/complete.rs +++ b/crates/tinymist-query/src/upstream/complete.rs @@ -479,39 +479,6 @@ fn field_access_completions(ctx: &mut CompletionContext, value: &Value, styles: } } -/// If is printable, return the symbol itself. -/// Otherwise, return the symbol's unicode description. -fn symbol_label_detail(ch: char) -> EcoString { - if !ch.is_whitespace() && !ch.is_control() { - return ch.into(); - } - match ch { - ' ' => "space".into(), - '\t' => "tab".into(), - '\n' => "newline".into(), - '\r' => "carriage return".into(), - // replacer - '\u{200D}' => "zero width joiner".into(), - '\u{200C}' => "zero width non-joiner".into(), - '\u{200B}' => "zero width space".into(), - '\u{2060}' => "word joiner".into(), - // spaces - '\u{00A0}' => "non-breaking space".into(), - '\u{202F}' => "narrow no-break space".into(), - '\u{2002}' => "en space".into(), - '\u{2003}' => "em space".into(), - '\u{2004}' => "three-per-em space".into(), - '\u{2005}' => "four-per-em space".into(), - '\u{2006}' => "six-per-em space".into(), - '\u{2007}' => "figure space".into(), - '\u{205f}' => "medium mathematical space".into(), - '\u{2008}' => "punctuation space".into(), - '\u{2009}' => "thin space".into(), - '\u{200A}' => "hair space".into(), - _ => format!("\\u{{{:04x}}}", ch as u32).into(), - } -} - /// Complete half-finished labels. fn complete_open_labels(ctx: &mut CompletionContext) -> bool { // A label anywhere in code: "( CompletionContext<'a, 'w> { value: &Value, parens: bool, docs: Option<&str>, + ) { + self.value_completion_( + label, + value, + parens, + match value { + Value::Symbol(s) => Some(symbol_label_detail(s.get())), + _ => None, + }, + docs, + ); + } + + /// Add a completion for a specific value. + fn value_completion_( + &mut self, + label: Option, + value: &Value, + parens: bool, + label_detail: Option, + docs: Option<&str>, ) { let at = label.as_deref().is_some_and(|field| !is_ident(field)); let label = label.unwrap_or_else(|| value.repr()); let detail = docs.map(Into::into).or_else(|| match value { - Value::Symbol(_) => None, + Value::Symbol(c) => Some(symbol_detail(c.get())), Value::Func(func) => func.docs().map(plain_docs_sentence), Value::Type(ty) => Some(plain_docs_sentence(ty.docs())), v => { @@ -1216,10 +1204,7 @@ impl<'a, 'w> CompletionContext<'a, 'w> { label, apply, detail, - label_detail: match value { - Value::Symbol(s) => Some(symbol_label_detail(s.get())), - _ => None, - }, + label_detail, command, ..Completion::default() }); diff --git a/crates/tinymist-query/src/upstream/complete/ext.rs b/crates/tinymist-query/src/upstream/complete/ext.rs index af0f4dc3..97beddce 100644 --- a/crates/tinymist-query/src/upstream/complete/ext.rs +++ b/crates/tinymist-query/src/upstream/complete/ext.rs @@ -5,7 +5,7 @@ use lsp_types::{CompletionItem, CompletionTextEdit, InsertTextFormat, TextEdit}; use once_cell::sync::OnceCell; use parking_lot::Mutex; use reflexo::path::{unix_slash, PathClean}; -use typst::foundations::{AutoValue, Func, Label, NoneValue, Type, Value}; +use typst::foundations::{AutoValue, Func, Label, NoneValue, Repr, Type, Value}; use typst::layout::{Dir, Length}; use typst::syntax::ast::AstNode; use typst::syntax::{ast, Span, SyntaxKind}; @@ -69,6 +69,7 @@ impl<'a, 'w> CompletionContext<'a, 'w> { let src = self.ctx.source_by_id(id).ok()?; self.ctx.type_check(src) })(); + let types = types.as_ref(); let mut ancestor = Some(self.leaf.clone()); while let Some(node) = &ancestor { @@ -279,10 +280,38 @@ impl<'a, 'w> CompletionContext<'a, 'w> { if !filter(None) || name.is_empty() { continue; } - let _ = types; + let span = match def_kind { + DefKind::Syntax(span) => span, + DefKind::Instance(span, _) => span, + }; + // we don't check literal type here for faster completion + let ty_detail = if let CompletionKind::Symbol(c) = &kind { + Some(symbol_label_detail(*c)) + } else { + types + .and_then(|types| { + let ty = types.mapping.get(&span)?; + let ty = types.simplify(ty.clone(), false); + types.describe(&ty).map(From::from) + }) + .or_else(|| { + if let DefKind::Instance(_, v) = &def_kind { + Some(describe_value(self.ctx, v)) + } else { + None + } + }) + }; + let detail = if let CompletionKind::Symbol(c) = &kind { + Some(symbol_detail(*c)) + } else { + ty_detail.clone() + }; + if kind == CompletionKind::Func { let base = Completion { kind: kind.clone(), + label_detail: ty_detail, // todo: only vscode and neovim (0.9.1) support this command: Some("editor.action.triggerSuggest"), ..Default::default() @@ -345,12 +374,20 @@ impl<'a, 'w> CompletionContext<'a, 'w> { SurroundingSyntax::Selector | SurroundingSyntax::SetRule ) && !matches!(&v, Value::Func(func) if func.element().is_some()); if !bad_instantiate { - self.value_completion(Some(name), &v, parens, None); + self.value_completion_( + Some(name), + &v, + parens, + ty_detail.clone(), + detail.as_deref(), + ); } } else { self.completions.push(Completion { kind, label: name, + label_detail: ty_detail.clone(), + detail, ..Completion::default() }); } @@ -422,6 +459,38 @@ impl<'a, 'w> CompletionContext<'a, 'w> { } } +fn describe_value(ctx: &mut AnalysisContext, v: &Value) -> EcoString { + match v { + Value::Func(f) => { + let mut f = f; + while let typst::foundations::func::Repr::With(with_f) = f.inner() { + f = &with_f.0; + } + + let sig = analyze_dyn_signature(ctx, f.clone()); + sig.primary() + .sig_ty + .as_ref() + .and_then(|e| e.describe()) + .unwrap_or_else(|| "function".into()) + .into() + } + Value::Module(m) => { + if let Some(fid) = m.file_id() { + let package = fid.package(); + let path = unix_slash(fid.vpath().as_rootless_path()); + if let Some(package) = package { + return eco_format!("{package}:{path}"); + } + return path.into(); + } + + "module".into() + } + _ => v.ty().repr(), + } +} + fn encolsed_by(parent: &LinkedNode, s: Option, leaf: &LinkedNode) -> bool { s.and_then(|s| parent.find(s)?.find(leaf.span())).is_some() } @@ -1296,6 +1365,49 @@ pub fn complete_path( ) } +/// If is printable, return the symbol itself. +/// Otherwise, return the symbol's unicode detailed description. +pub fn symbol_detail(ch: char) -> EcoString { + let ld = symbol_label_detail(ch); + if ld.starts_with("\\u") { + return ld; + } + format!("{}, unicode: `\\u{{{:04x}}}`", ld, ch as u32).into() +} + +/// If is printable, return the symbol itself. +/// Otherwise, return the symbol's unicode description. +pub fn symbol_label_detail(ch: char) -> EcoString { + if !ch.is_whitespace() && !ch.is_control() { + return ch.into(); + } + match ch { + ' ' => "space".into(), + '\t' => "tab".into(), + '\n' => "newline".into(), + '\r' => "carriage return".into(), + // replacer + '\u{200D}' => "zero width joiner".into(), + '\u{200C}' => "zero width non-joiner".into(), + '\u{200B}' => "zero width space".into(), + '\u{2060}' => "word joiner".into(), + // spaces + '\u{00A0}' => "non-breaking space".into(), + '\u{202F}' => "narrow no-break space".into(), + '\u{2002}' => "en space".into(), + '\u{2003}' => "em space".into(), + '\u{2004}' => "three-per-em space".into(), + '\u{2005}' => "four-per-em space".into(), + '\u{2006}' => "six-per-em space".into(), + '\u{2007}' => "figure space".into(), + '\u{205f}' => "medium mathematical space".into(), + '\u{2008}' => "punctuation space".into(), + '\u{2009}' => "thin space".into(), + '\u{200A}' => "hair space".into(), + _ => format!("\\u{{{:04x}}}", ch as u32).into(), + } +} + #[cfg(test)] mod tests { diff --git a/tests/e2e/main.rs b/tests/e2e/main.rs index 419a36b4..8dc97e99 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:10b6c633caf6174b1b1728fe79be4c7a"); + insta::assert_snapshot!(hash, @"siphash128_13:d48ef9c33521fc601f1a411e4ede8c2e"); } { @@ -385,7 +385,7 @@ fn e2e() { }); let hash = replay_log(&tinymist_binary, &root.join("vscode")); - insta::assert_snapshot!(hash, @"siphash128_13:fffd39b26d0e52d1b274290e378df265"); + insta::assert_snapshot!(hash, @"siphash128_13:86c5b1c533652019b3d99f7079e19270"); } }