mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-11-23 20:56:42 +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
|
|
@ -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"));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue