mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 05:05:00 +00:00
dev: merge signature docs and rest docs (#685)
* dev: deduplicate code * dev: merge signature docs and rest docs * dev: change struct of the `DocString` * dev: improve structure of SymbolDocs
This commit is contained in:
parent
d121e8279d
commit
b9da92175e
12 changed files with 522 additions and 455 deletions
|
@ -130,7 +130,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
var
|
||||
}
|
||||
|
||||
fn get_var(&mut self, s: Span, r: IdentRef) -> Option<Ty> {
|
||||
fn get_var(&mut self, s: Span, r: IdentRef) -> Option<Interned<TypeVar>> {
|
||||
let def_id = self.get_def_id(s, &r)?;
|
||||
|
||||
// todo: false positive of clippy
|
||||
|
@ -150,9 +150,9 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
);
|
||||
}
|
||||
|
||||
let var = self.info.vars.get_mut(&def_id).unwrap();
|
||||
TypeScheme::witness_(s, var.as_type(), &mut self.info.mapping);
|
||||
Some(var.as_type())
|
||||
let var = self.info.vars.get(&def_id).unwrap().var.clone();
|
||||
TypeScheme::witness_(s, Ty::Var(var.clone()), &mut self.info.mapping);
|
||||
Some(var)
|
||||
}
|
||||
|
||||
fn import_ty(&mut self, def_id: DefId) -> Option<Ty> {
|
||||
|
|
|
@ -14,7 +14,7 @@ use super::*;
|
|||
const DOC_VARS: u64 = 0;
|
||||
|
||||
impl<'a, 'w> TypeChecker<'a, 'w> {
|
||||
pub fn check_func_docs(&mut self, root: &LinkedNode) -> Option<DocString> {
|
||||
pub fn check_func_docs(&mut self, root: &LinkedNode) -> Option<Arc<DocString>> {
|
||||
let closure = root.cast::<ast::Closure>()?;
|
||||
let documenting_id = closure
|
||||
.name()
|
||||
|
@ -23,7 +23,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
self.check_docstring(root, DocStringKind::Function, documenting_id)
|
||||
}
|
||||
|
||||
pub fn check_var_docs(&mut self, root: &LinkedNode) -> Option<DocString> {
|
||||
pub fn check_var_docs(&mut self, root: &LinkedNode) -> Option<Arc<DocString>> {
|
||||
let lb = root.cast::<ast::LetBinding>()?;
|
||||
let first = lb.kind().bindings();
|
||||
let documenting_id = first
|
||||
|
@ -38,35 +38,40 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
root: &LinkedNode,
|
||||
kind: DocStringKind,
|
||||
base_id: DefId,
|
||||
) -> Option<DocString> {
|
||||
) -> Option<Arc<DocString>> {
|
||||
// todo: cache docs capture
|
||||
// use parent of params, todo: reliable way to get the def target
|
||||
let def = get_non_strict_def_target(root.clone())?;
|
||||
let docs = find_docs_of(&self.source, def)?;
|
||||
|
||||
let docstring = self.ctx.compute_docstring(root.span().id()?, docs, kind)?;
|
||||
Some(docstring.take().rename_based_on(base_id, self))
|
||||
let res = Arc::new(docstring.take().rename_based_on(base_id, self));
|
||||
self.info.var_docs.insert(base_id, res.clone());
|
||||
Some(res)
|
||||
}
|
||||
}
|
||||
|
||||
/// The documentation string of an item
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct DocString {
|
||||
pub struct DocString {
|
||||
/// The documentation of the item
|
||||
pub docs: Option<String>,
|
||||
pub docs: Option<EcoString>,
|
||||
/// The typing on definitions
|
||||
pub var_bounds: HashMap<DefId, TypeVarBounds>,
|
||||
/// The variable doc associated with the item
|
||||
pub vars: HashMap<EcoString, VarDoc>,
|
||||
pub vars: BTreeMap<StrRef, VarDoc>,
|
||||
/// The type of the resultant type
|
||||
pub res_ty: Option<Ty>,
|
||||
}
|
||||
|
||||
impl DocString {
|
||||
pub fn get_var(&self, name: &str) -> Option<&VarDoc> {
|
||||
/// Get the documentation of a variable associated with the item
|
||||
pub fn get_var(&self, name: &StrRef) -> Option<&VarDoc> {
|
||||
self.vars.get(name)
|
||||
}
|
||||
|
||||
pub fn var_ty(&self, name: &str) -> Option<&Ty> {
|
||||
/// Get the type of a variable associated with the item
|
||||
pub fn var_ty(&self, name: &StrRef) -> Option<&Ty> {
|
||||
self.get_var(name).and_then(|v| v.ty.as_ref())
|
||||
}
|
||||
|
||||
|
@ -105,11 +110,15 @@ impl DocString {
|
|||
}
|
||||
}
|
||||
|
||||
/// The documentation string of a variable associated with some item.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub(crate) struct VarDoc {
|
||||
pub _docs: Option<EcoString>,
|
||||
pub struct VarDoc {
|
||||
/// The documentation of the variable
|
||||
pub docs: Option<EcoString>,
|
||||
/// The type of the variable
|
||||
pub ty: Option<Ty>,
|
||||
pub _default: Option<EcoString>,
|
||||
/// The default value of the variable
|
||||
pub default: Option<EcoString>,
|
||||
}
|
||||
|
||||
pub(crate) fn compute_docstring(
|
||||
|
@ -129,6 +138,10 @@ pub(crate) fn compute_docstring(
|
|||
match kind {
|
||||
DocStringKind::Function => checker.check_func_docs(docs),
|
||||
DocStringKind::Variable => checker.check_var_docs(docs),
|
||||
DocStringKind::Module => None,
|
||||
DocStringKind::Constant => None,
|
||||
DocStringKind::Struct => None,
|
||||
DocStringKind::Reference => None,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -148,14 +161,14 @@ impl<'a, 'w> DocsChecker<'a, 'w> {
|
|||
let converted = identify_func_docs(&converted).ok()?;
|
||||
let module = self.ctx.module_by_str(docs)?;
|
||||
|
||||
let mut params = HashMap::new();
|
||||
let mut params = BTreeMap::new();
|
||||
for param in converted.params.into_iter() {
|
||||
params.insert(
|
||||
param.name,
|
||||
param.name.into(),
|
||||
VarDoc {
|
||||
_docs: Some(param.docs),
|
||||
docs: Some(param.docs),
|
||||
ty: self.check_type_strings(&module, ¶m.types),
|
||||
_default: param.default,
|
||||
default: param.default,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
@ -174,7 +187,7 @@ impl<'a, 'w> DocsChecker<'a, 'w> {
|
|||
|
||||
pub fn check_var_docs(mut self, docs: String) -> Option<DocString> {
|
||||
let converted = convert_docs(self.ctx.world(), &docs).ok()?;
|
||||
let converted = identify_var_docs(&converted).ok()?;
|
||||
let converted = identify_var_docs(converted).ok()?;
|
||||
let module = self.ctx.module_by_str(docs)?;
|
||||
|
||||
let res_ty = converted
|
||||
|
@ -184,7 +197,7 @@ impl<'a, 'w> DocsChecker<'a, 'w> {
|
|||
Some(DocString {
|
||||
docs: Some(converted.docs),
|
||||
var_bounds: self.vars,
|
||||
vars: HashMap::new(),
|
||||
vars: BTreeMap::new(),
|
||||
res_ty,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -189,11 +189,13 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
range: root.range(),
|
||||
};
|
||||
|
||||
self.get_var(root.span(), ident_ref).or_else(|| {
|
||||
let s = root.span();
|
||||
let v = resolve_global_value(self.ctx, root, mode == InterpretMode::Math)?;
|
||||
Some(Ty::Value(InsTy::new_at(v, s)))
|
||||
})
|
||||
self.get_var(root.span(), ident_ref)
|
||||
.map(Ty::Var)
|
||||
.or_else(|| {
|
||||
let s = root.span();
|
||||
let v = resolve_global_value(self.ctx, root, mode == InterpretMode::Math)?;
|
||||
Some(Ty::Value(InsTy::new_at(v, s)))
|
||||
})
|
||||
}
|
||||
|
||||
fn check_array(&mut self, root: LinkedNode<'_>) -> Option<Ty> {
|
||||
|
@ -373,7 +375,7 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
|
||||
fn check_closure(&mut self, root: LinkedNode<'_>) -> Option<Ty> {
|
||||
let docstring = self.check_func_docs(&root);
|
||||
let docstring = docstring.as_ref().unwrap_or(&EMPTY_DOCSTRING);
|
||||
let docstring = docstring.as_deref().unwrap_or(&EMPTY_DOCSTRING);
|
||||
let closure: ast::Closure = root.cast()?;
|
||||
|
||||
log::debug!("check closure: {:?} -> {docstring:#?}", closure.name());
|
||||
|
@ -389,24 +391,24 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
pos.push(self.check_pattern(pattern, Ty::Any, docstring, root.clone()));
|
||||
}
|
||||
ast::Param::Named(e) => {
|
||||
let name = e.name().get();
|
||||
let name = e.name().get().into();
|
||||
let exp = self.check_expr_in(e.expr().span(), root.clone());
|
||||
let v = self.get_var(e.name().span(), to_ident_ref(&root, e.name())?)?;
|
||||
if let Some(annotated) = docstring.var_ty(name.as_str()) {
|
||||
let v = Ty::Var(self.get_var(e.name().span(), to_ident_ref(&root, e.name())?)?);
|
||||
if let Some(annotated) = docstring.var_ty(&name) {
|
||||
self.constrain(&v, annotated);
|
||||
}
|
||||
// todo: this is less efficient than v.lbs.push(exp), we may have some idea to
|
||||
// optimize it, so I put a todo here.
|
||||
self.constrain(&exp, &v);
|
||||
named.insert(name.into(), v);
|
||||
defaults.insert(name.into(), exp);
|
||||
named.insert(name.clone(), v);
|
||||
defaults.insert(name, exp);
|
||||
}
|
||||
// todo: spread left/right
|
||||
ast::Param::Spread(a) => {
|
||||
if let Some(e) = a.sink_ident() {
|
||||
let exp = Ty::Builtin(BuiltinTy::Args);
|
||||
let v = self.get_var(e.span(), to_ident_ref(&root, e)?)?;
|
||||
if let Some(annotated) = docstring.var_ty(e.get().as_str()) {
|
||||
let v = Ty::Var(self.get_var(e.span(), to_ident_ref(&root, e)?)?);
|
||||
if let Some(annotated) = docstring.var_ty(&e.get().as_str().into()) {
|
||||
self.constrain(&v, annotated);
|
||||
}
|
||||
self.constrain(&exp, &v);
|
||||
|
@ -467,13 +469,13 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
.map(|init| self.check_expr_in(init.span(), root.clone()))
|
||||
.unwrap_or_else(|| Ty::Builtin(BuiltinTy::Infer));
|
||||
|
||||
let v = self.get_var(c.span(), to_ident_ref(&root, c)?)?;
|
||||
let v = Ty::Var(self.get_var(c.span(), to_ident_ref(&root, c)?)?);
|
||||
self.constrain(&value, &v);
|
||||
// todo lbs is the lexical signature.
|
||||
}
|
||||
ast::LetBindingKind::Normal(pattern) => {
|
||||
let docstring = self.check_var_docs(&root);
|
||||
let docstring = docstring.as_ref().unwrap_or(&EMPTY_DOCSTRING);
|
||||
let docstring = docstring.as_deref().unwrap_or(&EMPTY_DOCSTRING);
|
||||
|
||||
let value = let_binding
|
||||
.init()
|
||||
|
@ -617,14 +619,15 @@ impl<'a, 'w> TypeChecker<'a, 'w> {
|
|||
) -> Option<Ty> {
|
||||
Some(match pattern {
|
||||
ast::Pattern::Normal(ast::Expr::Ident(ident)) => {
|
||||
let v = self.get_var(ident.span(), to_ident_ref(&root, ident)?)?;
|
||||
let annotated = docs.var_ty(ident.get().as_str());
|
||||
let var = self.get_var(ident.span(), to_ident_ref(&root, ident)?)?;
|
||||
let annotated = docs.var_ty(&var.name);
|
||||
let var = Ty::Var(var);
|
||||
log::debug!("check pattern: {ident:?} with {value:?} and annotation {annotated:?}");
|
||||
if let Some(annotated) = annotated {
|
||||
self.constrain(&v, annotated);
|
||||
self.constrain(&var, annotated);
|
||||
}
|
||||
self.constrain(&value, &v);
|
||||
v
|
||||
self.constrain(&value, &var);
|
||||
var
|
||||
}
|
||||
ast::Pattern::Normal(_) => Ty::Any,
|
||||
ast::Pattern::Placeholder(_) => Ty::Any,
|
||||
|
|
|
@ -1,95 +0,0 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use ecow::{eco_format, EcoString};
|
||||
use parking_lot::Mutex;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tinymist_world::base::{EntryState, ShadowApi, TaskInputs};
|
||||
use tinymist_world::LspWorld;
|
||||
use typst::foundations::Bytes;
|
||||
use typst::{
|
||||
diag::StrResult,
|
||||
syntax::{FileId, VirtualPath},
|
||||
};
|
||||
|
||||
use crate::docs::library;
|
||||
|
||||
use super::tidy::*;
|
||||
|
||||
/// Kind of a docstring.
|
||||
#[derive(Debug, Clone, Hash, Serialize, Deserialize)]
|
||||
pub enum DocStringKind {
|
||||
/// A docstring for a function.
|
||||
Function,
|
||||
/// A docstring for a variable.
|
||||
Variable,
|
||||
}
|
||||
|
||||
/// Docs about a symbol.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum RawDocs {
|
||||
/// Docs about a function.
|
||||
#[serde(rename = "func")]
|
||||
Function(TidyFuncDocs),
|
||||
/// Docs about a variable.
|
||||
#[serde(rename = "var")]
|
||||
Variable(TidyVarDocs),
|
||||
/// Docs about a module.
|
||||
#[serde(rename = "module")]
|
||||
Module(TidyModuleDocs),
|
||||
/// Other kinds of docs.
|
||||
#[serde(rename = "plain")]
|
||||
Plain(EcoString),
|
||||
}
|
||||
|
||||
impl RawDocs {
|
||||
/// Get the markdown representation of the docs.
|
||||
pub fn docs(&self) -> &str {
|
||||
match self {
|
||||
Self::Function(docs) => docs.docs.as_str(),
|
||||
Self::Variable(docs) => docs.docs.as_str(),
|
||||
Self::Module(docs) => docs.docs.as_str(),
|
||||
Self::Plain(docs) => docs.as_str(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unfortunately, we have only 65536 possible file ids and we cannot revoke
|
||||
// them. So we share a global file id for all docs conversion.
|
||||
static DOCS_CONVERT_ID: std::sync::LazyLock<Mutex<FileId>> = std::sync::LazyLock::new(|| {
|
||||
Mutex::new(FileId::new(None, VirtualPath::new("__tinymist_docs__.typ")))
|
||||
});
|
||||
|
||||
pub(crate) fn convert_docs(world: &LspWorld, content: &str) -> StrResult<EcoString> {
|
||||
static DOCS_LIB: std::sync::LazyLock<Arc<typlite::scopes::Scopes<typlite::value::Value>>> =
|
||||
std::sync::LazyLock::new(library::lib);
|
||||
|
||||
let conv_id = DOCS_CONVERT_ID.lock();
|
||||
let entry = EntryState::new_rootless(conv_id.vpath().as_rooted_path().into()).unwrap();
|
||||
let entry = entry.select_in_workspace(*conv_id);
|
||||
|
||||
let mut w = world.task(TaskInputs {
|
||||
entry: Some(entry),
|
||||
inputs: None,
|
||||
});
|
||||
w.map_shadow_by_id(*conv_id, Bytes::from(content.as_bytes().to_owned()))?;
|
||||
// todo: bad performance
|
||||
w.source_db.take_state();
|
||||
|
||||
let conv = typlite::Typlite::new(Arc::new(w))
|
||||
.with_library(DOCS_LIB.clone())
|
||||
.annotate_elements(true)
|
||||
.convert()
|
||||
.map_err(|e| eco_format!("failed to convert to markdown: {e}"))?;
|
||||
|
||||
Ok(conv)
|
||||
}
|
||||
|
||||
pub(crate) fn identify_docs(kind: &str, content: &str) -> StrResult<RawDocs> {
|
||||
match kind {
|
||||
"function" => identify_func_docs(content).map(RawDocs::Function),
|
||||
"variable" => identify_var_docs(content).map(RawDocs::Variable),
|
||||
"module" => identify_tidy_module_docs(content).map(RawDocs::Module),
|
||||
_ => Err(eco_format!("unknown kind {kind}")),
|
||||
}
|
||||
}
|
|
@ -1,20 +1,17 @@
|
|||
//! Documentation generation utilities.
|
||||
|
||||
mod docstring;
|
||||
mod library;
|
||||
mod module;
|
||||
mod package;
|
||||
mod signature;
|
||||
mod symbol;
|
||||
mod tidy;
|
||||
|
||||
use ecow::EcoString;
|
||||
use reflexo::path::unix_slash;
|
||||
use typst::{foundations::Value, syntax::FileId};
|
||||
|
||||
pub use docstring::*;
|
||||
pub use module::*;
|
||||
pub use package::*;
|
||||
pub use signature::*;
|
||||
pub use symbol::*;
|
||||
pub(crate) use tidy::*;
|
||||
|
||||
fn file_id_repr(k: FileId) -> String {
|
||||
|
@ -25,13 +22,12 @@ fn file_id_repr(k: FileId) -> String {
|
|||
}
|
||||
}
|
||||
|
||||
fn kind_of(val: &Value) -> EcoString {
|
||||
fn kind_of(val: &Value) -> DocStringKind {
|
||||
match val {
|
||||
Value::Module(_) => "module",
|
||||
Value::Type(_) => "struct",
|
||||
Value::Func(_) => "function",
|
||||
Value::Label(_) => "reference",
|
||||
_ => "constant",
|
||||
Value::Module(_) => DocStringKind::Module,
|
||||
Value::Type(_) => DocStringKind::Struct,
|
||||
Value::Func(_) => DocStringKind::Function,
|
||||
Value::Label(_) => DocStringKind::Reference,
|
||||
_ => DocStringKind::Constant,
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
|
|
@ -16,7 +16,7 @@ use crate::syntax::{find_docs_of, get_non_strict_def_target};
|
|||
use crate::upstream::truncated_doc_repr;
|
||||
use crate::AnalysisContext;
|
||||
|
||||
use super::{get_manifest, get_manifest_id, kind_of, PackageInfo, RawDocs, SignatureDocs};
|
||||
use super::{get_manifest, get_manifest_id, kind_of, DocStringKind, PackageInfo, SymbolDocs};
|
||||
|
||||
/// Get documentation of symbols in a package.
|
||||
pub fn package_module_docs(ctx: &mut AnalysisContext, pkg: &PackageInfo) -> StrResult<SymbolsInfo> {
|
||||
|
@ -70,7 +70,7 @@ pub struct SymbolInfoHead {
|
|||
/// The name of the symbol.
|
||||
pub name: EcoString,
|
||||
/// The kind of the symbol.
|
||||
pub kind: EcoString,
|
||||
pub kind: DocStringKind,
|
||||
/// The location (file, start, end) of the symbol.
|
||||
pub loc: Option<(usize, usize, usize)>,
|
||||
/// Is the symbol reexport
|
||||
|
@ -81,10 +81,8 @@ pub struct SymbolInfoHead {
|
|||
pub oneliner: Option<String>,
|
||||
/// The raw documentation of the symbol.
|
||||
pub docs: Option<String>,
|
||||
/// The signature of the symbol.
|
||||
pub signature: Option<SignatureDocs>,
|
||||
/// The parsed documentation of the symbol.
|
||||
pub parsed_docs: Option<RawDocs>,
|
||||
pub parsed_docs: Option<SymbolDocs>,
|
||||
/// The value of the symbol.
|
||||
#[serde(skip)]
|
||||
pub constant: Option<EcoString>,
|
||||
|
|
|
@ -12,9 +12,7 @@ use typst::syntax::package::{PackageManifest, PackageSpec};
|
|||
use typst::syntax::{FileId, Span, VirtualPath};
|
||||
use typst::World;
|
||||
|
||||
use crate::docs::{
|
||||
convert_docs, file_id_repr, identify_docs, module_docs, signature_docs, RawDocs, SymbolsInfo,
|
||||
};
|
||||
use crate::docs::{file_id_repr, module_docs, symbol_docs, SymbolDocs, SymbolsInfo};
|
||||
use crate::syntax::IdentRef;
|
||||
use crate::ty::Ty;
|
||||
use crate::AnalysisContext;
|
||||
|
@ -159,33 +157,32 @@ pub fn package_docs(
|
|||
});
|
||||
sym.head.loc = span;
|
||||
|
||||
let sym_value = sym.head.value.clone();
|
||||
let signature = sym_value.and_then(|e| {
|
||||
let def_ident = IdentRef {
|
||||
name: sym.head.name.clone(),
|
||||
range: sym.head.name_range.clone()?,
|
||||
};
|
||||
signature_docs(ctx, type_info, Some(&def_ident), &e, Some(&mut doc_ty))
|
||||
let def_ident = sym.head.name_range.as_ref().map(|range| IdentRef {
|
||||
name: sym.head.name.clone(),
|
||||
range: range.clone(),
|
||||
});
|
||||
sym.head.signature = signature;
|
||||
let docs = symbol_docs(
|
||||
ctx,
|
||||
type_info,
|
||||
sym.head.kind,
|
||||
def_ident.as_ref(),
|
||||
sym.head.value.as_ref(),
|
||||
sym.head.docs.as_deref(),
|
||||
Some(&mut doc_ty),
|
||||
);
|
||||
|
||||
let mut convert_err = None;
|
||||
if let Some(docs) = &sym.head.docs {
|
||||
match convert_docs(world, docs) {
|
||||
Ok(content) => {
|
||||
let docs = identify_docs(sym.head.kind.as_str(), &content)
|
||||
.unwrap_or(RawDocs::Plain(content));
|
||||
|
||||
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);
|
||||
}
|
||||
match &docs {
|
||||
Ok(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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -227,7 +224,7 @@ pub fn package_docs(
|
|||
let head = jbase64(&sym.head);
|
||||
let _ = writeln!(md, "<!-- begin:symbol {ident} {head} -->");
|
||||
|
||||
if let Some(sig) = &sym.head.signature {
|
||||
if let Some(SymbolDocs::Function(sig)) = &sym.head.parsed_docs {
|
||||
let _ = writeln!(md, "<!-- begin:sig -->");
|
||||
let _ = writeln!(md, "```typc");
|
||||
let _ = writeln!(md, "let {name}({sig});", name = sym.head.name);
|
||||
|
@ -245,13 +242,18 @@ pub fn package_docs(
|
|||
}
|
||||
(Some(docs), _) => {
|
||||
let _ = writeln!(md, "{}", remove_list_annotations(docs.docs()));
|
||||
if let RawDocs::Function(f) = docs {
|
||||
for param in &f.params {
|
||||
if let SymbolDocs::Function(f) = docs {
|
||||
for param in f.pos.iter().chain(f.named.values()).chain(f.rest.as_ref())
|
||||
{
|
||||
let _ = writeln!(md, "<!-- begin:param {} -->", param.name);
|
||||
let ty = match ¶m.cano_type {
|
||||
Some((short, _)) => short,
|
||||
None => "unknown",
|
||||
};
|
||||
let _ = writeln!(
|
||||
md,
|
||||
"#### {} ({})\n<!-- begin:param-doc {} -->\n{}\n<!-- end:param-doc {} -->",
|
||||
param.name, param.types, param.name, param.docs, param.name
|
||||
"#### {} ({ty:?})\n<!-- begin:param-doc {} -->\n{}\n<!-- end:param-doc {} -->",
|
||||
param.name, param.name, param.docs, param.name
|
||||
);
|
||||
let _ = writeln!(md, "<!-- end:param -->");
|
||||
}
|
||||
|
|
|
@ -1,228 +0,0 @@
|
|||
use std::fmt;
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
use ecow::EcoString;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typst::foundations::Value;
|
||||
|
||||
use crate::analysis::analyze_dyn_signature;
|
||||
use crate::syntax::IdentRef;
|
||||
use crate::{ty::Ty, AnalysisContext};
|
||||
|
||||
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)]
|
||||
pub struct SignatureDocs {
|
||||
/// The positional parameters.
|
||||
pub pos: Vec<ParamDocs>,
|
||||
/// The named parameters.
|
||||
pub named: HashMap<String, ParamDocs>,
|
||||
/// The rest parameter.
|
||||
pub rest: Option<ParamDocs>,
|
||||
/// The return type.
|
||||
pub ret_ty: TypeRepr,
|
||||
}
|
||||
|
||||
impl fmt::Display for SignatureDocs {
|
||||
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 ParamDocs {
|
||||
/// The parameter's name.
|
||||
pub name: String,
|
||||
/// Documentation for the parameter.
|
||||
pub docs: String,
|
||||
/// Inferred type of the parameter.
|
||||
pub cano_type: TypeRepr,
|
||||
/// The parameter's default name as value.
|
||||
pub expr: Option<EcoString>,
|
||||
/// Is the parameter positional?
|
||||
pub positional: bool,
|
||||
/// Is the parameter named?
|
||||
///
|
||||
/// Can be true even if `positional` is true if the parameter can be given
|
||||
/// in both variants.
|
||||
pub named: bool,
|
||||
/// Can the parameter be given any number of times?
|
||||
pub variadic: bool,
|
||||
/// Is the parameter settable with a set rule?
|
||||
pub settable: bool,
|
||||
}
|
||||
|
||||
type TypeInfo = (Arc<crate::analysis::DefUseInfo>, Arc<crate::ty::TypeScheme>);
|
||||
|
||||
pub(crate) fn signature_docs(
|
||||
ctx: &mut AnalysisContext,
|
||||
type_info: Option<&TypeInfo>,
|
||||
def_ident: Option<&IdentRef>,
|
||||
runtime_fn: &Value,
|
||||
doc_ty: Option<ShowTypeRepr>,
|
||||
) -> Option<SignatureDocs> {
|
||||
let func = match runtime_fn {
|
||||
Value::Func(f) => f,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
// todo: documenting with bindings
|
||||
use typst::foundations::func::Repr;
|
||||
let mut func = func;
|
||||
loop {
|
||||
match func.inner() {
|
||||
Repr::Element(..) | Repr::Native(..) => {
|
||||
break;
|
||||
}
|
||||
Repr::With(w) => {
|
||||
func = &w.0;
|
||||
}
|
||||
Repr::Closure(..) => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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_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
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, pos)| (pos, type_sig.as_ref().and_then(|sig| sig.pos(i))));
|
||||
let named_in = sig
|
||||
.primary()
|
||||
.named
|
||||
.iter()
|
||||
.map(|x| (x, type_sig.as_ref().and_then(|sig| sig.named(x.0))));
|
||||
let rest_in = sig
|
||||
.primary()
|
||||
.rest
|
||||
.as_ref()
|
||||
.map(|x| (x, type_sig.as_ref().and_then(|sig| sig.rest_param())));
|
||||
|
||||
let ret_in = type_sig
|
||||
.as_ref()
|
||||
.and_then(|sig| sig.body.as_ref())
|
||||
.or_else(|| sig.primary().ret_ty.as_ref());
|
||||
|
||||
let pos = pos_in
|
||||
.map(|(param, ty)| ParamDocs {
|
||||
name: param.name.as_ref().to_owned(),
|
||||
docs: param.docs.as_ref().to_owned(),
|
||||
cano_type: doc_ty(ty.or(Some(¶m.base_type))),
|
||||
expr: param.expr.clone(),
|
||||
positional: param.positional,
|
||||
named: param.named,
|
||||
variadic: param.variadic,
|
||||
settable: param.settable,
|
||||
})
|
||||
.collect();
|
||||
|
||||
let named = named_in
|
||||
.map(|((name, param), ty)| {
|
||||
(
|
||||
name.as_ref().to_owned(),
|
||||
ParamDocs {
|
||||
name: param.name.as_ref().to_owned(),
|
||||
docs: param.docs.as_ref().to_owned(),
|
||||
cano_type: doc_ty(ty.or(Some(¶m.base_type))),
|
||||
expr: param.expr.clone(),
|
||||
positional: param.positional,
|
||||
named: param.named,
|
||||
variadic: param.variadic,
|
||||
settable: param.settable,
|
||||
},
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
let rest = rest_in.map(|(param, ty)| ParamDocs {
|
||||
name: param.name.as_ref().to_owned(),
|
||||
docs: param.docs.as_ref().to_owned(),
|
||||
cano_type: doc_ty(ty.or(Some(¶m.base_type))),
|
||||
expr: param.expr.clone(),
|
||||
positional: param.positional,
|
||||
named: param.named,
|
||||
variadic: param.variadic,
|
||||
settable: param.settable,
|
||||
});
|
||||
|
||||
let ret_ty = doc_ty(ret_in);
|
||||
|
||||
Some(SignatureDocs {
|
||||
pos,
|
||||
named,
|
||||
rest,
|
||||
ret_ty,
|
||||
})
|
||||
}
|
375
crates/tinymist-query/src/docs/symbol.rs
Normal file
375
crates/tinymist-query/src/docs/symbol.rs
Normal file
|
@ -0,0 +1,375 @@
|
|||
use core::fmt;
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use ecow::{eco_format, EcoString};
|
||||
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::{
|
||||
diag::StrResult,
|
||||
syntax::{FileId, VirtualPath},
|
||||
};
|
||||
|
||||
use super::tidy::*;
|
||||
use crate::analysis::{analyze_dyn_signature, ParamSpec};
|
||||
use crate::docs::library;
|
||||
use crate::syntax::IdentRef;
|
||||
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.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(tag = "kind")]
|
||||
pub enum SymbolDocs {
|
||||
/// Documentation about a function.
|
||||
#[serde(rename = "func")]
|
||||
Function(Box<SignatureDocs>),
|
||||
/// Documentation about a variable.
|
||||
#[serde(rename = "var")]
|
||||
Variable(TidyVarDocs),
|
||||
/// Documentation about a module.
|
||||
#[serde(rename = "module")]
|
||||
Module(TidyModuleDocs),
|
||||
/// Other kinds of documentation.
|
||||
#[serde(rename = "plain")]
|
||||
Plain {
|
||||
/// The content of the documentation.
|
||||
docs: EcoString,
|
||||
},
|
||||
}
|
||||
|
||||
impl SymbolDocs {
|
||||
/// Get the markdown representation of the documentation.
|
||||
pub fn docs(&self) -> &str {
|
||||
match self {
|
||||
Self::Function(docs) => docs.docs.as_str(),
|
||||
Self::Variable(docs) => docs.docs.as_str(),
|
||||
Self::Module(docs) => docs.docs.as_str(),
|
||||
Self::Plain { docs } => docs.as_str(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn symbol_docs(
|
||||
ctx: &mut AnalysisContext,
|
||||
type_info: Option<&TypeInfo>,
|
||||
kind: DocStringKind,
|
||||
def_ident: Option<&IdentRef>,
|
||||
sym_value: Option<&Value>,
|
||||
docs: Option<&str>,
|
||||
doc_ty: Option<ShowTypeRepr>,
|
||||
) -> Result<SymbolDocs, String> {
|
||||
let signature = sym_value.and_then(|e| signature_docs(ctx, type_info, def_ident, e, 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(SymbolDocs::Plain { docs: "".into() })
|
||||
}
|
||||
|
||||
/// Describes a primary function signature.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SignatureDocs {
|
||||
/// Documentation for the function.
|
||||
pub docs: EcoString,
|
||||
// pub return_ty: Option<EcoString>,
|
||||
// pub params: Vec<TidyParamDocs>,
|
||||
/// The positional parameters.
|
||||
pub pos: Vec<ParamDocs>,
|
||||
/// The named parameters.
|
||||
pub named: BTreeMap<String, ParamDocs>,
|
||||
/// The rest parameter.
|
||||
pub rest: Option<ParamDocs>,
|
||||
/// The return type.
|
||||
pub ret_ty: TypeRepr,
|
||||
}
|
||||
|
||||
impl fmt::Display for SignatureDocs {
|
||||
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 ParamDocs {
|
||||
/// The parameter's name.
|
||||
pub name: String,
|
||||
/// Documentation for the parameter.
|
||||
pub docs: String,
|
||||
/// Inferred type of the parameter.
|
||||
pub cano_type: TypeRepr,
|
||||
/// The parameter's default name as value.
|
||||
pub expr: Option<EcoString>,
|
||||
/// Is the parameter positional?
|
||||
pub positional: bool,
|
||||
/// Is the parameter named?
|
||||
///
|
||||
/// Can be true even if `positional` is true if the parameter can be given
|
||||
/// in both variants.
|
||||
pub named: bool,
|
||||
/// Can the parameter be given any number of times?
|
||||
pub variadic: bool,
|
||||
/// Is the parameter settable with a set rule?
|
||||
pub settable: bool,
|
||||
}
|
||||
|
||||
impl ParamDocs {
|
||||
fn new(param: &ParamSpec, ty: Option<&Ty>, doc_ty: Option<&mut ShowTypeRepr>) -> Self {
|
||||
Self {
|
||||
name: param.name.as_ref().to_owned(),
|
||||
docs: param.docs.as_ref().to_owned(),
|
||||
cano_type: format_ty(ty.or(Some(¶m.base_type)), doc_ty),
|
||||
expr: param.expr.clone(),
|
||||
positional: param.positional,
|
||||
named: param.named,
|
||||
variadic: param.variadic,
|
||||
settable: param.settable,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type TypeInfo = (Arc<crate::analysis::DefUseInfo>, Arc<crate::ty::TypeScheme>);
|
||||
|
||||
fn format_ty(ty: Option<&Ty>, doc_ty: Option<&mut ShowTypeRepr>) -> TypeRepr {
|
||||
match doc_ty {
|
||||
Some(doc_ty) => doc_ty(ty),
|
||||
None => ty
|
||||
.and_then(|ty| ty.describe())
|
||||
.map(|short| (short, format!("{ty:?}"))),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn signature_docs(
|
||||
ctx: &mut AnalysisContext,
|
||||
type_info: Option<&TypeInfo>,
|
||||
def_ident: Option<&IdentRef>,
|
||||
runtime_fn: &Value,
|
||||
mut doc_ty: Option<ShowTypeRepr>,
|
||||
) -> Option<SignatureDocs> {
|
||||
let func = match runtime_fn {
|
||||
Value::Func(f) => f,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
// todo: documenting with bindings
|
||||
use typst::foundations::func::Repr;
|
||||
let mut func = func;
|
||||
loop {
|
||||
match func.inner() {
|
||||
Repr::Element(..) | Repr::Native(..) => {
|
||||
break;
|
||||
}
|
||||
Repr::With(w) => {
|
||||
func = &w.0;
|
||||
}
|
||||
Repr::Closure(..) => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let sig = analyze_dyn_signature(ctx, func.clone());
|
||||
let def_id = type_info.and_then(|(def_use, _)| {
|
||||
let def_fid = func.span().id()?;
|
||||
let (def_id, _) = def_use.get_def(def_fid, def_ident?)?;
|
||||
Some(def_id)
|
||||
});
|
||||
let docstring = type_info.and_then(|(_, ty_chk)| ty_chk.var_docs.get(&def_id?));
|
||||
let type_sig = type_info.and_then(|(_, ty_chk)| ty_chk.type_of_def(def_id?));
|
||||
let type_sig = type_sig.and_then(|type_sig| type_sig.sig_repr(true));
|
||||
|
||||
let pos_in = sig
|
||||
.primary()
|
||||
.pos
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, pos)| (pos, type_sig.as_ref().and_then(|sig| sig.pos(i))));
|
||||
let named_in = sig
|
||||
.primary()
|
||||
.named
|
||||
.iter()
|
||||
.map(|x| (x, type_sig.as_ref().and_then(|sig| sig.named(x.0))));
|
||||
let rest_in = sig
|
||||
.primary()
|
||||
.rest
|
||||
.as_ref()
|
||||
.map(|x| (x, type_sig.as_ref().and_then(|sig| sig.rest_param())));
|
||||
|
||||
let ret_in = type_sig
|
||||
.as_ref()
|
||||
.and_then(|sig| sig.body.as_ref())
|
||||
.or_else(|| sig.primary().sig_ty.body.as_ref());
|
||||
|
||||
let pos = pos_in
|
||||
.map(|(param, ty)| ParamDocs::new(param, ty, doc_ty.as_mut()))
|
||||
.collect();
|
||||
let named = named_in
|
||||
.map(|((name, param), ty)| {
|
||||
(
|
||||
name.as_ref().to_owned(),
|
||||
ParamDocs::new(param, ty, doc_ty.as_mut()),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let rest = rest_in.map(|(param, ty)| ParamDocs::new(param, ty, doc_ty.as_mut()));
|
||||
|
||||
let ret_ty = format_ty(ret_in, doc_ty.as_mut());
|
||||
|
||||
Some(SignatureDocs {
|
||||
docs: docstring.and_then(|x| x.docs.clone()).unwrap_or_default(),
|
||||
pos,
|
||||
named,
|
||||
rest,
|
||||
ret_ty,
|
||||
})
|
||||
}
|
||||
|
||||
// Unfortunately, we have only 65536 possible file ids and we cannot revoke
|
||||
// them. So we share a global file id for all docs conversion.
|
||||
static DOCS_CONVERT_ID: std::sync::LazyLock<Mutex<FileId>> = std::sync::LazyLock::new(|| {
|
||||
Mutex::new(FileId::new(None, VirtualPath::new("__tinymist_docs__.typ")))
|
||||
});
|
||||
|
||||
pub(crate) fn convert_docs(world: &LspWorld, content: &str) -> StrResult<EcoString> {
|
||||
static DOCS_LIB: std::sync::LazyLock<Arc<typlite::scopes::Scopes<typlite::value::Value>>> =
|
||||
std::sync::LazyLock::new(library::lib);
|
||||
|
||||
let conv_id = DOCS_CONVERT_ID.lock();
|
||||
let entry = EntryState::new_rootless(conv_id.vpath().as_rooted_path().into()).unwrap();
|
||||
let entry = entry.select_in_workspace(*conv_id);
|
||||
|
||||
let mut w = world.task(TaskInputs {
|
||||
entry: Some(entry),
|
||||
inputs: None,
|
||||
});
|
||||
w.map_shadow_by_id(*conv_id, Bytes::from(content.as_bytes().to_owned()))?;
|
||||
// todo: bad performance
|
||||
w.source_db.take_state();
|
||||
|
||||
let conv = typlite::Typlite::new(Arc::new(w))
|
||||
.with_library(DOCS_LIB.clone())
|
||||
.annotate_elements(true)
|
||||
.convert()
|
||||
.map_err(|e| eco_format!("failed to convert to markdown: {e}"))?;
|
||||
|
||||
Ok(conv)
|
||||
}
|
||||
|
||||
pub(crate) fn identify_docs(kind: DocStringKind, docs: EcoString) -> StrResult<SymbolDocs> {
|
||||
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 }),
|
||||
}
|
||||
}
|
|
@ -13,20 +13,20 @@ pub struct TidyParamDocs {
|
|||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TidyFuncDocs {
|
||||
pub docs: String,
|
||||
pub docs: EcoString,
|
||||
pub return_ty: Option<EcoString>,
|
||||
pub params: Vec<TidyParamDocs>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TidyVarDocs {
|
||||
pub docs: String,
|
||||
pub docs: EcoString,
|
||||
pub return_ty: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TidyModuleDocs {
|
||||
pub docs: String,
|
||||
pub docs: EcoString,
|
||||
}
|
||||
|
||||
pub fn identify_func_docs(converted: &str) -> StrResult<TidyFuncDocs> {
|
||||
|
@ -129,8 +129,8 @@ pub fn identify_func_docs(converted: &str) -> StrResult<TidyFuncDocs> {
|
|||
}
|
||||
|
||||
let docs = match break_line {
|
||||
Some(line_no) => (lines[..line_no]).iter().copied().join("\n"),
|
||||
None => converted.to_owned(),
|
||||
Some(line_no) => (lines[..line_no]).iter().copied().join("\n").into(),
|
||||
None => converted.into(),
|
||||
};
|
||||
|
||||
params.reverse();
|
||||
|
@ -141,7 +141,7 @@ pub fn identify_func_docs(converted: &str) -> StrResult<TidyFuncDocs> {
|
|||
})
|
||||
}
|
||||
|
||||
pub fn identify_var_docs(converted: &str) -> StrResult<TidyVarDocs> {
|
||||
pub fn identify_var_docs(converted: EcoString) -> StrResult<TidyVarDocs> {
|
||||
let lines = converted.lines().collect::<Vec<_>>();
|
||||
|
||||
let mut return_ty = None;
|
||||
|
@ -170,17 +170,15 @@ pub fn identify_var_docs(converted: &str) -> StrResult<TidyVarDocs> {
|
|||
}
|
||||
|
||||
let docs = match break_line {
|
||||
Some(line_no) => (lines[..line_no]).iter().copied().join("\n"),
|
||||
None => converted.to_owned(),
|
||||
Some(line_no) => (lines[..line_no]).iter().copied().join("\n").into(),
|
||||
None => converted,
|
||||
};
|
||||
|
||||
Ok(TidyVarDocs { docs, return_ty })
|
||||
}
|
||||
|
||||
pub fn identify_tidy_module_docs(converted: &str) -> StrResult<TidyModuleDocs> {
|
||||
Ok(TidyModuleDocs {
|
||||
docs: converted.to_owned(),
|
||||
})
|
||||
pub fn identify_tidy_module_docs(docs: EcoString) -> StrResult<TidyModuleDocs> {
|
||||
Ok(TidyModuleDocs { docs })
|
||||
}
|
||||
|
||||
fn match_brace(trim_start: &str) -> Option<(&str, &str)> {
|
||||
|
@ -232,7 +230,7 @@ mod tests {
|
|||
}
|
||||
|
||||
fn var(s: &str) -> String {
|
||||
let f = super::identify_var_docs(s).unwrap();
|
||||
let f = super::identify_var_docs(s.into()).unwrap();
|
||||
let mut res = format!(">> docs:\n{}\n<< docs", f.docs);
|
||||
if let Some(t) = f.return_ty {
|
||||
res.push_str(&format!("\n>>return\n{t}\n<<return"));
|
||||
|
|
|
@ -21,7 +21,7 @@ use typst::{
|
|||
use super::PackageId;
|
||||
use crate::{
|
||||
adt::{interner::impl_internable, snapshot_map},
|
||||
analysis::BuiltinTy,
|
||||
analysis::{BuiltinTy, DocString},
|
||||
};
|
||||
|
||||
pub(crate) use super::{TyCtx, TyCtxMut};
|
||||
|
@ -951,6 +951,8 @@ impl IfTy {
|
|||
pub struct TypeScheme {
|
||||
/// The typing on definitions
|
||||
pub vars: HashMap<DefId, TypeVarBounds>,
|
||||
/// The checked documentation of definitions
|
||||
pub var_docs: HashMap<DefId, Arc<DocString>>,
|
||||
/// The local binding of the type variable
|
||||
pub local_binds: snapshot_map::SnapshotMap<DefId, Ty>,
|
||||
/// The typing on syntax structures
|
||||
|
@ -975,6 +977,7 @@ impl TypeScheme {
|
|||
pub fn type_of_def(&self, def: DefId) -> Option<Ty> {
|
||||
Some(self.simplify(self.vars.get(&def).map(|e| e.as_type())?, false))
|
||||
}
|
||||
|
||||
/// Get the type of a syntax structure
|
||||
pub fn type_of_span(&self, site: Span) -> Option<Ty> {
|
||||
self.mapping
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue