From 957b58687e7f94d0afa40ae1974a8493e853b016 Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com> Date: Tue, 15 Oct 2024 21:31:13 +0800 Subject: [PATCH] feat: merge hover and docs function formatter (#683) * feat: merge hover and docs function formatter * dev: clean up a bit * test: update snapshot --- crates/tinymist-query/src/analysis/global.rs | 13 +- .../tinymist-query/src/analysis/linked_def.rs | 10 ++ .../tinymist-query/src/analysis/signature.rs | 41 +---- crates/tinymist-query/src/docs/mod.rs | 161 +++++++++--------- .../call_info/snaps/test@builtin.typ.snap | 2 +- .../snaps/test@builtin_poly.typ.snap | 6 +- .../snaps/test@builtin_poly2.typ.snap | 2 +- .../call_info/snaps/test@user.typ.snap | 4 +- .../call_info/snaps/test@user_named.typ.snap | 4 +- .../snaps/test@user_named_with.typ.snap | 2 +- .../snaps/test@user_named_with2.typ.snap | 2 +- .../call_info/snaps/test@user_with.typ.snap | 2 +- .../src/fixtures/hover/annotate_fn.typ | 9 + .../hover/snaps/test@annotate_fn.typ.snap | 9 + .../hover/snaps/test@builtin.typ.snap | 2 +- .../hover/snaps/test@pagebreak.typ.snap | 2 +- crates/tinymist-query/src/hover.rs | 75 ++------ crates/tinymist-query/src/ty/builtin.rs | 2 +- tests/e2e/main.rs | 2 +- 19 files changed, 156 insertions(+), 194 deletions(-) create mode 100644 crates/tinymist-query/src/fixtures/hover/annotate_fn.typ create mode 100644 crates/tinymist-query/src/fixtures/hover/snaps/test@annotate_fn.typ.snap diff --git a/crates/tinymist-query/src/analysis/global.rs b/crates/tinymist-query/src/analysis/global.rs index 833ff466..167ea311 100644 --- a/crates/tinymist-query/src/analysis/global.rs +++ b/crates/tinymist-query/src/analysis/global.rs @@ -22,7 +22,7 @@ use crate::analysis::{ DefUseInfo, DefinitionLink, DocString, IdentRef, ImportInfo, PathPreference, SigTy, Signature, SignatureTarget, Ty, TypeScheme, }; -use crate::docs::DocStringKind; +use crate::docs::{DocSignature, DocStringKind}; use crate::prelude::*; use crate::syntax::{ construct_module_dependencies, find_expr_in_import, get_deref_target, resolve_id_by_path, @@ -481,6 +481,17 @@ impl<'w> AnalysisContext<'w> { } } + pub(crate) fn docs_signature( + &mut self, + source: &Source, + def_ident: Option<&IdentRef>, + runtime_fn: &Value, + ) -> Option { + let def_use = self.def_use(source.clone())?; + let ty_chk = self.type_check(source.clone())?; + crate::docs::docs_signature(self, Some(&(def_use, ty_chk)), def_ident, runtime_fn, None) + } + pub(crate) fn compute_docstring( &self, fid: TypstFileId, diff --git a/crates/tinymist-query/src/analysis/linked_def.rs b/crates/tinymist-query/src/analysis/linked_def.rs index 82cab2c0..0bb5bf12 100644 --- a/crates/tinymist-query/src/analysis/linked_def.rs +++ b/crates/tinymist-query/src/analysis/linked_def.rs @@ -34,6 +34,16 @@ pub struct DefinitionLink { pub name_range: Option>, } +impl DefinitionLink { + /// Convert the definition to an identifier reference. + pub fn to_ident_ref(&self) -> Option { + Some(IdentRef { + name: self.name.clone(), + range: self.name_range.clone()?, + }) + } +} + // todo: field definition /// Finds the definition of a symbol. pub fn find_definition( diff --git a/crates/tinymist-query/src/analysis/signature.rs b/crates/tinymist-query/src/analysis/signature.rs index 381de6e9..a812ad33 100644 --- a/crates/tinymist-query/src/analysis/signature.rs +++ b/crates/tinymist-query/src/analysis/signature.rs @@ -2,12 +2,11 @@ use core::fmt; use std::{borrow::Cow, collections::HashMap, ops::Range, sync::Arc}; -use ecow::{eco_format, eco_vec, EcoString, EcoVec}; -use itertools::Itertools; +use ecow::{eco_vec, EcoString, EcoVec}; use log::trace; use typst::syntax::{FileId as TypstFileId, Source}; use typst::{ - foundations::{CastInfo, Closure, Func, ParamInfo, Repr, Value}, + foundations::{Closure, Func, ParamInfo, Value}, syntax::{ ast::{self, AstNode}, LinkedNode, Span, SyntaxKind, @@ -20,6 +19,7 @@ use crate::adt::interner::Interned; use crate::analysis::resolve_callee; use crate::syntax::{get_def_target, get_deref_target, DefTarget}; use crate::ty::SigTy; +use crate::upstream::truncated_repr; use crate::AnalysisContext; use super::{find_definition, DefinitionLink, LexicalKind, LexicalVarKind, Ty}; @@ -35,12 +35,8 @@ pub struct ParamSpec { pub docs: Cow<'static, str>, /// Inferred type of the parameter. pub(crate) base_type: Ty, - /// The parameter's default name as type. - pub type_repr: Option, /// The parameter's default name as value. pub expr: Option, - /// Creates an instance of the parameter's default value. - pub default: Option Value>, /// Is the parameter positional? pub positional: bool, /// Is the parameter named? @@ -60,9 +56,7 @@ impl ParamSpec { name: p.name.into(), docs: Cow::Borrowed(p.docs), base_type: Ty::from_param_site(f, p), - type_repr: Some(eco_format!("{}", TypeExpr(&p.input))), - expr: None, - default: p.default, + expr: p.default.map(|d| truncated_repr(&d())), positional: p.positional, named: p.named, variadic: p.variadic, @@ -413,9 +407,8 @@ fn analyze_closure_signature(c: Arc>) -> Vec> { params.push(Arc::new(ParamSpec { name: name.as_str().into(), base_type: Ty::Any, - type_repr: None, + // type_repr: None, expr: None, - default: None, positional: true, named: false, variadic: false, @@ -429,9 +422,7 @@ fn analyze_closure_signature(c: Arc>) -> Vec> { params.push(Arc::new(ParamSpec { name: n.name().into(), base_type: Ty::Any, - type_repr: Some(expr.clone()), expr: Some(expr.clone()), - default: None, positional: false, named: true, variadic: false, @@ -444,9 +435,7 @@ fn analyze_closure_signature(c: Arc>) -> Vec> { params.push(Arc::new(ParamSpec { name: ident.unwrap_or_default().into(), base_type: Ty::Any, - type_repr: None, expr: None, - default: None, positional: true, named: false, variadic: true, @@ -509,23 +498,3 @@ fn unwrap_expr(mut e: ast::Expr) -> ast::Expr { e } - -struct TypeExpr<'a>(&'a CastInfo); - -impl<'a> fmt::Display for TypeExpr<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(match self.0 { - CastInfo::Any => "any", - CastInfo::Value(v, _doc) => return write!(f, "{}", v.repr()), - CastInfo::Type(v) => { - f.write_str(v.short_name())?; - return Ok(()); - } - CastInfo::Union(v) => { - let mut values = v.iter().map(|e| TypeExpr(e).to_string()); - f.write_str(&values.join(" | "))?; - return Ok(()); - } - }) - } -} diff --git a/crates/tinymist-query/src/docs/mod.rs b/crates/tinymist-query/src/docs/mod.rs index 323665c4..0371ef6c 100644 --- a/crates/tinymist-query/src/docs/mod.rs +++ b/crates/tinymist-query/src/docs/mod.rs @@ -94,6 +94,7 @@ impl Docs { } type TypeRepr = Option<(/* short */ String, /* long */ String)>; +type ShowTypeRepr<'a> = &'a mut dyn FnMut(Option<&Ty>) -> TypeRepr; /// Describes a primary function signature. #[derive(Debug, Clone, Serialize, Deserialize)] @@ -108,6 +109,65 @@ pub struct DocSignature { pub ret_ty: TypeRepr, } +impl fmt::Display for DocSignature { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut is_first = true; + let mut write_sep = |f: &mut fmt::Formatter<'_>| { + if is_first { + is_first = false; + return Ok(()); + } + f.write_str(", ") + }; + + for p in &self.pos { + write_sep(f)?; + f.write_str(&p.name)?; + if let Some(t) = &p.cano_type { + write!(f, ": {}", t.0)?; + } + } + if let Some(rest) = &self.rest { + write_sep(f)?; + f.write_str("..")?; + f.write_str(&rest.name)?; + if let Some(t) = &rest.cano_type { + write!(f, ": {}", t.0)?; + } + } + + if !self.named.is_empty() { + let mut name_prints = vec![]; + for v in self.named.values() { + let ty = v.cano_type.as_ref().map(|t| &t.0); + name_prints.push((v.name.clone(), ty, v.expr.clone())) + } + name_prints.sort(); + for (k, t, v) in name_prints { + write_sep(f)?; + let v = v.as_deref().unwrap_or("any"); + let mut v = v.trim(); + if v.starts_with('{') && v.ends_with('}') && v.len() > 30 { + v = "{ ... }" + } + if v.starts_with('`') && v.ends_with('`') && v.len() > 30 { + v = "raw" + } + if v.starts_with('[') && v.ends_with(']') && v.len() > 30 { + v = "content" + } + f.write_str(&k)?; + if let Some(t) = t { + write!(f, ": {t}")?; + } + write!(f, " = {v}")?; + } + } + + Ok(()) + } +} + /// Describes a function parameter. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DocParamSpec { @@ -117,8 +177,6 @@ pub struct DocParamSpec { pub docs: String, /// Inferred type of the parameter. pub cano_type: TypeRepr, - /// The parameter's default name as type. - pub type_repr: Option, /// The parameter's default name as value. pub expr: Option, /// Is the parameter positional? @@ -438,14 +496,14 @@ fn identify_docs(kind: &str, content: &str) -> StrResult { type TypeInfo = (Arc, Arc); -fn docs_signature( +pub(crate) fn docs_signature( ctx: &mut AnalysisContext, type_info: Option<&TypeInfo>, - sym: &SymbolInfo, - e: Value, - doc_ty: &mut impl FnMut(Option<&Ty>) -> TypeRepr, + def_ident: Option<&IdentRef>, + runtime_fn: &Value, + doc_ty: Option, ) -> Option { - let func = match &e { + let func = match runtime_fn { Value::Func(f) => f, _ => return None, }; @@ -470,15 +528,17 @@ fn docs_signature( let sig = analyze_dyn_signature(ctx, func.clone()); let type_sig = type_info.and_then(|(def_use, ty_chk)| { let def_fid = func.span().id()?; - let def_ident = IdentRef { - name: sym.head.name.clone(), - range: sym.head.name_range.clone()?, - }; - let (def_id, _) = def_use.get_def(def_fid, &def_ident)?; + let (def_id, _) = def_use.get_def(def_fid, def_ident?)?; ty_chk.type_of_def(def_id) }); let type_sig = type_sig.and_then(|type_sig| type_sig.sig_repr(true)); + const F: fn(Option<&Ty>) -> TypeRepr = |ty: Option<&Ty>| { + ty.and_then(|ty| ty.describe()) + .map(|short| (short, format!("{ty:?}"))) + }; + let mut binding = F; + let doc_ty = doc_ty.unwrap_or(&mut binding); let pos_in = sig .primary() .pos @@ -505,8 +565,7 @@ fn docs_signature( .map(|(param, ty)| DocParamSpec { name: param.name.as_ref().to_owned(), docs: param.docs.as_ref().to_owned(), - cano_type: doc_ty(ty), - type_repr: param.type_repr.clone(), + cano_type: doc_ty(ty.or(Some(¶m.base_type))), expr: param.expr.clone(), positional: param.positional, named: param.named, @@ -522,8 +581,7 @@ fn docs_signature( DocParamSpec { name: param.name.as_ref().to_owned(), docs: param.docs.as_ref().to_owned(), - cano_type: doc_ty(ty), - type_repr: param.type_repr.clone(), + cano_type: doc_ty(ty.or(Some(¶m.base_type))), expr: param.expr.clone(), positional: param.positional, named: param.named, @@ -537,8 +595,7 @@ fn docs_signature( let rest = rest_in.map(|(param, ty)| DocParamSpec { name: param.name.as_ref().to_owned(), docs: param.docs.as_ref().to_owned(), - cano_type: doc_ty(ty), - type_repr: param.type_repr.clone(), + cano_type: doc_ty(ty.or(Some(¶m.base_type))), expr: param.expr.clone(), positional: param.positional, named: param.named, @@ -704,8 +761,13 @@ pub fn generate_md_docs( sym.head.loc = span; let sym_value = sym.head.value.clone(); - let signature = - sym_value.and_then(|e| docs_signature(ctx, type_info, &sym, e, &mut doc_ty)); + let signature = sym_value.and_then(|e| { + let def_ident = IdentRef { + name: sym.head.name.clone(), + range: sym.head.name_range.clone()?, + }; + docs_signature(ctx, type_info, Some(&def_ident), &e, Some(&mut doc_ty)) + }); sym.head.signature = signature; let mut convert_err = None; @@ -769,12 +831,7 @@ pub fn generate_md_docs( if let Some(sig) = &sym.head.signature { let _ = writeln!(md, ""); let _ = writeln!(md, "```typc"); - let _ = writeln!( - md, - "let {name}({params});", - name = sym.head.name, - params = ParamTooltip(sig) - ); + let _ = writeln!(md, "let {name}({sig});", name = sym.head.name); let _ = writeln!(md, "```"); let _ = writeln!(md, ""); } @@ -943,58 +1000,6 @@ fn oneliner(docs: &str) -> &str { docs.lines().next().unwrap_or_default() } -// todo: hover with `with_stack`, todo: merge with hover tooltip -struct ParamTooltip<'a>(&'a DocSignature); - -impl<'a> fmt::Display for ParamTooltip<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut is_first = true; - let mut write_sep = |f: &mut fmt::Formatter<'_>| { - if is_first { - is_first = false; - return Ok(()); - } - f.write_str(", ") - }; - - let primary_sig = self.0; - - for p in &primary_sig.pos { - write_sep(f)?; - write!(f, "{}", p.name)?; - } - if let Some(rest) = &primary_sig.rest { - write_sep(f)?; - write!(f, "{}", rest.name)?; - } - - if !primary_sig.named.is_empty() { - let mut name_prints = vec![]; - for v in primary_sig.named.values() { - name_prints.push((v.name.clone(), v.type_repr.clone())) - } - name_prints.sort(); - for (k, v) in name_prints { - write_sep(f)?; - let v = v.as_deref().unwrap_or("any"); - let mut v = v.trim(); - if v.starts_with('{') && v.ends_with('}') && v.len() > 30 { - v = "{ ... }" - } - if v.starts_with('`') && v.ends_with('`') && v.len() > 30 { - v = "raw" - } - if v.starts_with('[') && v.ends_with(']') && v.len() > 30 { - v = "content" - } - write!(f, "{k}: {v}")?; - } - } - - Ok(()) - } -} - fn remove_list_annotations(s: &str) -> String { let s = s.to_string(); static REG: std::sync::LazyLock = std::sync::LazyLock::new(|| { diff --git a/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin.typ.snap b/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin.typ.snap index 9cba77bb..f55a0be6 100644 --- a/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin.typ.snap +++ b/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin.typ.snap @@ -3,4 +3,4 @@ source: crates/tinymist-query/src/analysis.rs expression: CallSnapshot(result.as_deref()) input_file: crates/tinymist-query/src/fixtures/call_info/builtin.typ --- -1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "angle", docs: "The angle whose sine to calculate.", base_type: (Type(angle) | Type(float) | Type(integer)), type_repr: Some("int | float | angle"), expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } +1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "angle", docs: "The angle whose sine to calculate.", base_type: (Type(angle) | Type(float) | Type(integer)), expr: None, positional: true, named: false, variadic: false, settable: false } } diff --git a/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin_poly.typ.snap b/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin_poly.typ.snap index 71867af6..83bb3a54 100644 --- a/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin_poly.typ.snap +++ b/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin_poly.typ.snap @@ -3,6 +3,6 @@ source: crates/tinymist-query/src/analysis.rs expression: CallSnapshot(result.as_deref()) input_file: crates/tinymist-query/src/fixtures/call_info/builtin_poly.typ --- -255 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "red", docs: "The red component.", base_type: (Type(integer) | Type(ratio)), type_repr: Some("int | ratio"), expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } -255 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "green", docs: "The green component.", base_type: (Type(integer) | Type(ratio)), type_repr: Some("int | ratio"), expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } -255 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "blue", docs: "The blue component.", base_type: (Type(integer) | Type(ratio)), type_repr: Some("int | ratio"), expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } +255 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "red", docs: "The red component.", base_type: (Type(integer) | Type(ratio)), expr: None, positional: true, named: false, variadic: false, settable: false } } +255 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "green", docs: "The green component.", base_type: (Type(integer) | Type(ratio)), expr: None, positional: true, named: false, variadic: false, settable: false } } +255 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "blue", docs: "The blue component.", base_type: (Type(integer) | Type(ratio)), expr: None, positional: true, named: false, variadic: false, settable: false } } diff --git a/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin_poly2.typ.snap b/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin_poly2.typ.snap index 294b7cfc..ef7f80ba 100644 --- a/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin_poly2.typ.snap +++ b/crates/tinymist-query/src/fixtures/call_info/snaps/test@builtin_poly2.typ.snap @@ -3,4 +3,4 @@ source: crates/tinymist-query/src/analysis.rs expression: CallSnapshot(result.as_deref()) input_file: crates/tinymist-query/src/fixtures/call_info/builtin_poly2.typ --- -"#fff" -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "red", docs: "The red component.", base_type: (Type(integer) | Type(ratio)), type_repr: Some("int | ratio"), expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } +"#fff" -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "red", docs: "The red component.", base_type: (Type(integer) | Type(ratio)), expr: None, positional: true, named: false, variadic: false, settable: false } } diff --git a/crates/tinymist-query/src/fixtures/call_info/snaps/test@user.typ.snap b/crates/tinymist-query/src/fixtures/call_info/snaps/test@user.typ.snap index fbd6be23..96d9d23f 100644 --- a/crates/tinymist-query/src/fixtures/call_info/snaps/test@user.typ.snap +++ b/crates/tinymist-query/src/fixtures/call_info/snaps/test@user.typ.snap @@ -3,5 +3,5 @@ source: crates/tinymist-query/src/analysis.rs expression: CallSnapshot(result.as_deref()) input_file: crates/tinymist-query/src/fixtures/call_info/user.typ --- -1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "x", docs: "", base_type: Any, type_repr: None, expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } -1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "y", docs: "", base_type: Any, type_repr: None, expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } +1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "x", docs: "", base_type: Any, expr: None, positional: true, named: false, variadic: false, settable: false } } +1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "y", docs: "", base_type: Any, expr: None, positional: true, named: false, variadic: false, settable: false } } diff --git a/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named.typ.snap b/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named.typ.snap index ee9ecb4e..36cd0b4f 100644 --- a/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named.typ.snap +++ b/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named.typ.snap @@ -3,5 +3,5 @@ source: crates/tinymist-query/src/analysis.rs expression: CallSnapshot(result.as_deref()) input_file: crates/tinymist-query/src/fixtures/call_info/user_named.typ --- -y: 1 -> CallParamInfo { kind: Named, is_content_block: false, param: ParamSpec { name: "y", docs: "Default value: none", base_type: Any, type_repr: Some("none"), expr: Some("none"), default: None, positional: false, named: true, variadic: false, settable: true } } -1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "x", docs: "", base_type: Any, type_repr: None, expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } +y: 1 -> CallParamInfo { kind: Named, is_content_block: false, param: ParamSpec { name: "y", docs: "Default value: none", base_type: Any, expr: Some("none"), positional: false, named: true, variadic: false, settable: true } } +1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "x", docs: "", base_type: Any, expr: None, positional: true, named: false, variadic: false, settable: false } } diff --git a/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named_with.typ.snap b/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named_with.typ.snap index a81d94ea..89b07df7 100644 --- a/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named_with.typ.snap +++ b/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named_with.typ.snap @@ -3,4 +3,4 @@ source: crates/tinymist-query/src/analysis.rs expression: CallSnapshot(result.as_deref()) input_file: crates/tinymist-query/src/fixtures/call_info/user_named_with.typ --- -1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "x", docs: "", base_type: Any, type_repr: None, expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } +1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "x", docs: "", base_type: Any, expr: None, positional: true, named: false, variadic: false, settable: false } } diff --git a/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named_with2.typ.snap b/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named_with2.typ.snap index db90414e..18aaaec9 100644 --- a/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named_with2.typ.snap +++ b/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_named_with2.typ.snap @@ -3,4 +3,4 @@ source: crates/tinymist-query/src/analysis.rs expression: CallSnapshot(result.as_deref()) input_file: crates/tinymist-query/src/fixtures/call_info/user_named_with2.typ --- -y: 1 -> CallParamInfo { kind: Named, is_content_block: false, param: ParamSpec { name: "y", docs: "Default value: none", base_type: Any, type_repr: Some("none"), expr: Some("none"), default: None, positional: false, named: true, variadic: false, settable: true } } +y: 1 -> CallParamInfo { kind: Named, is_content_block: false, param: ParamSpec { name: "y", docs: "Default value: none", base_type: Any, expr: Some("none"), positional: false, named: true, variadic: false, settable: true } } diff --git a/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_with.typ.snap b/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_with.typ.snap index 370580b7..bcbe1aa7 100644 --- a/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_with.typ.snap +++ b/crates/tinymist-query/src/fixtures/call_info/snaps/test@user_with.typ.snap @@ -3,4 +3,4 @@ source: crates/tinymist-query/src/analysis.rs expression: CallSnapshot(result.as_deref()) input_file: crates/tinymist-query/src/fixtures/call_info/user_with.typ --- -1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "y", docs: "", base_type: Any, type_repr: None, expr: None, default: None, positional: true, named: false, variadic: false, settable: false } } +1 -> CallParamInfo { kind: Positional, is_content_block: false, param: ParamSpec { name: "y", docs: "", base_type: Any, expr: None, positional: true, named: false, variadic: false, settable: false } } diff --git a/crates/tinymist-query/src/fixtures/hover/annotate_fn.typ b/crates/tinymist-query/src/fixtures/hover/annotate_fn.typ new file mode 100644 index 00000000..87f33a2d --- /dev/null +++ b/crates/tinymist-query/src/fixtures/hover/annotate_fn.typ @@ -0,0 +1,9 @@ +/// #let fn = `(..fn-args) => any`; +/// +/// - fn (function, fn): The `fn`. +/// - max-repetitions (int): The `max-repetitions`. +/// - repetitions (int): The `repetitions`. +/// - args (any, fn-args): The `args`. +#let touying-fn-wrapper(fn, max-repetitions: none, repetitions: none, ..args) = none + +#(/* ident after */ touying-fn-wrapper); diff --git a/crates/tinymist-query/src/fixtures/hover/snaps/test@annotate_fn.typ.snap b/crates/tinymist-query/src/fixtures/hover/snaps/test@annotate_fn.typ.snap new file mode 100644 index 00000000..4da70af6 --- /dev/null +++ b/crates/tinymist-query/src/fixtures/hover/snaps/test@annotate_fn.typ.snap @@ -0,0 +1,9 @@ +--- +source: crates/tinymist-query/src/hover.rs +expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" +input_file: crates/tinymist-query/src/fixtures/hover/annotate_fn.typ +--- +{ + "contents": "```typc\nlet touying-fn-wrapper(fn: (..: []) => any | function, ..args: arguments, max-repetitions: int | none = none, repetitions: int | none = none);\n```\n\n---\n\n\n #let fn = `(..fn-args) => any`;\n\n - fn (function, fn): The `fn`.\n - max-repetitions (int): The `max-repetitions`.\n - repetitions (int): The `repetitions`.\n - args (any, fn-args): The `args`.", + "range": "8:20:8:38" +} diff --git a/crates/tinymist-query/src/fixtures/hover/snaps/test@builtin.typ.snap b/crates/tinymist-query/src/fixtures/hover/snaps/test@builtin.typ.snap index 15c3308a..b10f048a 100644 --- a/crates/tinymist-query/src/fixtures/hover/snaps/test@builtin.typ.snap +++ b/crates/tinymist-query/src/fixtures/hover/snaps/test@builtin.typ.snap @@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" input_file: crates/tinymist-query/src/fixtures/hover/builtin.typ --- { - "contents": "```typc\nlet table(children, align: alignment | auto | array | function, column-gutter: auto | relative | fraction | int | array, columns: auto | relative | fraction | int | array, fill: color | gradient | pattern | none | array | function, gutter: auto | relative | fraction | int | array, inset: relative | dictionary | array | function, row-gutter: auto | relative | fraction | int | array, rows: auto | relative | fraction | int | array, stroke: length | color | gradient | pattern | dictionary | stroke | none | array | function);\n```\n\n---\n\n\nA table of items.\n\nTables are used to arrange content in cells. Cells can contain arbitrary\ncontent, including multiple paragraphs and are specified in row-major order.\nFor a hands-on explanation of all the ways you can use and customize tables\nin Typst, check out the [table guide](https://typst.app/docs/guides/table-guide/).\n\nBecause tables are just grids with different defaults for some cell\nproperties (notably `stroke` and `inset`), refer to the [grid\ndocumentation](https://typst.app/docs/reference/layout/grid/) for more information on how to size the table tracks\nand specify the cell appearance properties.\n\nIf you are unsure whether you should be using a table or a grid, consider\nwhether the content you are arranging semantically belongs together as a set\nof related data points or similar or whether you are just want to enhance\nyour presentation by arranging unrelated content in a grid. In the former\ncase, a table is the right choice, while in the latter case, a grid is more\nappropriate. Furthermore, Typst will annotate its output in the future such\nthat screenreaders will annouce content in `table` as tabular while a grid's\ncontent will be announced no different than multiple content blocks in the\ndocument flow.\n\nNote that, to override a particular cell's properties or apply show rules on\ntable cells, you can use the [`table.cell`](https://typst.app/docs/reference/model/table/#definitions-cell) element. See its\ndocumentation for more information.\n\nAlthough the `table` and the `grid` share most properties, set and show\nrules on one of them do not affect the other.\n\nTo give a table a caption and make it [referenceable](https://typst.app/docs/reference/model/ref/), put it into a\n[figure].\n\n# Example\n\nThe example below demonstrates some of the most common table options.\n```typ\n#table(\n columns: (1fr, auto, auto),\n inset: 10pt,\n align: horizon,\n table.header(\n [], [*Area*], [*Parameters*],\n ),\n image(\"cylinder.svg\"),\n $ pi h (D^2 - d^2) / 4 $,\n [\n $h$: height \\\n $D$: outer radius \\\n $d$: inner radius\n ],\n image(\"tetrahedron.svg\"),\n $ sqrt(2) / 12 a^3 $,\n [$a$: edge length]\n)\n```\n\nMuch like with grids, you can use [`table.cell`](https://typst.app/docs/reference/model/table/#definitions-cell) to customize\nthe appearance and the position of each cell.\n\n```typ\n>>> #set page(width: auto)\n>>> #set text(font: \"IBM Plex Sans\")\n>>> #let gray = rgb(\"#565565\")\n>>>\n#set table(\n stroke: none,\n gutter: 0.2em,\n fill: (x, y) =>\n if x == 0 or y == 0 { gray },\n inset: (right: 1.5em),\n)\n\n#show table.cell: it => {\n if it.x == 0 or it.y == 0 {\n set text(white)\n strong(it)\n } else if it.body == [] {\n // Replace empty cells with 'N/A'\n pad(..it.inset)[_N/A_]\n } else {\n it\n }\n}\n\n#let a = table.cell(\n fill: green.lighten(60%),\n)[A]\n#let b = table.cell(\n fill: aqua.lighten(60%),\n)[B]\n\n#table(\n columns: 4,\n [], [Exam 1], [Exam 2], [Exam 3],\n\n [John], [], a, [],\n [Mary], [], a, a,\n [Robert], b, a, b,\n)\n```\n\n---\n[Open docs](https://typst.app/docs/reference/model/table/)", + "contents": "```typc\nlet table(..children: content, align: alignment | array | auto | function = auto, column-gutter: array | auto | integer | length = (), columns: array | auto | integer | length = (), fill: color = none, gutter: array | auto | integer | length = (), inset: inset = 5pt, row-gutter: array | auto | integer | length = (), rows: array | auto | integer | length = (), stroke: stroke = 1pt + black);\n```\n\n---\n\n\nA table of items.\n\nTables are used to arrange content in cells. Cells can contain arbitrary\ncontent, including multiple paragraphs and are specified in row-major order.\nFor a hands-on explanation of all the ways you can use and customize tables\nin Typst, check out the [table guide](https://typst.app/docs/guides/table-guide/).\n\nBecause tables are just grids with different defaults for some cell\nproperties (notably `stroke` and `inset`), refer to the [grid\ndocumentation](https://typst.app/docs/reference/layout/grid/) for more information on how to size the table tracks\nand specify the cell appearance properties.\n\nIf you are unsure whether you should be using a table or a grid, consider\nwhether the content you are arranging semantically belongs together as a set\nof related data points or similar or whether you are just want to enhance\nyour presentation by arranging unrelated content in a grid. In the former\ncase, a table is the right choice, while in the latter case, a grid is more\nappropriate. Furthermore, Typst will annotate its output in the future such\nthat screenreaders will annouce content in `table` as tabular while a grid's\ncontent will be announced no different than multiple content blocks in the\ndocument flow.\n\nNote that, to override a particular cell's properties or apply show rules on\ntable cells, you can use the [`table.cell`](https://typst.app/docs/reference/model/table/#definitions-cell) element. See its\ndocumentation for more information.\n\nAlthough the `table` and the `grid` share most properties, set and show\nrules on one of them do not affect the other.\n\nTo give a table a caption and make it [referenceable](https://typst.app/docs/reference/model/ref/), put it into a\n[figure].\n\n# Example\n\nThe example below demonstrates some of the most common table options.\n```typ\n#table(\n columns: (1fr, auto, auto),\n inset: 10pt,\n align: horizon,\n table.header(\n [], [*Area*], [*Parameters*],\n ),\n image(\"cylinder.svg\"),\n $ pi h (D^2 - d^2) / 4 $,\n [\n $h$: height \\\n $D$: outer radius \\\n $d$: inner radius\n ],\n image(\"tetrahedron.svg\"),\n $ sqrt(2) / 12 a^3 $,\n [$a$: edge length]\n)\n```\n\nMuch like with grids, you can use [`table.cell`](https://typst.app/docs/reference/model/table/#definitions-cell) to customize\nthe appearance and the position of each cell.\n\n```typ\n>>> #set page(width: auto)\n>>> #set text(font: \"IBM Plex Sans\")\n>>> #let gray = rgb(\"#565565\")\n>>>\n#set table(\n stroke: none,\n gutter: 0.2em,\n fill: (x, y) =>\n if x == 0 or y == 0 { gray },\n inset: (right: 1.5em),\n)\n\n#show table.cell: it => {\n if it.x == 0 or it.y == 0 {\n set text(white)\n strong(it)\n } else if it.body == [] {\n // Replace empty cells with 'N/A'\n pad(..it.inset)[_N/A_]\n } else {\n it\n }\n}\n\n#let a = table.cell(\n fill: green.lighten(60%),\n)[A]\n#let b = table.cell(\n fill: aqua.lighten(60%),\n)[B]\n\n#table(\n columns: 4,\n [], [Exam 1], [Exam 2], [Exam 3],\n\n [John], [], a, [],\n [Mary], [], a, a,\n [Robert], b, a, b,\n)\n```\n\n---\n[Open docs](https://typst.app/docs/reference/model/table/)", "range": "0:20:0:25" } diff --git a/crates/tinymist-query/src/fixtures/hover/snaps/test@pagebreak.typ.snap b/crates/tinymist-query/src/fixtures/hover/snaps/test@pagebreak.typ.snap index 6985fe50..bccb0de3 100644 --- a/crates/tinymist-query/src/fixtures/hover/snaps/test@pagebreak.typ.snap +++ b/crates/tinymist-query/src/fixtures/hover/snaps/test@pagebreak.typ.snap @@ -4,6 +4,6 @@ expression: "JsonRepr::new_redacted(result, &REDACT_LOC)" input_file: crates/tinymist-query/src/fixtures/hover/pagebreak.typ --- { - "contents": "```typc\nlet pagebreak(to: \"even\" | \"odd\" | none, weak: bool);\n```\n\n---\n\n\nA manual page break.\n\nMust not be used inside any containers.\n\n# Example\n```typ\nThe next page contains\nmore details on compound theory.\n#pagebreak()\n\n== Compound Theory\nIn 1984, the first ...\n```\n\n---\n[Open docs](https://typst.app/docs/reference/layout/pagebreak/)", + "contents": "```typc\nlet pagebreak(to: \"even\" | \"odd\" | none = none, weak: bool = false);\n```\n\n---\n\n\nA manual page break.\n\nMust not be used inside any containers.\n\n# Example\n```typ\nThe next page contains\nmore details on compound theory.\n#pagebreak()\n\n== Compound Theory\nIn 1984, the first ...\n```\n\n---\n[Open docs](https://typst.app/docs/reference/layout/pagebreak/)", "range": "0:20:0:29" } diff --git a/crates/tinymist-query/src/hover.rs b/crates/tinymist-query/src/hover.rs index 99be60f5..2a992773 100644 --- a/crates/tinymist-query/src/hover.rs +++ b/crates/tinymist-query/src/hover.rs @@ -3,9 +3,8 @@ use core::fmt; use typst_shim::syntax::LinkedNodeExt; use crate::{ - analysis::{ - analyze_dyn_signature, find_definition, get_link_exprs_in, DefinitionLink, Signature, - }, + analysis::{find_definition, get_link_exprs_in, DefinitionLink}, + docs::DocSignature, jump_from_cursor, prelude::*, syntax::{find_docs_before, get_deref_target, LexicalKind, LexicalVarKind}, @@ -252,17 +251,17 @@ fn def_tooltip( Some(LspHoverContents::Array(results)) } LexicalKind::Var(LexicalVarKind::Function) => { - let sig = if let Some(Value::Func(func)) = &lnk.value { - Some(analyze_dyn_signature(ctx, func.clone())) - } else { - None - }; + let sig = lnk + .value + .as_ref() + .and_then(|e| ctx.docs_signature(source, lnk.to_ident_ref().as_ref(), e)); + results.push(MarkedString::LanguageString(LanguageString { language: "typc".to_owned(), value: format!( "let {name}({params});", name = lnk.name, - params = ParamTooltip(sig) + params = ParamTooltip(sig.as_ref()) ), })); @@ -353,64 +352,14 @@ fn render_actions(results: &mut Vec, actions: Vec) { } // todo: hover with `with_stack` -struct ParamTooltip(Option); +struct ParamTooltip<'a>(Option<&'a DocSignature>); -impl fmt::Display for ParamTooltip { +impl fmt::Display for ParamTooltip<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Some(sig) = &self.0 else { + let Some(sig) = self.0 else { return Ok(()); }; - let mut is_first = true; - let mut write_sep = |f: &mut fmt::Formatter<'_>| { - if is_first { - is_first = false; - return Ok(()); - } - f.write_str(", ") - }; - - let primary_sig = match sig { - Signature::Primary(sig) => sig, - Signature::Partial(sig) => { - let _ = sig.with_stack; - - &sig.signature - } - }; - - for p in &primary_sig.pos { - write_sep(f)?; - write!(f, "{}", p.name)?; - } - if let Some(rest) = &primary_sig.rest { - write_sep(f)?; - write!(f, "{}", rest.name)?; - } - - if !primary_sig.named.is_empty() { - let mut name_prints = vec![]; - for v in primary_sig.named.values() { - name_prints.push((v.name.clone(), v.type_repr.clone())) - } - name_prints.sort(); - for (k, v) in name_prints { - write_sep(f)?; - let v = v.as_deref().unwrap_or("any"); - let mut v = v.trim(); - if v.starts_with('{') && v.ends_with('}') && v.len() > 30 { - v = "{ ... }" - } - if v.starts_with('`') && v.ends_with('`') && v.len() > 30 { - v = "raw" - } - if v.starts_with('[') && v.ends_with(']') && v.len() > 30 { - v = "content" - } - write!(f, "{k}: {v}")?; - } - } - - Ok(()) + sig.fmt(f) } } diff --git a/crates/tinymist-query/src/ty/builtin.rs b/crates/tinymist-query/src/ty/builtin.rs index 9d2dea32..870aaaa1 100644 --- a/crates/tinymist-query/src/ty/builtin.rs +++ b/crates/tinymist-query/src/ty/builtin.rs @@ -317,7 +317,7 @@ impl BuiltinTy { BuiltinTy::FlowNone => "none", BuiltinTy::Auto => "auto", - BuiltinTy::Args => "args", + BuiltinTy::Args => "arguments", BuiltinTy::Color => "color", BuiltinTy::TextSize => "text.size", BuiltinTy::TextFont => "text.font", diff --git a/tests/e2e/main.rs b/tests/e2e/main.rs index c8dc688f..b7802595 100644 --- a/tests/e2e/main.rs +++ b/tests/e2e/main.rs @@ -385,7 +385,7 @@ fn e2e() { }); let hash = replay_log(&tinymist_binary, &root.join("vscode")); - insta::assert_snapshot!(hash, @"siphash128_13:cbabb6bf71ce157c4e779aecf9d22c2a"); + insta::assert_snapshot!(hash, @"siphash128_13:c74f86e2e0fa2ecb0323814645f39c1b"); } }