From 1c1bc19caf84949580d7a3d5b1db0173aa042ce9 Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com> Date: Wed, 30 Oct 2024 22:29:07 +0800 Subject: [PATCH] dev: more consistent way to get docs of decls (#752) * dev: more consistent way to get docs of decls * test: update snapshot --- .../tinymist-query/src/analysis/definition.rs | 9 +- crates/tinymist-query/src/analysis/global.rs | 79 +++++-- .../tinymist-query/src/analysis/signature.rs | 13 +- crates/tinymist-query/src/analysis/tyck.rs | 6 +- .../src/analysis/tyck/syntax.rs | 6 +- crates/tinymist-query/src/docs/mod.rs | 12 +- crates/tinymist-query/src/docs/module.rs | 167 ++++++++------ crates/tinymist-query/src/docs/package.rs | 97 ++++---- crates/tinymist-query/src/docs/symbol.rs | 116 ++-------- crates/tinymist-query/src/goto_definition.rs | 2 +- crates/tinymist-query/src/hover.rs | 214 ++++++------------ crates/tinymist-query/src/prepare_rename.rs | 2 +- crates/tinymist-query/src/references.rs | 11 +- crates/tinymist-query/src/rename.rs | 2 +- crates/tinymist-query/src/signature_help.rs | 58 ++++- crates/tinymist-query/src/syntax/def.rs | 54 +++++ crates/tinymist-query/src/syntax/docs.rs | 22 +- crates/tinymist-query/src/syntax/expr.rs | 11 +- crates/tinymist-query/src/ty/def.rs | 4 +- crates/tinymist-query/src/ty/describe.rs | 15 ++ .../src/upstream/complete/ext.rs | 6 +- crates/tinymist-query/src/upstream/mod.rs | 5 - tests/e2e/main.rs | 2 +- 23 files changed, 462 insertions(+), 451 deletions(-) diff --git a/crates/tinymist-query/src/analysis/definition.rs b/crates/tinymist-query/src/analysis/definition.rs index 61ce3edd..73aa3b98 100644 --- a/crates/tinymist-query/src/analysis/definition.rs +++ b/crates/tinymist-query/src/analysis/definition.rs @@ -5,7 +5,7 @@ use typst::introspection::Introspector; use typst::model::BibliographyElem; use super::{prelude::*, BuiltinTy, InsTy, SharedContext}; -use crate::syntax::{get_deref_target, Decl, DeclExpr, DerefTarget, Expr, ExprInfo}; +use crate::syntax::{Decl, DeclExpr, DerefTarget, Expr, ExprInfo}; use crate::VersionedDocument; /// A linked definition in the source code @@ -59,7 +59,7 @@ pub fn definition( ctx: &Arc, source: &Source, document: Option<&VersionedDocument>, - deref_target: DerefTarget<'_>, + deref_target: DerefTarget, ) -> Option { match deref_target { // todi: field access @@ -249,10 +249,7 @@ impl CallConvention { pub fn resolve_call_target(ctx: &Arc, node: &SyntaxNode) -> Option { let callee = (|| { let source = ctx.source_by_id(node.span().id()?).ok()?; - let node = source.find(node.span())?; - let cursor = node.offset(); - let deref_target = get_deref_target(node, cursor)?; - let def = ctx.definition(&source, None, deref_target)?; + let def = ctx.def_of_span(&source, None, node.span())?; let func_ptr = match def.term.and_then(|val| val.value()) { Some(Value::Func(f)) => Some(f), Some(Value::Type(ty)) => ty.constructor().ok(), diff --git a/crates/tinymist-query/src/analysis/global.rs b/crates/tinymist-query/src/analysis/global.rs index 84649ed9..952b81d8 100644 --- a/crates/tinymist-query/src/analysis/global.rs +++ b/crates/tinymist-query/src/analysis/global.rs @@ -13,12 +13,13 @@ use reflexo_typst::{EntryReader, WorldDeps}; use rustc_hash::FxHashMap; use tinymist_world::LspWorld; use tinymist_world::DETACHED_ENTRY; -use typst::diag::{eco_format, At, FileError, FileResult, SourceResult}; +use typst::diag::{eco_format, At, FileError, FileResult, SourceResult, StrResult}; use typst::engine::{Route, Sink, Traced}; use typst::eval::Eval; use typst::foundations::{Bytes, Module, Styles}; use typst::layout::Position; use typst::model::Document; +use typst::syntax::package::PackageManifest; use typst::syntax::{package::PackageSpec, Span, VirtualPath}; use crate::analysis::prelude::*; @@ -26,10 +27,11 @@ use crate::analysis::{ analyze_bib, analyze_import_, analyze_signature, post_type_check, BibInfo, PathPreference, Signature, SignatureTarget, Ty, TypeScheme, }; -use crate::docs::{SignatureDocs, VarDocs}; +use crate::docs::{DefDocs, TidyModuleDocs}; use crate::syntax::{ construct_module_dependencies, find_expr_in_import, get_deref_target, resolve_id_by_path, - scan_workspace_files, DerefTarget, ExprInfo, LexicalScope, ModuleDependency, Processing, + scan_workspace_files, Decl, DefKind, DerefTarget, ExprInfo, LexicalScope, ModuleDependency, + Processing, }; use crate::upstream::{tooltip_, Tooltip}; use crate::{ @@ -200,10 +202,6 @@ impl<'w> AnalysisContext<'w> { } } - pub(crate) fn variable_docs(&mut self, pos: &LinkedNode) -> Option { - crate::docs::variable_docs(self, pos) - } - pub(crate) fn preload_package(&self, entry_point: TypstFileId) { self.shared_().preload_package(entry_point); } @@ -376,6 +374,11 @@ impl LocalContext { } } + /// Get the expression information of a source file. + pub(crate) fn expr_stage_by_id(&mut self, fid: TypstFileId) -> Option> { + Some(self.expr_stage(&self.source_by_id(fid).ok()?)) + } + /// Get the expression information of a source file. pub(crate) fn expr_stage(&mut self, source: &Source) -> Arc { let id = source.id(); @@ -389,6 +392,29 @@ impl LocalContext { let cache = &self.caches.modules.entry(id).or_default().type_check; cache.get_or_init(|| self.shared.type_check(source)).clone() } + + pub(crate) fn def_docs(&mut self, def: &Definition) -> Option { + // let plain_docs = sym.head.docs.as_deref(); + // let plain_docs = plain_docs.or(sym.head.oneliner.as_deref()); + match def.decl.kind() { + DefKind::Function => { + let sig = self.sig_of_def(def.clone())?; + let docs = crate::docs::signature_docs(&sig, None)?; + Some(DefDocs::Function(Box::new(docs))) + } + DefKind::Struct | DefKind::Constant | DefKind::Variable => { + let docs = crate::docs::variable_docs(self, def.decl.span())?; + Some(DefDocs::Variable(docs)) + } + DefKind::Module => { + let ei = self.expr_stage_by_id(def.decl.file_id()?)?; + Some(DefDocs::Module(TidyModuleDocs { + docs: ei.module_docstring.docs.clone().unwrap_or_default(), + })) + } + DefKind::Reference => None, + } + } } /// The shared analysis context for analyzers. @@ -594,6 +620,11 @@ impl SharedContext { res } + /// Get the expression information of a source file. + pub(crate) fn expr_stage_by_id(self: &Arc, fid: TypstFileId) -> Option> { + Some(self.expr_stage(&self.source_by_id(fid).ok()?)) + } + /// Get the expression information of a source file. pub(crate) fn expr_stage(self: &Arc, source: &Source) -> Arc { let mut route = Processing::default(); @@ -660,7 +691,25 @@ impl SharedContext { }) } - pub(crate) fn definition( + pub(crate) fn def_of_span( + self: &Arc, + source: &Source, + doc: Option<&VersionedDocument>, + span: Span, + ) -> Option { + let target = self.deref_syntax(source, span)?; + definition(self, source, doc, target) + } + + pub(crate) fn def_of_decl(&self, decl: &Interned) -> Option { + match decl.as_ref() { + Decl::Func(..) => Some(Definition::new(decl.clone(), None)), + Decl::Module(..) => None, + _ => None, + } + } + + pub(crate) fn def_of_syntax( self: &Arc, source: &Source, doc: Option<&VersionedDocument>, @@ -669,22 +718,17 @@ impl SharedContext { definition(self, source, doc, deref_target) } - pub(crate) fn signature_def(self: &Arc, def: Definition) -> Option { + pub(crate) fn sig_of_def(self: &Arc, def: Definition) -> Option { log::debug!("check definition func {def:?}"); let source = def.decl.file_id().and_then(|f| self.source_by_id(f).ok()); analyze_signature(self, SignatureTarget::Def(source, def)) } - pub(crate) fn signature_dyn(self: &Arc, func: Func) -> Signature { + pub(crate) fn sig_of_func(self: &Arc, func: Func) -> Signature { log::debug!("check runtime func {func:?}"); analyze_signature(self, SignatureTarget::Runtime(func)).unwrap() } - pub(crate) fn signature_docs(self: &Arc, def: &Definition) -> Option { - let sig = self.signature_def(def.clone())?; - crate::docs::signature_docs(&sig, None) - } - /// Try to find imported target from the current source file. /// This function will try to resolves target statically. /// @@ -789,6 +833,11 @@ impl SharedContext { } } + /// Get the manifest of a package by file id. + pub fn get_manifest(&self, toml_id: TypstFileId) -> StrResult { + crate::docs::get_manifest(&self.world, toml_id) + } + /// Compute the signature of a function. pub fn compute_signature( self: &Arc, diff --git a/crates/tinymist-query/src/analysis/signature.rs b/crates/tinymist-query/src/analysis/signature.rs index 58201f61..5ac5f57b 100644 --- a/crates/tinymist-query/src/analysis/signature.rs +++ b/crates/tinymist-query/src/analysis/signature.rs @@ -6,7 +6,7 @@ use typst::foundations::{Closure, ParamInfo}; use super::{prelude::*, BoundChecker, Definition, DocSource, SharedContext, SigTy, TypeVar}; use crate::analysis::PostTypeChecker; -use crate::docs::{UntypedSignatureDocs, UntypedSymbolDocs, UntypedVarDocs}; +use crate::docs::{UntypedDefDocs, UntypedSignatureDocs, UntypedVarDocs}; use crate::syntax::get_non_strict_def_target; use crate::ty::TyCtx; use crate::ty::TypeBounds; @@ -290,8 +290,8 @@ fn analyze_type_signature( // todo: this will affect inlay hint: _var_with let (_var_with, docstring) = match type_info.var_docs.get(&v.def).map(|x| x.as_ref()) { - Some(UntypedSymbolDocs::Function(sig)) => (vec![], Either::Left(sig.as_ref())), - Some(UntypedSymbolDocs::Variable(d)) => find_alias_stack(&mut ty_ctx, &v, d)?, + Some(UntypedDefDocs::Function(sig)) => (vec![], Either::Left(sig.as_ref())), + Some(UntypedDefDocs::Variable(d)) => find_alias_stack(&mut ty_ctx, &v, d)?, _ => return None, }; @@ -415,10 +415,10 @@ impl<'a, 'b> BoundChecker for AliasStackChecker<'a, 'b> { log::debug!("collecting var {u:?} {pol:?} => {docs:?}"); // todo: bind builtin functions match docs { - Some(UntypedSymbolDocs::Function(sig)) => { + Some(UntypedDefDocs::Function(sig)) => { self.res = Some(Either::Left(sig)); } - Some(UntypedSymbolDocs::Variable(d)) => { + Some(UntypedDefDocs::Variable(d)) => { self.checking_with = true; self.stack.push(d); self.check_var_rec(u, pol); @@ -468,8 +468,7 @@ fn analyze_dyn_signature( SignatureTarget::Def(_source, def) => def.value()?.to_func()?, SignatureTarget::SyntaxFast(..) => return None, SignatureTarget::Syntax(source, span) => { - let target = ctx.deref_syntax(source, *span)?; - let def = ctx.definition(source, None, target)?; + let def = ctx.def_of_span(source, None, *span)?; def.value()?.to_func()? } SignatureTarget::Convert(func) | SignatureTarget::Runtime(func) => func.clone(), diff --git a/crates/tinymist-query/src/analysis/tyck.rs b/crates/tinymist-query/src/analysis/tyck.rs index 15db24f6..5352bbc2 100644 --- a/crates/tinymist-query/src/analysis/tyck.rs +++ b/crates/tinymist-query/src/analysis/tyck.rs @@ -121,13 +121,13 @@ impl<'a> TypeChecker<'a> { log::debug!("import_ty {name} from {fid:?}"); - let source = self.ctx.source_by_id(fid).ok()?; - let ext_def_use_info = self.ctx.expr_stage(&source); + let ext_def_use_info = self.ctx.expr_stage_by_id(fid)?; + let source = &ext_def_use_info.source; // todo: check types in cycle let ext_type_info = if let Some(route) = self.route.get(&source.id()) { route.clone() } else { - self.ctx.type_check_(&source, self.route) + self.ctx.type_check_(source, self.route) }; let ext_def = ext_def_use_info.exports.get(&name)?; diff --git a/crates/tinymist-query/src/analysis/tyck/syntax.rs b/crates/tinymist-query/src/analysis/tyck/syntax.rs index 2b26d685..0545fd2f 100644 --- a/crates/tinymist-query/src/analysis/tyck/syntax.rs +++ b/crates/tinymist-query/src/analysis/tyck/syntax.rs @@ -2,7 +2,7 @@ use super::*; use crate::analysis::ParamAttrs; -use crate::docs::{SignatureDocsT, TypelessParamDocs, UntypedSymbolDocs}; +use crate::docs::{SignatureDocsT, TypelessParamDocs, UntypedDefDocs}; use crate::syntax::{def::*, DocString, VarDoc}; use crate::ty::*; @@ -261,13 +261,13 @@ impl<'a> TypeChecker<'a> { if let Some(base) = base { self.info.var_docs.insert( base.clone(), - Arc::new(UntypedSymbolDocs::Function(Box::new(SignatureDocsT { + Arc::new(UntypedDefDocs::Function(Box::new(SignatureDocsT { docs: docstring.docs.clone().unwrap_or_default(), pos: pos_docs, named: named_docs, rest: rest_docs, ret_ty: (), - def_docs: Default::default(), + hover_docs: Default::default(), }))), ); } diff --git a/crates/tinymist-query/src/docs/mod.rs b/crates/tinymist-query/src/docs/mod.rs index e113fdf9..17fbbcdc 100644 --- a/crates/tinymist-query/src/docs/mod.rs +++ b/crates/tinymist-query/src/docs/mod.rs @@ -7,7 +7,7 @@ mod symbol; mod tidy; use reflexo::path::unix_slash; -use typst::{foundations::Value, syntax::FileId}; +use typst::syntax::FileId; pub use module::*; pub use package::*; @@ -21,13 +21,3 @@ fn file_id_repr(k: FileId) -> String { unix_slash(k.vpath().as_rooted_path()) } } - -fn kind_of(val: &Value) -> DocStringKind { - match val { - Value::Module(_) => DocStringKind::Module, - Value::Type(_) => DocStringKind::Struct, - Value::Func(_) => DocStringKind::Function, - Value::Label(_) => DocStringKind::Reference, - _ => DocStringKind::Constant, - } -} diff --git a/crates/tinymist-query/src/docs/module.rs b/crates/tinymist-query/src/docs/module.rs index d89cebc3..dca2e5b8 100644 --- a/crates/tinymist-query/src/docs/module.rs +++ b/crates/tinymist-query/src/docs/module.rs @@ -1,27 +1,26 @@ //! Module documentation. use std::collections::HashMap; -use std::ops::Range; +use std::sync::Arc; use ecow::{eco_vec, EcoString, EcoVec}; use itertools::Itertools; use serde::{Deserialize, Serialize}; -use typst::diag::{eco_format, StrResult}; -use typst::foundations::{Module, Value}; +use typst::diag::StrResult; use typst::syntax::package::PackageSpec; -use typst::syntax::{FileId, Span}; +use typst::syntax::FileId; use crate::docs::file_id_repr; -use crate::syntax::{find_docs_of, get_non_strict_def_target}; -use crate::upstream::truncated_doc_repr; +use crate::syntax::{Decl, DefKind, Expr, ExprInfo}; +use crate::ty::Interned; use crate::AnalysisContext; -use super::{get_manifest, get_manifest_id, kind_of, DocStringKind, PackageInfo, SymbolDocs}; +use super::{get_manifest_id, DefDocs, PackageInfo}; /// Get documentation of symbols in a package. pub fn package_module_docs(ctx: &mut AnalysisContext, pkg: &PackageInfo) -> StrResult { let toml_id = get_manifest_id(pkg)?; - let manifest = get_manifest(ctx.world(), toml_id)?; + let manifest = ctx.get_manifest(toml_id)?; let entry_point = toml_id.join(&manifest.package.entrypoint); module_docs(ctx, entry_point) @@ -40,11 +39,11 @@ pub fn module_docs(ctx: &mut AnalysisContext, entry_point: FileId) -> StrResult< extras: &mut extras, }; - let src = scan_ctx + let ei = scan_ctx .ctx - .module_by_id(entry_point) - .map_err(|e| eco_format!("failed to get module by id {entry_point:?}: {e:?}"))?; - let mut symbols = scan_ctx.module_sym(eco_vec![], src); + .expr_stage_by_id(entry_point) + .ok_or("entry point not found")?; + let mut symbols = scan_ctx.module_sym(eco_vec![], ei); let module_uses = aliases .into_iter() @@ -70,7 +69,7 @@ pub struct SymbolInfoHead { /// The name of the symbol. pub name: EcoString, /// The kind of the symbol. - pub kind: DocStringKind, + pub kind: DefKind, /// The location (file, start, end) of the symbol. pub loc: Option<(usize, usize, usize)>, /// Is the symbol reexport @@ -80,24 +79,16 @@ pub struct SymbolInfoHead { /// The one-line documentation of the symbol. pub oneliner: Option, /// The raw documentation of the symbol. - pub docs: Option, + pub docs: Option, /// The parsed documentation of the symbol. - pub parsed_docs: Option, + pub parsed_docs: Option, /// The value of the symbol. #[serde(skip)] pub constant: Option, - /// The file owning the symbol. - #[serde(skip)] - pub fid: Option, - /// The span of the symbol. - #[serde(skip)] - pub span: Option, /// The name range of the symbol. - #[serde(skip)] - pub name_range: Option>, /// The value of the symbol. #[serde(skip)] - pub value: Option, + pub decl: Option>, } /// Information about a symbol. @@ -129,11 +120,59 @@ struct ScanSymbolCtx<'a, 'w> { } impl ScanSymbolCtx<'_, '_> { - fn module_sym(&mut self, path: EcoVec<&str>, module: Module) -> SymbolInfo { - let key = module.name().to_owned(); + fn module_sym(&mut self, path: EcoVec<&str>, ei: Arc) -> SymbolInfo { + let name = { + let stem = ei.fid.vpath().as_rooted_path().file_stem(); + stem.and_then(|s| Some(Interned::new_str(s.to_str()?))) + .unwrap_or_default() + }; + let module_decl = Decl::module(name.clone(), ei.fid).into(); let site = Some(self.root); let p = path.clone(); - self.sym(&key, p, site.as_ref(), &Value::Module(module)) + self.sym(&name, p, site.as_ref(), &module_decl, None) + } + + fn expr( + &mut self, + key: &str, + path: EcoVec<&str>, + site: Option<&FileId>, + val: &Expr, + ) -> SymbolInfo { + match val { + Expr::Decl(d) => self.sym(key, path, site, d, Some(val)), + Expr::Ref(r) if r.root.is_some() => { + self.expr(key, path, site, r.root.as_ref().unwrap()) + } + // todo: select + Expr::Select(..) => { + let mut path = path.clone(); + path.push(key); + let head = SymbolInfoHead { + name: key.to_string().into(), + kind: DefKind::Module, + ..Default::default() + }; + SymbolInfo { + head, + children: eco_vec![], + } + } + // v => panic!("unexpected export: {key} -> {v}"), + _ => { + let mut path = path.clone(); + path.push(key); + let head = SymbolInfoHead { + name: key.to_string().into(), + kind: DefKind::Constant, + ..Default::default() + }; + SymbolInfo { + head, + children: eco_vec![], + } + } + } } fn sym( @@ -141,12 +180,13 @@ impl ScanSymbolCtx<'_, '_> { key: &str, path: EcoVec<&str>, site: Option<&FileId>, - val: &Value, + val: &Interned, + expr: Option<&Expr>, ) -> SymbolInfo { - let mut head = create_head(self.ctx, key, val); + let mut head = create_head(self.ctx, key, val, expr); - if !matches!(&val, Value::Module(..)) { - if let Some((span, mod_fid)) = head.span.and_then(Span::id).zip(site) { + if !matches!(val.as_ref(), Decl::Module(..)) { + if let Some((span, mod_fid)) = head.decl.as_ref().and_then(|d| d.file_id()).zip(site) { if span != *mod_fid { head.export_again = true; head.oneliner = head.docs.as_deref().map(oneliner).map(|e| e.to_owned()); @@ -155,8 +195,8 @@ impl ScanSymbolCtx<'_, '_> { } } - let children = match val { - Value::Module(module) => module.file_id().and_then(|fid| { + let children = match val.as_ref() { + Decl::Module(..) => val.file_id().and_then(|fid| { // only generate docs for the same package if fid.package() != self.for_spec { return None; @@ -174,12 +214,15 @@ impl ScanSymbolCtx<'_, '_> { log::debug!("found module: {path:?}"); - let symbols = module.scope().iter(); - let symbols = symbols - .map(|(k, v, _)| { + let ei = self.ctx.expr_stage_by_id(fid)?; + + let symbols = ei + .exports + .iter() + .map(|(k, v)| { let mut path = path.clone(); path.push(k); - self.sym(k, path.clone(), Some(&fid), v) + self.expr(k, path.clone(), Some(&fid), v) }) .collect(); Some(symbols) @@ -188,18 +231,18 @@ impl ScanSymbolCtx<'_, '_> { }; // Insert module that is not exported - if let Some(fid) = head.fid { + if let Some(fid) = head.decl.as_ref().and_then(|d| d.file_id()) { // only generate docs for the same package if fid.package() == self.for_spec { let av = self.aliases.entry(fid).or_default(); if av.is_empty() { - let m = self.ctx.module_by_id(fid); + let src = self.ctx.expr_stage_by_id(fid); let mut path = path.clone(); path.push("-"); path.push(key); log::debug!("found internal module: {path:?}"); - if let Ok(m) = m { + if let Some(m) = src { let msym = self.module_sym(path, m); self.extras.push(msym) } @@ -212,44 +255,24 @@ impl ScanSymbolCtx<'_, '_> { } } -fn create_head(world: &mut AnalysisContext, k: &str, v: &Value) -> SymbolInfoHead { - let kind = kind_of(v); - let (docs, name_range, fid, span) = match v { - Value::Func(f) => { - let mut span = None; - let mut name_range = None; - let docs = None.or_else(|| { - let source = world.source_by_id(f.span().id()?).ok()?; - let node = source.find(f.span())?; - log::debug!("node: {k} -> {:?}", node.parent()); - // use parent of params, todo: reliable way to get the def target - let def = get_non_strict_def_target(node.parent()?.clone())?; - span = Some(def.node().span()); - name_range = def.name_range(); +fn create_head( + ctx: &mut AnalysisContext, + k: &str, + decl: &Interned, + expr: Option<&Expr>, +) -> SymbolInfoHead { + let kind = decl.kind(); - find_docs_of(&source, def) - }); - - let s = span.or(Some(f.span())); - - (docs, name_range, s.and_then(Span::id), s) - } - Value::Module(m) => (None, None, m.file_id(), None), - _ => Default::default(), - }; + let parsed_docs = ctx.def_of_decl(decl).and_then(|def| ctx.def_docs(&def)); + let docs = parsed_docs.as_ref().map(|d| d.docs().clone()); SymbolInfoHead { name: k.to_string().into(), kind, - constant: None.or_else(|| match v { - Value::Func(_) => None, - t => Some(truncated_doc_repr(t)), - }), + constant: expr.map(|e| e.repr()), docs, - name_range, - fid, - span, - value: Some(v.clone()), + parsed_docs, + decl: Some(decl.clone()), ..Default::default() } } diff --git a/crates/tinymist-query/src/docs/package.rs b/crates/tinymist-query/src/docs/package.rs index bc73ab31..e2ba854f 100644 --- a/crates/tinymist-query/src/docs/package.rs +++ b/crates/tinymist-query/src/docs/package.rs @@ -5,21 +5,18 @@ use std::path::PathBuf; use ecow::{EcoString, EcoVec}; use indexmap::IndexSet; use serde::{Deserialize, Serialize}; -use tinymist_world::LspWorld; use typst::diag::{eco_format, StrResult}; -use typst::foundations::Value; use typst::syntax::package::{PackageManifest, PackageSpec}; use typst::syntax::{FileId, Span, VirtualPath}; use typst::World; -use crate::docs::{file_id_repr, module_docs, symbol_docs, SymbolDocs, SymbolsInfo}; -use crate::ty::Ty; +use crate::docs::{file_id_repr, module_docs, DefDocs, SymbolsInfo}; use crate::AnalysisContext; /// Check Package. pub fn check_package(ctx: &mut AnalysisContext, spec: &PackageInfo) -> StrResult<()> { let toml_id = get_manifest_id(spec)?; - let manifest = get_manifest(ctx.world(), toml_id)?; + let manifest = ctx.get_manifest(toml_id)?; let entry_point = toml_id.join(&manifest.package.entrypoint); @@ -33,7 +30,7 @@ pub fn package_docs(ctx: &mut AnalysisContext, spec: &PackageInfo) -> StrResult< let mut md = String::new(); let toml_id = get_manifest_id(spec)?; - let manifest = get_manifest(ctx.world(), toml_id)?; + let manifest = ctx.get_manifest(toml_id)?; let for_spec = toml_id.package().unwrap(); let entry_point = toml_id.join(&manifest.package.entrypoint); @@ -54,7 +51,7 @@ pub fn package_docs(ctx: &mut AnalysisContext, spec: &PackageInfo) -> StrResult< md.push('\n'); md.push('\n'); - let manifest = get_manifest(&ctx.world, toml_id)?; + let manifest = ctx.get_manifest(toml_id)?; let meta = PackageMeta { namespace: spec.namespace.clone(), @@ -84,32 +81,13 @@ pub fn package_docs(ctx: &mut AnalysisContext, spec: &PackageInfo) -> StrResult< .clone() }; - // todo: extend this cache idea for all crate? - #[allow(clippy::mutable_key_type)] - let mut describe_cache = HashMap::::new(); - let mut doc_ty = |ty: Option<&Ty>| { - let ty = ty?; - let short = { - describe_cache - .entry(ty.clone()) - .or_insert_with(|| ty.describe().unwrap_or_else(|| "unknown".to_string())) - .clone() - }; - - Some((short, format!("{ty:?}"))) - }; - while !modules_to_generate.is_empty() { for (parent_ident, sym) in std::mem::take(&mut modules_to_generate) { // parent_ident, symbols let symbols = sym.children; - let module_val = sym.head.value.as_ref().unwrap(); - let module = match module_val { - Value::Module(m) => m, - _ => todo!(), - }; - let fid = module.file_id(); + let module_val = sym.head.decl.as_ref().unwrap(); + let fid = module_val.file_id(); let aka = fid.map(&mut akas).unwrap_or_default(); // It is (primary) known to safe as a part of HTML string, so we don't have to @@ -141,7 +119,8 @@ pub fn package_docs(ctx: &mut AnalysisContext, spec: &PackageInfo) -> StrResult< let _ = writeln!(md, ""); for mut sym in symbols { - let span = sym.head.span.and_then(|v| { + let span = sym.head.decl.as_ref().map(|d| d.span()); + let fid_range = span.and_then(|v| { v.id().and_then(|e| { let fid = file_ids.insert_full(e).0; let src = ctx.source_by_id(e).ok()?; @@ -149,34 +128,42 @@ pub fn package_docs(ctx: &mut AnalysisContext, spec: &PackageInfo) -> StrResult< Some((fid, rng.start, rng.end)) }) }); - let sym_fid = sym.head.fid; - let sym_fid = sym_fid.or_else(|| sym.head.span.and_then(Span::id)).or(fid); - let span = span.or_else(|| { + let sym_fid = sym.head.decl.as_ref().and_then(|d| d.file_id()); + let sym_fid = sym_fid.or_else(|| span.and_then(Span::id)).or(fid); + let span = fid_range.or_else(|| { let fid = sym_fid?; Some((file_ids.insert_full(fid).0, 0, 0)) }); sym.head.loc = span; + // .ok_or_else(|| { + // let err = format!("failed to convert docs in {title}").replace( + // "-->", "—>", // avoid markdown comment + // ); + // log::error!("{err}"); + // err + // }) + let docs = sym.head.parsed_docs.clone(); + // Err(e) => { + // let err = format!("failed to convert docs: {e}").replace( + // "-->", "—>", // avoid markdown comment + // ); + // log::error!("{err}"); + // return Err(err); + // } - let docs = symbol_docs( - ctx, - sym.head.kind, - sym.head.value.as_ref(), - sym.head.docs.as_deref(), - Some(&mut doc_ty), - ); - - let mut convert_err = None; + let convert_err = None::; match &docs { - Ok(docs) => { + Some(docs) => { sym.head.parsed_docs = Some(docs.clone()); sym.head.docs = None; } - Err(e) => { - let err = format!("failed to convert docs in {title}: {e}").replace( - "-->", "—>", // avoid markdown comment - ); - log::error!("{err}"); - convert_err = Some(err); + None => { + // let err = format!("failed to convert docs in {title}: + // {e}").replace( "-->", + // "—>", // avoid markdown comment + // ); + // log::error!("{err}"); + // convert_err = Some(err); } } @@ -188,8 +175,7 @@ pub fn package_docs(ctx: &mut AnalysisContext, spec: &PackageInfo) -> StrResult< let _ = writeln!(md, "### {}: {} in {primary}", sym.head.kind, sym.head.name); if sym.head.export_again { - let sub_fid = sym.head.fid; - if let Some(fid) = sub_fid { + if let Some(fid) = sym_fid { let lnk = if fid.package() == Some(for_spec) { let sub_aka = akas(fid); let sub_primary = sub_aka.first().cloned().unwrap_or_default(); @@ -218,7 +204,7 @@ pub fn package_docs(ctx: &mut AnalysisContext, spec: &PackageInfo) -> StrResult< let head = jbase64(&sym.head); let _ = writeln!(md, ""); - if let Some(SymbolDocs::Function(sig)) = &sym.head.parsed_docs { + if let Some(DefDocs::Function(sig)) = &sym.head.parsed_docs { let _ = writeln!(md, ""); let _ = writeln!(md, "```typc"); let _ = write!(md, "let {}", sym.head.name); @@ -238,7 +224,7 @@ pub fn package_docs(ctx: &mut AnalysisContext, spec: &PackageInfo) -> StrResult< } (Some(docs), _) => { let _ = writeln!(md, "{}", remove_list_annotations(docs.docs())); - if let SymbolDocs::Function(f) = docs { + if let DefDocs::Function(f) = docs { for param in f.pos.iter().chain(f.named.values()).chain(f.rest.as_ref()) { let _ = writeln!(md, "", param.name); @@ -273,9 +259,8 @@ pub fn package_docs(ctx: &mut AnalysisContext, spec: &PackageInfo) -> StrResult< } if !sym.children.is_empty() { - let sub_fid = sym.head.fid; - log::debug!("sub_fid: {sub_fid:?}"); - match sub_fid { + log::debug!("sub_fid: {sym_fid:?}"); + match sym_fid { Some(fid) => { let aka = akas(fid); let primary = aka.first().cloned().unwrap_or_default(); @@ -352,7 +337,7 @@ pub fn get_manifest_id(spec: &PackageInfo) -> StrResult { } /// Parses the manifest of the package located at `package_path`. -pub fn get_manifest(world: &LspWorld, toml_id: FileId) -> StrResult { +pub fn get_manifest(world: &dyn World, toml_id: FileId) -> StrResult { let toml_data = world .file(toml_id) .map_err(|err| eco_format!("failed to read package manifest ({})", err))?; diff --git a/crates/tinymist-query/src/docs/symbol.rs b/crates/tinymist-query/src/docs/symbol.rs index b09c51d3..50b2887e 100644 --- a/crates/tinymist-query/src/docs/symbol.rs +++ b/crates/tinymist-query/src/docs/symbol.rs @@ -7,64 +7,32 @@ use parking_lot::Mutex; use serde::{Deserialize, Serialize}; use tinymist_world::base::{EntryState, ShadowApi, TaskInputs}; use tinymist_world::LspWorld; -use typst::foundations::{Bytes, Value}; -use typst::syntax::LinkedNode; +use typst::foundations::Bytes; use typst::{ diag::StrResult, - syntax::{FileId, VirtualPath}, + syntax::{FileId, Span, VirtualPath}, }; use super::tidy::*; -use crate::analysis::{ParamAttrs, ParamSpec, Signature, ToFunc}; +use crate::analysis::{ParamAttrs, ParamSpec, Signature}; use crate::docs::library; +use crate::prelude::*; +use crate::ty::Ty; use crate::ty::{DocSource, Interned}; use crate::upstream::plain_docs_sentence; -use crate::{ty::Ty, AnalysisContext}; type TypeRepr = Option<(/* short */ String, /* long */ String)>; type ShowTypeRepr<'a> = &'a mut dyn FnMut(Option<&Ty>) -> TypeRepr; -/// Kind of a docstring. -#[derive(Debug, Default, Clone, Copy, Hash, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub enum DocStringKind { - /// A docstring for a any constant. - #[default] - Constant, - /// A docstring for a function. - Function, - /// A docstring for a variable. - Variable, - /// A docstring for a module. - Module, - /// A docstring for a struct. - Struct, - /// A docstring for a reference. - Reference, -} - -impl fmt::Display for DocStringKind { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::Constant => write!(f, "constant"), - Self::Function => write!(f, "function"), - Self::Variable => write!(f, "variable"), - Self::Module => write!(f, "module"), - Self::Struct => write!(f, "struct"), - Self::Reference => write!(f, "reference"), - } - } -} - /// Documentation about a symbol (without type information). -pub type UntypedSymbolDocs = SymbolDocsT<()>; +pub type UntypedDefDocs = DefDocsT<()>; /// Documentation about a symbol. -pub type SymbolDocs = SymbolDocsT; +pub type DefDocs = DefDocsT; /// Documentation about a symbol. #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "kind")] -pub enum SymbolDocsT { +pub enum DefDocsT { /// Documentation about a function. #[serde(rename = "func")] Function(Box>), @@ -82,7 +50,7 @@ pub enum SymbolDocsT { }, } -impl SymbolDocsT { +impl DefDocsT { /// Get the markdown representation of the documentation. pub fn docs(&self) -> &EcoString { match self { @@ -94,37 +62,14 @@ impl SymbolDocsT { } } -pub(crate) fn symbol_docs( - ctx: &mut AnalysisContext, - kind: DocStringKind, - sym_value: Option<&Value>, - docs: Option<&str>, - doc_ty: Option, -) -> Result { - let signature = - sym_value.and_then(|e| signature_docs(&ctx.signature_dyn(e.to_func()?), doc_ty)); - if let Some(signature) = signature { - return Ok(SymbolDocs::Function(Box::new(signature))); - } - - if let Some(docs) = &docs { - match convert_docs(ctx.world(), docs) { - Ok(content) => { - let docs = identify_docs(kind, content.clone()) - .unwrap_or(SymbolDocs::Plain { docs: content }); - return Ok(docs); - } - Err(e) => { - let err = format!("failed to convert docs: {e}").replace( - "-->", "—>", // avoid markdown comment - ); - log::error!("{err}"); - return Err(err); - } +impl DefDocs { + /// Get full documentation for the signature. + pub fn hover_docs(&self) -> EcoString { + match self { + DefDocs::Function(docs) => docs.hover_docs().clone(), + _ => plain_docs_sentence(self.docs()), } } - - Ok(SymbolDocs::Plain { docs: "".into() }) } /// Describes a primary function signature. @@ -142,20 +87,20 @@ pub struct SignatureDocsT { pub ret_ty: T, /// The full documentation for the signature. #[serde(skip)] - pub def_docs: OnceLock, + pub hover_docs: OnceLock, } impl SignatureDocsT { /// Get full documentation for the signature. - pub fn def_docs(&self) -> &String { - self.def_docs - .get_or_init(|| plain_docs_sentence(&format!("{}", DefDocs(self))).into()) + pub fn hover_docs(&self) -> &EcoString { + self.hover_docs + .get_or_init(|| plain_docs_sentence(&format!("{}", SigDefDocs(self)))) } } -struct DefDocs<'a>(&'a SignatureDocs); +struct SigDefDocs<'a>(&'a SignatureDocs); -impl fmt::Display for DefDocs<'_> { +impl fmt::Display for SigDefDocs<'_> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let docs = self.0; let base_docs = docs.docs.trim(); @@ -318,10 +263,10 @@ fn format_ty(ty: Option<&Ty>, doc_ty: Option<&mut ShowTypeRepr>) -> TypeRepr { } } -pub(crate) fn variable_docs(ctx: &mut AnalysisContext, pos: &LinkedNode) -> Option { - let source = ctx.source_by_id(pos.span().id()?).ok()?; +pub(crate) fn variable_docs(ctx: &mut LocalContext, pos: Span) -> Option { + let source = ctx.source_by_id(pos.id()?).ok()?; let type_info = ctx.type_check(&source); - let ty = type_info.type_of_span(pos.span())?; + let ty = type_info.type_of_span(pos)?; // todo multiple sources let mut srcs = ty.sources(); @@ -396,7 +341,7 @@ pub(crate) fn signature_docs( named, rest, ret_ty, - def_docs: OnceLock::new(), + hover_docs: OnceLock::new(), }) } @@ -430,14 +375,3 @@ pub(crate) fn convert_docs(world: &LspWorld, content: &str) -> StrResult StrResult { - match kind { - DocStringKind::Function => Err(eco_format!("must be already handled")), - DocStringKind::Variable => identify_var_docs(docs).map(SymbolDocs::Variable), - DocStringKind::Constant => identify_var_docs(docs).map(SymbolDocs::Variable), - DocStringKind::Module => identify_tidy_module_docs(docs).map(SymbolDocs::Module), - DocStringKind::Struct => Ok(SymbolDocs::Plain { docs }), - DocStringKind::Reference => Ok(SymbolDocs::Plain { docs }), - } -} diff --git a/crates/tinymist-query/src/goto_definition.rs b/crates/tinymist-query/src/goto_definition.rs index c3fc2fdb..a2cdccaf 100644 --- a/crates/tinymist-query/src/goto_definition.rs +++ b/crates/tinymist-query/src/goto_definition.rs @@ -35,7 +35,7 @@ impl StatefulRequest for GotoDefinitionRequest { let deref_target = ctx.deref_syntax_at(&source, self.position, 1)?; let origin_selection_range = ctx.to_lsp_range(deref_target.node().range(), &source); - let def = ctx.definition(&source, doc.as_ref(), deref_target)?; + let def = ctx.def_of_syntax(&source, doc.as_ref(), deref_target)?; let (fid, def_range) = def.def_at(ctx.shared())?; diff --git a/crates/tinymist-query/src/hover.rs b/crates/tinymist-query/src/hover.rs index 0637238b..6e4cb2a7 100644 --- a/crates/tinymist-query/src/hover.rs +++ b/crates/tinymist-query/src/hover.rs @@ -1,15 +1,15 @@ -use core::fmt; +use core::fmt::Write; use typst_shim::syntax::LinkedNodeExt; use crate::{ analysis::{get_link_exprs_in, Definition}, - docs::SignatureDocs, + docs::DefDocs, jump_from_cursor, prelude::*, - syntax::{find_docs_before, get_deref_target, Decl}, + syntax::{get_deref_target, Decl, DefKind}, ty::PathPreference, - upstream::{expr_tooltip, plain_docs_sentence, route_of_value, truncated_repr, Tooltip}, + upstream::{expr_tooltip, route_of_value, truncated_repr, Tooltip}, LspHoverContents, StatefulRequest, }; @@ -223,10 +223,8 @@ fn def_tooltip( cursor: usize, ) -> Option { let leaf = LinkedNode::new(source.root()).leaf_at_compat(cursor)?; - let deref_target = get_deref_target(leaf.clone(), cursor)?; - - let def = ctx.definition(source, document, deref_target.clone())?; + let def = ctx.def_of_syntax(source, document, deref_target.clone())?; let mut results = vec![]; let mut actions = vec![]; @@ -250,76 +248,66 @@ fn def_tooltip( Some(LspHoverContents::Array(results)) } - Func(..) | Closure(..) => { - let sig = ctx.signature_docs(&def); + _ => { + let sym_docs = ctx.def_docs(&def); - results.push(MarkedString::LanguageString(LanguageString { - language: "typc".to_owned(), - value: format!( - "let {name}{params}{result};", - name = def.name(), - params = ParamTooltip(sig.as_ref()), - result = - ResultTooltip(def.name(), sig.as_ref().and_then(|sig| sig.ret_ty.as_ref())) - ), - })); - - if let Some(doc) = sig { - results.push(MarkedString::String(doc.def_docs().into())); - } - - if let Some(link) = ExternalDocLink::get(ctx, &def) { - actions.push(link); - } - - render_actions(&mut results, actions); - Some(LspHoverContents::Array(results)) - } - ModuleAlias(..) | Module(..) | PathStem(..) | ImportPath(..) | IncludePath(..) => { - let id = def.decl.file_id()?; - let src = ctx.source_by_id(id).ok()?; - let ei = ctx.expr_stage(&src); - let docs = ei.module_docstring.docs.clone()?; - results.push(MarkedString::String(docs.as_str().into())); - Some(LspHoverContents::Array(results)) - } - IdentRef(..) | ImportAlias(..) | Import(..) | Var(..) => { - let deref_node = deref_target.node(); - let sig = ctx.variable_docs(deref_target.node()); - - // todo: check sensible length, value highlighting - if let Some(values) = expr_tooltip(ctx.world(), deref_node) { - match values { - Tooltip::Text(values) => { - results.push(MarkedString::String(values.into())); - } - Tooltip::Code(values) => { - results.push(MarkedString::LanguageString(LanguageString { - language: "typc".to_owned(), - value: values.into(), - })); + if matches!(def.decl.kind(), DefKind::Variable | DefKind::Constant) { + // todo: check sensible length, value highlighting + if let Some(values) = expr_tooltip(ctx.world(), deref_target.node()) { + match values { + Tooltip::Text(values) => { + results.push(MarkedString::String(values.into())); + } + Tooltip::Code(values) => { + results.push(MarkedString::LanguageString(LanguageString { + language: "typc".to_owned(), + value: values.into(), + })); + } } } } - results.push(MarkedString::LanguageString(LanguageString { - language: "typc".to_owned(), - value: format!( - "let {name}{result};", - name = def.name(), - result = ResultTooltip( - def.name(), - sig.as_ref().and_then(|sig| sig.return_ty.as_ref()) - ) - ), - })); + // todo: hover with `with_stack` - if let Some(doc) = sig { - results.push(MarkedString::String(doc.def_docs().clone())); - } else if let Some(doc) = DocTooltip::get(ctx, &def) { - results.push(MarkedString::String(doc)); + if matches!( + def.decl.kind(), + DefKind::Function | DefKind::Variable | DefKind::Constant + ) && !def.name().is_empty() + { + results.push(MarkedString::LanguageString(LanguageString { + language: "typc".to_owned(), + value: { + let mut type_doc = String::new(); + type_doc.push_str("let "); + type_doc.push_str(def.name()); + + match &sym_docs { + Some(DefDocs::Variable(docs)) => { + push_result_ty(def.name(), docs.return_ty.as_ref(), &mut type_doc); + } + Some(DefDocs::Function(docs)) => { + let _ = docs.print(&mut type_doc); + push_result_ty(def.name(), docs.ret_ty.as_ref(), &mut type_doc); + } + _ => {} + } + type_doc.push(';'); + type_doc + }, + })); } + if let Some(doc) = sym_docs { + results.push(MarkedString::String(doc.hover_docs().into())); + } + + // if let Some(doc) = sig { + // results.push(MarkedString::String(doc.def_docs().clone())); + // } else if let Some(doc) = DocTooltip::get(ctx, &def) { + // results.push(MarkedString::String(doc)); + // } + if let Some(link) = ExternalDocLink::get(ctx, &def) { actions.push(link); } @@ -327,11 +315,20 @@ fn def_tooltip( render_actions(&mut results, actions); Some(LspHoverContents::Array(results)) } - Pattern(..) | Docs(..) | Generated(..) | Constant(..) | ContentRef(..) | StrName(..) - | ModuleImport(..) | Content(..) | Spread(..) => None, } } +fn push_result_ty(name: &str, ty_repr: Option<&(String, String)>, type_doc: &mut String) { + let Some((short, _)) = ty_repr else { + return; + }; + if short == name { + return; + } + + let _ = write!(type_doc, " = {short}"); +} + fn render_actions(results: &mut Vec, actions: Vec) { if actions.is_empty() { return; @@ -371,33 +368,6 @@ fn render_actions(results: &mut Vec, actions: Vec) { results.push(MarkedString::String(g)); } -// todo: hover with `with_stack` -struct ParamTooltip<'a>(Option<&'a SignatureDocs>); - -impl fmt::Display for ParamTooltip<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Some(sig) = self.0 else { - return Ok(()); - }; - sig.print(f) - } -} - -struct ResultTooltip<'a>(&'a str, Option<&'a (String, String)>); - -impl fmt::Display for ResultTooltip<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Some((short, _)) = self.1 else { - return Ok(()); - }; - if short == self.0 { - return Ok(()); - } - - write!(f, " = {short}") - } -} - struct ExternalDocLink; impl ExternalDocLink { @@ -452,54 +422,6 @@ impl ExternalDocLink { } } -pub(crate) struct DocTooltip; - -impl DocTooltip { - pub fn get(ctx: &mut AnalysisContext, def: &Definition) -> Option { - self::DocTooltip::get_inner(ctx, def).map(|s| "\n\n".to_owned() + &s) - } - - fn get_inner(ctx: &mut AnalysisContext, def: &Definition) -> Option { - let value = def.value(); - if matches!(value, Some(Value::Func(..))) { - if let Some(builtin) = Self::builtin_func_tooltip(def) { - return Some(plain_docs_sentence(builtin).into()); - } - }; - - let (fid, def_range) = def.def_at(ctx.shared()).clone()?; - - let src = ctx.source_by_id(fid).ok()?; - find_docs_before(&src, def_range.start) - } -} - -impl DocTooltip { - fn builtin_func_tooltip(def: &Definition) -> Option<&'_ str> { - let value = def.value(); - let Some(Value::Func(func)) = &value else { - return None; - }; - - use typst::foundations::func::Repr; - let mut func = func; - let docs = 'search: loop { - match func.inner() { - Repr::Native(n) => break 'search n.docs, - Repr::Element(e) => break 'search e.docs(), - Repr::With(w) => { - func = &w.0; - } - Repr::Closure(..) => { - return None; - } - } - }; - - Some(docs) - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/tinymist-query/src/prepare_rename.rs b/crates/tinymist-query/src/prepare_rename.rs index 29f18858..16fb6019 100644 --- a/crates/tinymist-query/src/prepare_rename.rs +++ b/crates/tinymist-query/src/prepare_rename.rs @@ -47,7 +47,7 @@ impl StatefulRequest for PrepareRenameRequest { } let origin_selection_range = ctx.to_lsp_range(deref_target.node().range(), &source); - let def = ctx.definition(&source, doc.as_ref(), deref_target.clone())?; + let def = ctx.def_of_syntax(&source, doc.as_ref(), deref_target.clone())?; let (name, range) = prepare_renaming(ctx, &deref_target, &def)?; diff --git a/crates/tinymist-query/src/references.rs b/crates/tinymist-query/src/references.rs index f8df9f97..56030862 100644 --- a/crates/tinymist-query/src/references.rs +++ b/crates/tinymist-query/src/references.rs @@ -53,7 +53,7 @@ pub(crate) fn find_references( } }; - let def = ctx.definition(source, doc, target)?; + let def = ctx.def_of_syntax(source, doc, target)?; let worker = ReferencesWorker { ctx: ctx.fork_for_search(), @@ -103,14 +103,13 @@ impl<'a, 'w> ReferencesWorker<'a, 'w> { fn file(&mut self, ref_fid: TypstFileId) -> Option<()> { log::debug!("references: file: {ref_fid:?}"); - let ref_source = self.ctx.ctx.source_by_id(ref_fid).ok()?; - let expr_info = self.ctx.ctx.expr_stage(&ref_source); + let ei = self.ctx.ctx.expr_stage_by_id(ref_fid)?; let uri = self.ctx.ctx.uri_for_id(ref_fid).ok()?; - let t = expr_info.get_refs(self.def.decl.clone()); - self.push_idents(&ref_source, &uri, t); + let t = ei.get_refs(self.def.decl.clone()); + self.push_idents(&ei.source, &uri, t); - if expr_info.is_exported(&self.def.decl) { + if ei.is_exported(&self.def.decl) { self.ctx.push_dependents(ref_fid); } diff --git a/crates/tinymist-query/src/rename.rs b/crates/tinymist-query/src/rename.rs index a93ef321..ea7ed07a 100644 --- a/crates/tinymist-query/src/rename.rs +++ b/crates/tinymist-query/src/rename.rs @@ -41,7 +41,7 @@ impl StatefulRequest for RenameRequest { let source = ctx.source_by_path(&self.path).ok()?; let deref_target = ctx.deref_syntax_at(&source, self.position, 1)?; - let def = ctx.definition(&source, doc.as_ref(), deref_target.clone())?; + let def = ctx.def_of_syntax(&source, doc.as_ref(), deref_target.clone())?; prepare_renaming(ctx, &deref_target, &def)?; diff --git a/crates/tinymist-query/src/signature_help.rs b/crates/tinymist-query/src/signature_help.rs index 2e9cf894..06a1b717 100644 --- a/crates/tinymist-query/src/signature_help.rs +++ b/crates/tinymist-query/src/signature_help.rs @@ -3,9 +3,11 @@ use typst_shim::syntax::LinkedNodeExt; use crate::{ adt::interner::Interned, + analysis::Definition, prelude::*, - syntax::{get_check_target, get_deref_target, CheckTarget, ParamTarget}, - DocTooltip, LspParamInfo, SemanticRequest, + syntax::{find_docs_before, get_check_target, get_deref_target, CheckTarget, ParamTarget}, + upstream::plain_docs_sentence, + LspParamInfo, SemanticRequest, }; /// The [`textDocument/signatureHelp`] request is sent from the client to the @@ -40,7 +42,7 @@ impl SemanticRequest for SignatureHelpRequest { let deref_target = get_deref_target(callee, cursor)?; - let def_link = ctx.definition(&source, None, deref_target)?; + let def_link = ctx.def_of_syntax(&source, None, deref_target)?; let documentation = DocTooltip::get(ctx, &def_link) .as_deref() @@ -59,7 +61,7 @@ impl SemanticRequest for SignatureHelpRequest { function = &inner.0; } - let sig = ctx.signature_dyn(function.clone()); + let sig = ctx.sig_of_func(function.clone()); log::debug!("got signature {sig:?}"); @@ -145,6 +147,54 @@ impl SemanticRequest for SignatureHelpRequest { } } +pub(crate) struct DocTooltip; + +impl DocTooltip { + pub fn get(ctx: &mut AnalysisContext, def: &Definition) -> Option { + self::DocTooltip::get_inner(ctx, def).map(|s| "\n\n".to_owned() + &s) + } + + fn get_inner(ctx: &mut AnalysisContext, def: &Definition) -> Option { + let value = def.value(); + if matches!(value, Some(Value::Func(..))) { + if let Some(builtin) = Self::builtin_func_tooltip(def) { + return Some(plain_docs_sentence(builtin).into()); + } + }; + + let (fid, def_range) = def.def_at(ctx.shared()).clone()?; + + let src = ctx.source_by_id(fid).ok()?; + find_docs_before(&src, def_range.start) + } +} + +impl DocTooltip { + fn builtin_func_tooltip(def: &Definition) -> Option<&'_ str> { + let value = def.value(); + let Some(Value::Func(func)) = &value else { + return None; + }; + + use typst::foundations::func::Repr; + let mut func = func; + let docs = 'search: loop { + match func.inner() { + Repr::Native(n) => break 'search n.docs, + Repr::Element(e) => break 'search e.docs(), + Repr::With(w) => { + func = &w.0; + } + Repr::Closure(..) => { + return None; + } + } + }; + + Some(docs) + } +} + fn markdown_docs(docs: &str) -> Documentation { Documentation::MarkupContent(MarkupContent { kind: MarkupKind::Markdown, diff --git a/crates/tinymist-query/src/syntax/def.rs b/crates/tinymist-query/src/syntax/def.rs index 555957eb..7f1ab50f 100644 --- a/crates/tinymist-query/src/syntax/def.rs +++ b/crates/tinymist-query/src/syntax/def.rs @@ -2,6 +2,7 @@ use core::fmt; use std::{collections::BTreeMap, ops::Range}; use reflexo_typst::package::PackageSpec; +use serde::{Deserialize, Serialize}; use tinymist_derive::DeclEnum; use typst::{ foundations::{Element, Func, Module, Type, Value}, @@ -71,6 +72,12 @@ pub enum Expr { Star, } impl Expr { + pub(crate) fn repr(&self) -> EcoString { + let mut s = EcoString::new(); + let _ = ExprFormatter::new(&mut s, true).write_expr(self); + s + } + pub(crate) fn span(&self) -> Span { match self { Expr::Decl(d) => d.span(), @@ -171,6 +178,38 @@ fn select_of(source: Interned, name: Interned) -> Expr { Expr::Type(Ty::Select(SelectTy::new(source, name))) } +/// Kind of a definition. +#[derive(Debug, Default, Clone, Copy, Hash, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum DefKind { + /// A definition for some constant. + #[default] + Constant, + /// A definition for some function. + Function, + /// A definition for some variable. + Variable, + /// A definition for some module. + Module, + /// A definition for some struct. + Struct, + /// A definition for some reference. + Reference, +} + +impl fmt::Display for DefKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Constant => write!(f, "constant"), + Self::Function => write!(f, "function"), + Self::Variable => write!(f, "variable"), + Self::Module => write!(f, "module"), + Self::Struct => write!(f, "struct"), + Self::Reference => write!(f, "reference"), + } + } +} + pub type DeclExpr = Interned; #[derive(Clone, PartialEq, Eq, Hash, DeclEnum)] @@ -369,6 +408,21 @@ impl Decl { ) } + pub fn kind(&self) -> DefKind { + use Decl::*; + match self { + ModuleAlias(..) | Module(..) | PathStem(..) | ImportPath(..) | IncludePath(..) => { + DefKind::Module + } + // Type(_) => DocStringKind::Struct, + Func(..) | Closure(..) => DefKind::Function, + Label(..) | BibEntry(..) | ContentRef(..) => DefKind::Reference, + IdentRef(..) | ImportAlias(..) | Import(..) | Var(..) => DefKind::Variable, + Pattern(..) | Docs(..) | Generated(..) | Constant(..) | StrName(..) + | ModuleImport(..) | Content(..) | Spread(..) => DefKind::Constant, + } + } + pub fn file_id(&self) -> Option { match self { Self::Module(ModuleDecl { fid, .. }) => Some(*fid), diff --git a/crates/tinymist-query/src/syntax/docs.rs b/crates/tinymist-query/src/syntax/docs.rs index 2b7d515d..b433b120 100644 --- a/crates/tinymist-query/src/syntax/docs.rs +++ b/crates/tinymist-query/src/syntax/docs.rs @@ -10,10 +10,10 @@ use crate::{ analysis::SharedContext, docs::{ convert_docs, identify_func_docs, identify_tidy_module_docs, identify_var_docs, - DocStringKind, UntypedSymbolDocs, VarDocsT, + UntypedDefDocs, VarDocsT, }, prelude::*, - syntax::Decl, + syntax::{Decl, DefKind}, ty::{BuiltinTy, Interned, PackageId, SigTy, StrRef, Ty, TypeBounds, TypeVar, TypeVarBounds}, }; @@ -62,8 +62,8 @@ pub struct VarDoc { impl VarDoc { /// Convert the variable doc to an untyped version - pub fn to_untyped(&self) -> Arc { - Arc::new(UntypedSymbolDocs::Variable(VarDocsT { + pub fn to_untyped(&self) -> Arc { + Arc::new(UntypedDefDocs::Variable(VarDocsT { docs: self.docs.clone(), return_ty: (), def_docs: OnceLock::new(), @@ -75,7 +75,7 @@ pub(crate) fn compute_docstring( ctx: &Arc, fid: TypstFileId, docs: String, - kind: DocStringKind, + kind: DefKind, ) -> Option { let checker = DocsChecker { fid, @@ -86,12 +86,12 @@ pub(crate) fn compute_docstring( next_id: 0, }; match kind { - DocStringKind::Function => checker.check_func_docs(docs), - DocStringKind::Variable => checker.check_var_docs(docs), - DocStringKind::Module => checker.check_module_docs(docs), - DocStringKind::Constant => None, - DocStringKind::Struct => None, - DocStringKind::Reference => None, + DefKind::Function => checker.check_func_docs(docs), + DefKind::Variable => checker.check_var_docs(docs), + DefKind::Module => checker.check_module_docs(docs), + DefKind::Constant => None, + DefKind::Struct => None, + DefKind::Reference => None, } } diff --git a/crates/tinymist-query/src/syntax/expr.rs b/crates/tinymist-query/src/syntax/expr.rs index 8e0a27db..77fa6526 100644 --- a/crates/tinymist-query/src/syntax/expr.rs +++ b/crates/tinymist-query/src/syntax/expr.rs @@ -15,9 +15,8 @@ use typst::{ use crate::{ analysis::{QueryStatGuard, SharedContext}, - docs::DocStringKind, prelude::*, - syntax::find_module_level_docs, + syntax::{find_module_level_docs, DefKind}, ty::{BuiltinTy, InsTy, Interned, Ty}, }; @@ -82,7 +81,7 @@ pub(crate) fn expr_of( let module_docstring = Arc::new( find_module_level_docs(&source) - .and_then(|docs| compute_docstring(&ctx, source.id(), docs, DocStringKind::Module)) + .and_then(|docs| compute_docstring(&ctx, source.id(), docs, DefKind::Module)) .unwrap_or_default(), ); @@ -275,7 +274,7 @@ impl<'a> ExprWorker<'a> { } } - fn check_docstring(&mut self, decl: &DeclExpr, kind: DocStringKind) { + fn check_docstring(&mut self, decl: &DeclExpr, kind: DefKind) { if let Some(docs) = self.comment_matcher.collect() { let docstring = compute_docstring(&self.ctx, self.fid, docs, kind); if let Some(docstring) = docstring { @@ -427,7 +426,7 @@ impl<'a> ExprWorker<'a> { let span = p.span(); let decl = Decl::pattern(span).into(); - self.check_docstring(&decl, DocStringKind::Variable); + self.check_docstring(&decl, DefKind::Variable); let pattern = self.check_pattern(p); Expr::Let(Interned::new(LetExpr { span, @@ -443,7 +442,7 @@ impl<'a> ExprWorker<'a> { Some(name) => Decl::func(name).into(), None => Decl::closure(typed.span()).into(), }; - self.check_docstring(&decl, DocStringKind::Function); + self.check_docstring(&decl, DefKind::Function); self.resolve_as(Decl::as_def(&decl, None)); let (params, body) = self.with_scope(|this| { diff --git a/crates/tinymist-query/src/ty/def.rs b/crates/tinymist-query/src/ty/def.rs index 79ebac23..9c77a483 100644 --- a/crates/tinymist-query/src/ty/def.rs +++ b/crates/tinymist-query/src/ty/def.rs @@ -21,7 +21,7 @@ use super::PackageId; use crate::{ adt::{interner::impl_internable, snapshot_map}, analysis::BuiltinTy, - docs::UntypedSymbolDocs, + docs::UntypedDefDocs, syntax::{DeclExpr, UnaryOp}, }; @@ -962,7 +962,7 @@ pub struct TypeScheme { /// The typing on definitions pub vars: FxHashMap, /// The checked documentation of definitions - pub var_docs: FxHashMap>, + pub var_docs: FxHashMap>, /// The local binding of the type variable pub local_binds: snapshot_map::SnapshotMap, /// The typing on syntax structures diff --git a/crates/tinymist-query/src/ty/describe.rs b/crates/tinymist-query/src/ty/describe.rs index f4a26bd9..36e2551c 100644 --- a/crates/tinymist-query/src/ty/describe.rs +++ b/crates/tinymist-query/src/ty/describe.rs @@ -26,6 +26,21 @@ impl Ty { let mut worker = TypeDescriber::default(); worker.describe_root(self) } + + // todo: extend this cache idea for all crate? + // #[allow(clippy::mutable_key_type)] + // let mut describe_cache = HashMap::::new(); + // let doc_ty = |ty: Option<&Ty>| { + // let ty = ty?; + // let short = { + // describe_cache + // .entry(ty.clone()) + // .or_insert_with(|| ty.describe().unwrap_or_else(|| + // "unknown".to_string())) .clone() + // }; + + // Some((short, format!("{ty:?}"))) + // }; } #[derive(Default)] diff --git a/crates/tinymist-query/src/upstream/complete/ext.rs b/crates/tinymist-query/src/upstream/complete/ext.rs index d5c9a422..00a2a3b0 100644 --- a/crates/tinymist-query/src/upstream/complete/ext.rs +++ b/crates/tinymist-query/src/upstream/complete/ext.rs @@ -464,7 +464,7 @@ fn describe_value(ctx: &mut AnalysisContext, v: &Value) -> EcoString { f = &with_f.0; } - let sig = ctx.signature_dyn(f.clone()); + let sig = ctx.sig_of_func(f.clone()); sig.primary() .ty() .describe() @@ -582,7 +582,7 @@ pub fn param_completions<'a>( let pos_index = param_index_at_leaf(&ctx.leaf, &func, args).map(|i| if this.is_some() { i + 1 } else { i }); - let signature = ctx.ctx.signature_dyn(func.clone()); + let signature = ctx.ctx.sig_of_func(func.clone()); let leaf_type = ctx.ctx.literal_type_of_node(ctx.leaf.clone()); log::debug!("pos_param_completion_by_type: {:?}", leaf_type); @@ -998,7 +998,7 @@ pub fn named_param_value_completions<'a>( func = f.0.clone(); } - let signature = ctx.ctx.signature_dyn(func.clone()); + let signature = ctx.ctx.sig_of_func(func.clone()); let primary_sig = signature.primary(); diff --git a/crates/tinymist-query/src/upstream/mod.rs b/crates/tinymist-query/src/upstream/mod.rs index 2411c7a4..3aae1b4c 100644 --- a/crates/tinymist-query/src/upstream/mod.rs +++ b/crates/tinymist-query/src/upstream/mod.rs @@ -424,11 +424,6 @@ pub fn truncated_repr(value: &Value) -> EcoString { truncated_repr_::<_10MB>(value) } -pub fn truncated_doc_repr(value: &Value) -> EcoString { - const _128B: usize = 128; - truncated_repr_::<_128B>(value) -} - /// Run a function with a VM instance in the world pub fn with_vm(world: Tracked, f: impl FnOnce(&mut typst::eval::Vm) -> T) -> T { use comemo::Track; diff --git a/tests/e2e/main.rs b/tests/e2e/main.rs index b90e6390..9b62c6dd 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:1cd09a6f0362384cafcbf632da1a1d0"); + insta::assert_snapshot!(hash, @"siphash128_13:e30e4c00579d6dea2554624283161132"); } }