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:
Myriad-Dreamin 2024-10-16 11:16:16 +08:00 committed by GitHub
parent d121e8279d
commit b9da92175e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 522 additions and 455 deletions

View file

@ -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}")),
}
}

View file

@ -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()
}

View file

@ -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>,

View file

@ -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 &param.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 -->");
}

View file

@ -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(&param.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(&param.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(&param.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,
})
}

View 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(&param.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 }),
}
}

View file

@ -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"));