mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-11-23 12:46:43 +00:00
feat: export package docs to json format and render to html (#1809)
Some checks failed
tinymist::auto_tag / auto-tag (push) Has been cancelled
tinymist::ci / Duplicate Actions Detection (push) Has been cancelled
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Has been cancelled
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Has been cancelled
tinymist::ci / prepare-build (push) Has been cancelled
tinymist::gh_pages / build-gh-pages (push) Has been cancelled
tinymist::ci / announce (push) Has been cancelled
tinymist::ci / build (push) Has been cancelled
Some checks failed
tinymist::auto_tag / auto-tag (push) Has been cancelled
tinymist::ci / Duplicate Actions Detection (push) Has been cancelled
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Has been cancelled
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Has been cancelled
tinymist::ci / prepare-build (push) Has been cancelled
tinymist::gh_pages / build-gh-pages (push) Has been cancelled
tinymist::ci / announce (push) Has been cancelled
tinymist::ci / build (push) Has been cancelled
It provides a `package-doc.json` and HTML is rendered using a
`package-doc` function.
```typ
#let package-doc(info, path: none) [
#metadata((
// more sub paths
)) <static-paths>
#render-page(info, path) // the content of $path/.html
]
#package-doc(json("package-doc.json"))
```
This commit is contained in:
parent
2c552ce985
commit
bf081ec347
11 changed files with 2544 additions and 133 deletions
|
|
@ -66,6 +66,8 @@ pub fn module_docs(ctx: &mut LocalContext, entry_point: FileId) -> StrResult<Pac
|
|||
/// Information about a definition.
|
||||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||||
pub struct DefInfo {
|
||||
/// The raw documentation of the definition.
|
||||
pub id: EcoString,
|
||||
/// The name of the definition.
|
||||
pub name: EcoString,
|
||||
/// The kind of the definition.
|
||||
|
|
@ -74,6 +76,10 @@ pub struct DefInfo {
|
|||
pub loc: Option<(usize, usize, usize)>,
|
||||
/// Whether the definition external to the module.
|
||||
pub is_external: bool,
|
||||
/// The module link to the definition
|
||||
pub module_link: Option<String>,
|
||||
/// The symbol link to the definition
|
||||
pub symbol_link: Option<String>,
|
||||
/// The link to the definition if it is external.
|
||||
pub external_link: Option<String>,
|
||||
/// The one-line documentation of the definition.
|
||||
|
|
@ -90,7 +96,7 @@ pub struct DefInfo {
|
|||
#[serde(skip)]
|
||||
pub decl: Option<Interned<Decl>>,
|
||||
/// The children of the definition.
|
||||
pub children: EcoVec<DefInfo>,
|
||||
pub children: Vec<DefInfo>,
|
||||
}
|
||||
|
||||
/// Information about the definitions in a package.
|
||||
|
|
@ -206,6 +212,7 @@ impl ScanDefCtx<'_> {
|
|||
};
|
||||
|
||||
let mut head = DefInfo {
|
||||
id: EcoString::new(),
|
||||
name: key.to_string().into(),
|
||||
kind: decl.kind(),
|
||||
constant: expr.map(|expr| expr.repr()),
|
||||
|
|
@ -215,6 +222,8 @@ impl ScanDefCtx<'_> {
|
|||
children: children.unwrap_or_default(),
|
||||
loc: None,
|
||||
is_external: false,
|
||||
module_link: None,
|
||||
symbol_link: None,
|
||||
external_link: None,
|
||||
oneliner: None,
|
||||
};
|
||||
|
|
|
|||
299
crates/tinymist-query/src/docs/package-doc.typ
Normal file
299
crates/tinymist-query/src/docs/package-doc.typ
Normal file
|
|
@ -0,0 +1,299 @@
|
|||
#import "@preview/cmarker:0.1.6": render
|
||||
// re-export page template
|
||||
#import "/typ/packages/package-docs/template.typ": project
|
||||
|
||||
#let module-divider = html.elem("hr", attrs: (class: "module-divider"));
|
||||
#show link: it => if type(it.dest) == label {
|
||||
html.elem("a", attrs: (href: "#" + str(it.dest), class: "symbol-link"), it.body)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
#let heading-label(name) = {
|
||||
let it = name.replace(regex("[\s\:]"), "-").replace(regex("[.()]"), "").replace(regex("-+"), "-").replace("M", "m")
|
||||
label(it)
|
||||
}
|
||||
#let labelled-heading(depth, it) = {
|
||||
heading(depth: depth, html.elem("span", attrs: (id: str(heading-label(it))), it))
|
||||
}
|
||||
#let markdown-docs = render.with(
|
||||
scope: (
|
||||
image: (src, alt: none) => {
|
||||
html.elem("img", attrs: (src: src, alt: alt, class: "code-image"))
|
||||
},
|
||||
),
|
||||
)
|
||||
|
||||
#let display-package-spec(pkg-spec) = {
|
||||
"@"
|
||||
pkg-spec.meta.namespace
|
||||
"/"
|
||||
pkg-spec.meta.name
|
||||
":"
|
||||
pkg-spec.meta.version
|
||||
}
|
||||
|
||||
#let span = html.elem.with("span")
|
||||
#let code = html.elem.with("code")
|
||||
#let keyword = code.with(attrs: (class: "code-kw"))
|
||||
#let builtin-ty = code.with(attrs: (class: "type-builtin"))
|
||||
|
||||
#let symbol-doc(symbol-ctx, info) = {
|
||||
// let ident = if !primary.is_empty() {
|
||||
// eco_format!("symbol-{}-{primary}.{}", child.kind, child.name)
|
||||
// } else {
|
||||
// eco_format!("symbol-{}-{}", child.kind, child.name)
|
||||
// };
|
||||
|
||||
let symlink(body) = if info.symbol_link != none {
|
||||
// let _ = writeln!(out, "#link({})[Symbol Docs]\n", TypstLink(lnk));
|
||||
html.elem("a", attrs: (href: info.symbol_link, class: "symbol-link"), body)
|
||||
} else {
|
||||
body
|
||||
}
|
||||
|
||||
if info.is_external {
|
||||
let fid = info.loc.at(0, default: none)
|
||||
let file = symbol-ctx.files.at(fid, default: none)
|
||||
let package = symbol-ctx.packages.at(file.package, default: none)
|
||||
|
||||
let title = if info.kind == "module" {
|
||||
let title = if package != none and file.package > 0 {
|
||||
span(attrs: (title: display-package-spec(package)), "external")
|
||||
code(" ")
|
||||
code(file.path)
|
||||
} else {
|
||||
code(file.path)
|
||||
}
|
||||
|
||||
symlink(code(title))
|
||||
} else {
|
||||
// keyword("extern")
|
||||
// code(" ")
|
||||
symlink(code(info.name))
|
||||
if info.kind == "function" {
|
||||
code("()")
|
||||
} else {
|
||||
code(": ")
|
||||
builtin-ty[any]
|
||||
}
|
||||
}
|
||||
|
||||
html.elem("div", attrs: (class: "detail-header doc-symbol-" + info.kind), [=== #title])
|
||||
|
||||
if info.oneliner != none {
|
||||
markdown-docs(info.oneliner)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
labelled-heading(3, info.kind + ": " + info.name + " in " + symbol-ctx.in-module)
|
||||
|
||||
// if info.symbol_link != none {
|
||||
// // let _ = writeln!(out, "#link({})[Symbol Docs]\n", TypstLink(lnk));
|
||||
// par(symlink("Symbol Docs"))
|
||||
// }
|
||||
|
||||
// let convert_err = None::<EcoString>;
|
||||
if info.parsed_docs != none {
|
||||
if info.parsed_docs.kind == "func" {
|
||||
// if let Some(DefDocs::Function(sig)) = &info.parsed_docs {
|
||||
// // let _ = writeln!(out, "<!-- begin:sig -->");
|
||||
// let _ = writeln!(out, "```typc");
|
||||
// let _ = write!(out, "let {}", info.name);
|
||||
// let _ = sig.print(&mut out);
|
||||
// let _ = writeln!(out, ";");
|
||||
// let _ = writeln!(out, "```");
|
||||
// // let _ = writeln!(out, "<!-- end:sig -->");
|
||||
// }
|
||||
// repr(info.parsed_docs)
|
||||
}
|
||||
}
|
||||
|
||||
let printed_docs = false
|
||||
if not info.is_external {
|
||||
// let convert_err = None::<EcoString>;
|
||||
if info.parsed_docs != none {
|
||||
let docs = info.parsed_docs
|
||||
if docs.docs != none and docs.docs.len() > 0 {
|
||||
// remove_list_annotations(docs.docs())
|
||||
printed_docs = true
|
||||
markdown-docs(docs.docs)
|
||||
}
|
||||
if docs.kind == "func" {
|
||||
labelled-heading(4, "Resultant")
|
||||
|
||||
docs.ret_ty.at(0)
|
||||
|
||||
labelled-heading(4, "Parameters")
|
||||
|
||||
let pos = docs.at("pos", default: ())
|
||||
let named = docs.at("named", default: (:)).values()
|
||||
let rest = docs.at("rest", default: none)
|
||||
|
||||
let params = pos + named
|
||||
if rest != none {
|
||||
params.push(rest)
|
||||
}
|
||||
|
||||
for param in params {
|
||||
labelled-heading(5, param.name)
|
||||
|
||||
let param-head = (param.name, ": ", param.cano_type.at(0))
|
||||
if param.positional {
|
||||
param-head.push(" (Positional)")
|
||||
}
|
||||
if param.variadic {
|
||||
param-head.push(" (Variadic)")
|
||||
}
|
||||
if param.settable {
|
||||
param-head.push(" (Settable)")
|
||||
}
|
||||
if param.named {
|
||||
param-head.push(" (Named, default: ")
|
||||
param-head.push(param.default)
|
||||
param-head.push(")")
|
||||
}
|
||||
raw(block: true, param-head.join())
|
||||
|
||||
markdown-docs(param.docs)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if not printed_docs {
|
||||
let plain_docs = info.docs
|
||||
if plain_docs == none {
|
||||
plain_docs = info.oneliner
|
||||
}
|
||||
// todo: eval with error tolerance?
|
||||
// if plain_docs != none {
|
||||
// eval(plain_docs, mode: "markup")
|
||||
// }
|
||||
|
||||
// if let Some(lnk) = &child.module_link {
|
||||
// match lnk.as_str() {
|
||||
// "builtin" => {
|
||||
// let _ = writeln!(out, "A Builtin Module");
|
||||
// }
|
||||
// _lnk => {
|
||||
// // let _ = writeln!(out, "#link({})[Module Docs]\n",
|
||||
// // TypstLink(lnk));
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// // let _ = writeln!(out, "<!-- end:symbol {ident} -->");
|
||||
// let _ = writeln!(out, "]),");
|
||||
}
|
||||
}
|
||||
|
||||
#let analyze-package(p) = {
|
||||
(
|
||||
packages: p.packages,
|
||||
files: p.files,
|
||||
)
|
||||
}
|
||||
|
||||
#let analyze-module(m) = {
|
||||
let modules = ()
|
||||
let functions = ()
|
||||
let constants = ()
|
||||
let unknowns = ()
|
||||
|
||||
for child in m.children {
|
||||
if child.kind == "module" {
|
||||
modules.push(child)
|
||||
} else if child.kind == "function" {
|
||||
functions.push(child)
|
||||
} else if child.kind == "variable" {
|
||||
constants.push(child)
|
||||
} else {
|
||||
unknowns.push(child)
|
||||
}
|
||||
}
|
||||
|
||||
(
|
||||
modules: modules.sorted(key: it => it.id),
|
||||
functions: functions,
|
||||
constants: constants,
|
||||
unknowns: unknowns,
|
||||
)
|
||||
}
|
||||
|
||||
#let module-doc(info: none, name: none, symbol-ctx, m) = {
|
||||
let m = analyze-module(m)
|
||||
|
||||
if info.prefix.len() > 0 {
|
||||
let primary = info.prefix
|
||||
let title = "Module: " + primary + " in " + info.prefix
|
||||
|
||||
module-divider
|
||||
labelled-heading(1, title)
|
||||
} else {
|
||||
labelled-heading(1, "Package Exports")
|
||||
}
|
||||
|
||||
let symbol-ctx = (
|
||||
in-module: info.prefix,
|
||||
..symbol-ctx,
|
||||
)
|
||||
|
||||
if m.modules.len() > 0 {
|
||||
[== Modules]
|
||||
for child in m.modules {
|
||||
symbol-doc(symbol-ctx, child)
|
||||
}
|
||||
}
|
||||
if m.constants.len() > 0 {
|
||||
[== Constants]
|
||||
for child in m.constants {
|
||||
symbol-doc(symbol-ctx, child)
|
||||
}
|
||||
}
|
||||
if m.functions.len() > 0 {
|
||||
[== Functions]
|
||||
for child in m.functions {
|
||||
symbol-doc(symbol-ctx, child)
|
||||
}
|
||||
}
|
||||
if m.unknowns.len() > 0 {
|
||||
[== Unknowns]
|
||||
for child in m.unknowns {
|
||||
symbol-doc(symbol-ctx, child)
|
||||
}
|
||||
}
|
||||
}
|
||||
#let package-doc(info) = {
|
||||
let info = json(info)
|
||||
let title = "@" + info.meta.namespace + "/" + info.meta.name + " " + info.meta.version
|
||||
|
||||
show: project.with(title: title)
|
||||
html.elem("style", read("/typ/packages/package-docs/global.css"))
|
||||
show: html.elem.with("main")
|
||||
|
||||
strong[
|
||||
This documentation is generated locally. Please submit issues to #link("https://github.com/Myriad-Dreamin/tinymist")[tinymist] if you see incorrect information in it.
|
||||
]
|
||||
|
||||
html.elem("h1", attrs: (id: "package-doc-title"), title)
|
||||
|
||||
let repo = info.meta.manifest.package.at("repository", default: none)
|
||||
if repo != none {
|
||||
let repo_link = html.elem("a", attrs: (href: repo, class: "package-repo-link"), "Repository")
|
||||
html.elem("p", repo_link)
|
||||
}
|
||||
|
||||
let description = info.meta.manifest.package.at("description", default: none)
|
||||
if description != none {
|
||||
description
|
||||
}
|
||||
|
||||
let symbol-ctx = analyze-package(info)
|
||||
|
||||
|
||||
for (name, m, info) in info.modules {
|
||||
module-doc(info: info, name: name, symbol-ctx, m)
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,8 @@ use std::path::PathBuf;
|
|||
use ecow::{EcoString, EcoVec};
|
||||
use indexmap::IndexSet;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tinymist_analysis::docs::tidy::remove_list_annotations;
|
||||
use tinymist_world::package::PackageSpec;
|
||||
use typst::diag::{eco_format, StrResult};
|
||||
use typst::syntax::package::PackageManifest;
|
||||
use typst::syntax::{FileId, Span};
|
||||
|
|
@ -13,13 +15,29 @@ use crate::docs::{file_id_repr, module_docs, DefDocs, PackageDefInfo};
|
|||
use crate::package::{get_manifest_id, PackageInfo};
|
||||
use crate::LocalContext;
|
||||
|
||||
use tinymist_analysis::docs::tidy::remove_list_annotations;
|
||||
/// Documentation Information about a package.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PackageDoc {
|
||||
meta: PackageMeta,
|
||||
packages: Vec<PackageMeta>,
|
||||
files: Vec<FileMeta>,
|
||||
modules: Vec<(EcoString, crate::docs::DefInfo, ModuleInfo)>,
|
||||
}
|
||||
|
||||
/// Documentation Information about a package module.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
struct ModuleInfo {
|
||||
prefix: EcoString,
|
||||
name: EcoString,
|
||||
loc: Option<usize>,
|
||||
parent_ident: EcoString,
|
||||
aka: EcoVec<String>,
|
||||
}
|
||||
|
||||
/// Generate full documents in markdown format
|
||||
pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<String> {
|
||||
pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<PackageDoc> {
|
||||
log::info!("generate_md_docs {spec:?}");
|
||||
|
||||
let mut md = String::new();
|
||||
let toml_id = get_manifest_id(spec)?;
|
||||
let manifest = ctx.get_manifest(toml_id)?;
|
||||
|
||||
|
|
@ -32,16 +50,6 @@ pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<Str
|
|||
|
||||
crate::log_debug_ct!("module_uses: {module_uses:#?}");
|
||||
|
||||
let title = for_spec.to_string();
|
||||
|
||||
let mut errors = vec![];
|
||||
|
||||
writeln!(md, "# {title}").unwrap();
|
||||
md.push('\n');
|
||||
writeln!(md, "This documentation is generated locally. Please submit issues to [tinymist](https://github.com/Myriad-Dreamin/tinymist/issues) if you see **incorrect** information in it.").unwrap();
|
||||
md.push('\n');
|
||||
md.push('\n');
|
||||
|
||||
let manifest = ctx.get_manifest(toml_id)?;
|
||||
|
||||
let meta = PackageMeta {
|
||||
|
|
@ -50,8 +58,6 @@ pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<Str
|
|||
version: spec.version.to_string(),
|
||||
manifest: Some(manifest),
|
||||
};
|
||||
let package_meta = jbase64(&meta);
|
||||
let _ = writeln!(md, "<!-- begin:package {package_meta} -->");
|
||||
|
||||
let mut modules_to_generate = vec![(root.name.clone(), root)];
|
||||
let mut generated_modules = HashSet::new();
|
||||
|
|
@ -72,10 +78,11 @@ pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<Str
|
|||
.clone()
|
||||
};
|
||||
|
||||
let mut modules = vec![];
|
||||
|
||||
while !modules_to_generate.is_empty() {
|
||||
for (parent_ident, def) in std::mem::take(&mut modules_to_generate) {
|
||||
for (parent_ident, mut def) in std::mem::take(&mut modules_to_generate) {
|
||||
// parent_ident, symbols
|
||||
let children = def.children;
|
||||
|
||||
let module_val = def.decl.as_ref().unwrap();
|
||||
let fid = module_val.file_id();
|
||||
|
|
@ -84,32 +91,18 @@ pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<Str
|
|||
// It is (primary) known to safe as a part of HTML string, so we don't have to
|
||||
// do sanitization here.
|
||||
let primary = aka.first().cloned().unwrap_or_default();
|
||||
if !primary.is_empty() {
|
||||
let _ = writeln!(md, "---\n## Module: {primary}");
|
||||
}
|
||||
|
||||
crate::log_debug_ct!("module: {primary} -- {parent_ident}");
|
||||
|
||||
let persist_fid = fid.map(|fid| file_ids.insert_full(fid).0);
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct ModuleInfo {
|
||||
prefix: EcoString,
|
||||
name: EcoString,
|
||||
loc: Option<usize>,
|
||||
parent_ident: EcoString,
|
||||
aka: EcoVec<String>,
|
||||
}
|
||||
let module_info = jbase64(&ModuleInfo {
|
||||
let module_info = ModuleInfo {
|
||||
prefix: primary.as_str().into(),
|
||||
name: def.name.clone(),
|
||||
loc: persist_fid,
|
||||
parent_ident: parent_ident.clone(),
|
||||
aka,
|
||||
});
|
||||
let _ = writeln!(md, "<!-- begin:module {primary} {module_info} -->");
|
||||
};
|
||||
|
||||
for mut child in children {
|
||||
for child in def.children.iter_mut() {
|
||||
let span = child.decl.as_ref().map(|decl| decl.span());
|
||||
let fid_range = span.and_then(|v| {
|
||||
v.id().and_then(|fid| {
|
||||
|
|
@ -127,9 +120,7 @@ pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<Str
|
|||
});
|
||||
child.loc = span;
|
||||
|
||||
let convert_err = None::<EcoString>;
|
||||
if let Some(docs) = &child.parsed_docs {
|
||||
child.parsed_docs = Some(docs.clone());
|
||||
if child.parsed_docs.is_some() {
|
||||
child.docs = None;
|
||||
}
|
||||
|
||||
|
|
@ -138,7 +129,6 @@ pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<Str
|
|||
} else {
|
||||
eco_format!("symbol-{}-{}", child.kind, child.name)
|
||||
};
|
||||
let _ = writeln!(md, "### {}: {} in {primary}", child.kind, child.name);
|
||||
|
||||
if child.is_external {
|
||||
if let Some(fid) = child_fid {
|
||||
|
|
@ -163,112 +153,40 @@ pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<Str
|
|||
child.external_link = Some(lnk.clone());
|
||||
lnk
|
||||
};
|
||||
let _ = writeln!(md, "[Symbol Docs]({lnk})\n");
|
||||
child.symbol_link = Some(lnk);
|
||||
}
|
||||
}
|
||||
|
||||
let child_children = std::mem::take(&mut child.children);
|
||||
let head = jbase64(&child);
|
||||
let _ = writeln!(md, "<!-- begin:symbol {ident} {head} -->");
|
||||
|
||||
if let Some(DefDocs::Function(sig)) = &child.parsed_docs {
|
||||
let _ = writeln!(md, "<!-- begin:sig -->");
|
||||
let _ = writeln!(md, "```typc");
|
||||
let _ = write!(md, "let {}", child.name);
|
||||
let _ = sig.print(&mut md);
|
||||
let _ = writeln!(md, ";");
|
||||
let _ = writeln!(md, "```");
|
||||
let _ = writeln!(md, "<!-- end:sig -->");
|
||||
}
|
||||
|
||||
let mut printed_docs = false;
|
||||
match (&child.parsed_docs, convert_err) {
|
||||
(_, Some(err)) => {
|
||||
let err = format!("failed to convert docs in {title}: {err}").replace(
|
||||
"-->", "—>", // avoid markdown comment
|
||||
);
|
||||
let _ = writeln!(md, "<!-- convert-error: {err} -->");
|
||||
errors.push(err);
|
||||
}
|
||||
(Some(docs), _) if !child.is_external => {
|
||||
let _ = writeln!(md, "{}", remove_list_annotations(docs.docs()));
|
||||
printed_docs = true;
|
||||
if let DefDocs::Function(docs) = docs {
|
||||
for param in docs
|
||||
.pos
|
||||
.iter()
|
||||
.chain(docs.named.values())
|
||||
.chain(docs.rest.as_ref())
|
||||
{
|
||||
let _ = writeln!(md, "<!-- begin:param {} -->", param.name);
|
||||
let ty = match ¶m.cano_type {
|
||||
Some((short, _, _)) => short,
|
||||
None => "unknown",
|
||||
};
|
||||
let _ = writeln!(
|
||||
md,
|
||||
"#### {} ({ty:?})\n<!-- begin:param-doc {} -->\n{}\n<!-- end:param-doc {} -->",
|
||||
param.name, param.name, param.docs, param.name
|
||||
);
|
||||
let _ = writeln!(md, "<!-- end:param -->");
|
||||
}
|
||||
}
|
||||
}
|
||||
(_, None) => {}
|
||||
}
|
||||
|
||||
if !printed_docs {
|
||||
let plain_docs = child.docs.as_deref();
|
||||
let plain_docs = plain_docs.or(child.oneliner.as_deref());
|
||||
|
||||
if let Some(docs) = plain_docs {
|
||||
let contains_code = docs.contains("```");
|
||||
if contains_code {
|
||||
let _ = writeln!(md, "`````typ");
|
||||
}
|
||||
let _ = writeln!(md, "{docs}");
|
||||
if contains_code {
|
||||
let _ = writeln!(md, "`````");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !child_children.is_empty() {
|
||||
crate::log_debug_ct!("sub_fid: {child_fid:?}");
|
||||
match child_fid {
|
||||
let lnk = match child_fid {
|
||||
Some(fid) => {
|
||||
let aka = akas(fid);
|
||||
let primary = aka.first().cloned().unwrap_or_default();
|
||||
let link = format!("module-{primary}").replace(".", "");
|
||||
let _ = writeln!(md, "[Module Docs](#{link})\n");
|
||||
|
||||
if generated_modules.insert(fid) {
|
||||
let mut child = child.clone();
|
||||
child.children = child_children;
|
||||
modules_to_generate.push((ident.clone(), child));
|
||||
}
|
||||
|
||||
let link = format!("module-{primary}").replace(".", "");
|
||||
format!("#{link}")
|
||||
}
|
||||
None => {
|
||||
let _ = writeln!(md, "A Builtin Module");
|
||||
}
|
||||
}
|
||||
None => "builtin".to_owned(),
|
||||
};
|
||||
|
||||
child.module_link = Some(lnk);
|
||||
}
|
||||
|
||||
let _ = writeln!(md, "<!-- end:symbol {ident} -->");
|
||||
child.id = ident;
|
||||
}
|
||||
|
||||
let _ = writeln!(md, "<!-- end:module {primary} -->");
|
||||
modules.push((parent_ident, def, module_info));
|
||||
}
|
||||
}
|
||||
|
||||
let res = ConvertResult { errors };
|
||||
let err = jbase64(&res);
|
||||
let _ = writeln!(md, "<!-- begin:errors {err} -->");
|
||||
let _ = writeln!(md, "## Errors");
|
||||
for errs in res.errors {
|
||||
let _ = writeln!(md, "- {errs}");
|
||||
}
|
||||
let _ = writeln!(md, "<!-- end:errors -->");
|
||||
|
||||
let mut packages = IndexSet::new();
|
||||
|
||||
let files = file_ids
|
||||
|
|
@ -295,11 +213,172 @@ pub fn package_docs(ctx: &mut LocalContext, spec: &PackageInfo) -> StrResult<Str
|
|||
})
|
||||
.collect();
|
||||
|
||||
let meta = PackageMetaEnd { packages, files };
|
||||
let package_meta = jbase64(&meta);
|
||||
let _ = writeln!(md, "<!-- end:package {package_meta} -->");
|
||||
let doc = PackageDoc {
|
||||
meta,
|
||||
packages,
|
||||
files,
|
||||
modules,
|
||||
};
|
||||
|
||||
Ok(md)
|
||||
Ok(doc)
|
||||
}
|
||||
|
||||
/// Generate full documents in markdown format
|
||||
pub fn package_docs_typ(doc: &PackageDoc) -> StrResult<String> {
|
||||
let mut out = String::new();
|
||||
|
||||
let _ = writeln!(out, "{}", include_str!("package-doc.typ"));
|
||||
|
||||
let pi = &doc.meta;
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"#package-doc(bytes(read(\"{}-{}-{}.json\")))",
|
||||
pi.namespace, pi.name, pi.version,
|
||||
);
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
/// Generate full documents in markdown format
|
||||
pub fn package_docs_md(doc: &PackageDoc) -> StrResult<String> {
|
||||
let mut out = String::new();
|
||||
|
||||
let title = doc.meta.spec().to_string();
|
||||
|
||||
writeln!(out, "# {title}").unwrap();
|
||||
out.push('\n');
|
||||
writeln!(out, "This documentation is generated locally. Please submit issues to [tinymist](https://github.com/Myriad-Dreamin/tinymist/issues) if you see **incorrect** information in it.").unwrap();
|
||||
out.push('\n');
|
||||
out.push('\n');
|
||||
|
||||
let package_meta = jbase64(&doc.meta);
|
||||
let _ = writeln!(out, "<!-- begin:package {package_meta} -->");
|
||||
|
||||
let mut errors = vec![];
|
||||
for (parent_ident, def, module_info) in &doc.modules {
|
||||
// parent_ident, symbols
|
||||
let primary = &module_info.prefix;
|
||||
if !module_info.prefix.is_empty() {
|
||||
let _ = writeln!(out, "---\n## Module: {primary}");
|
||||
}
|
||||
|
||||
crate::log_debug_ct!("module: {primary} -- {parent_ident}");
|
||||
let module_info = jbase64(&module_info);
|
||||
let _ = writeln!(out, "<!-- begin:module {primary} {module_info} -->");
|
||||
|
||||
for child in &def.children {
|
||||
let convert_err = None::<EcoString>;
|
||||
|
||||
let ident = if !primary.is_empty() {
|
||||
eco_format!("symbol-{}-{primary}.{}", child.kind, child.name)
|
||||
} else {
|
||||
eco_format!("symbol-{}-{}", child.kind, child.name)
|
||||
};
|
||||
let _ = writeln!(out, "### {}: {} in {primary}", child.kind, child.name);
|
||||
|
||||
if let Some(lnk) = &child.symbol_link {
|
||||
let _ = writeln!(out, "[Symbol Docs]({lnk})\n");
|
||||
}
|
||||
|
||||
let head = jbase64(&child);
|
||||
let _ = writeln!(out, "<!-- begin:symbol {ident} {head} -->");
|
||||
|
||||
if let Some(DefDocs::Function(sig)) = &child.parsed_docs {
|
||||
let _ = writeln!(out, "<!-- begin:sig -->");
|
||||
let _ = writeln!(out, "```typc");
|
||||
let _ = write!(out, "let {}", child.name);
|
||||
let _ = sig.print(&mut out);
|
||||
let _ = writeln!(out, ";");
|
||||
let _ = writeln!(out, "```");
|
||||
let _ = writeln!(out, "<!-- end:sig -->");
|
||||
}
|
||||
|
||||
let mut printed_docs = false;
|
||||
match (&child.parsed_docs, convert_err) {
|
||||
(_, Some(err)) => {
|
||||
let err = format!("failed to convert docs in {title}: {err}").replace(
|
||||
"-->", "—>", // avoid markdown comment
|
||||
);
|
||||
let _ = writeln!(out, "<!-- convert-error: {err} -->");
|
||||
errors.push(err);
|
||||
}
|
||||
(Some(docs), _) if !child.is_external => {
|
||||
let _ = writeln!(out, "{}", remove_list_annotations(docs.docs()));
|
||||
printed_docs = true;
|
||||
if let DefDocs::Function(docs) = docs {
|
||||
for param in docs
|
||||
.pos
|
||||
.iter()
|
||||
.chain(docs.named.values())
|
||||
.chain(docs.rest.as_ref())
|
||||
{
|
||||
let _ = writeln!(out, "<!-- begin:param {} -->", param.name);
|
||||
let ty = match ¶m.cano_type {
|
||||
Some((short, _, _)) => short,
|
||||
None => "unknown",
|
||||
};
|
||||
let _ = writeln!(
|
||||
out,
|
||||
"#### {} ({ty:?})\n<!-- begin:param-doc {} -->\n{}\n<!-- end:param-doc {} -->",
|
||||
param.name, param.name, param.docs, param.name
|
||||
);
|
||||
let _ = writeln!(out, "<!-- end:param -->");
|
||||
}
|
||||
}
|
||||
}
|
||||
(_, None) => {}
|
||||
}
|
||||
|
||||
if !printed_docs {
|
||||
let plain_docs = child.docs.as_deref();
|
||||
let plain_docs = plain_docs.or(child.oneliner.as_deref());
|
||||
|
||||
if let Some(docs) = plain_docs {
|
||||
let contains_code = docs.contains("```");
|
||||
if contains_code {
|
||||
let _ = writeln!(out, "`````typ");
|
||||
}
|
||||
let _ = writeln!(out, "{docs}");
|
||||
if contains_code {
|
||||
let _ = writeln!(out, "`````");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(lnk) = &child.module_link {
|
||||
match lnk.as_str() {
|
||||
"builtin" => {
|
||||
let _ = writeln!(out, "A Builtin Module");
|
||||
}
|
||||
lnk => {
|
||||
let _ = writeln!(out, "[Module Docs]({lnk})\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let _ = writeln!(out, "<!-- end:symbol {ident} -->");
|
||||
}
|
||||
|
||||
let _ = writeln!(out, "<!-- end:module {primary} -->");
|
||||
}
|
||||
|
||||
let res = ConvertResult { errors };
|
||||
let err = jbase64(&res);
|
||||
let _ = writeln!(out, "<!-- begin:errors {err} -->");
|
||||
let _ = writeln!(out, "## Errors");
|
||||
for errs in res.errors {
|
||||
let _ = writeln!(out, "- {errs}");
|
||||
}
|
||||
let _ = writeln!(out, "<!-- end:errors -->");
|
||||
|
||||
let meta = PackageMetaEnd {
|
||||
packages: doc.packages.clone(),
|
||||
files: doc.files.clone(),
|
||||
};
|
||||
let package_meta = jbase64(&meta);
|
||||
let _ = writeln!(out, "<!-- end:package {package_meta} -->");
|
||||
|
||||
Ok(out)
|
||||
}
|
||||
|
||||
fn jbase64<T: Serialize>(s: &T) -> String {
|
||||
|
|
@ -309,7 +388,7 @@ fn jbase64<T: Serialize>(s: &T) -> String {
|
|||
}
|
||||
|
||||
/// Information about a package.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PackageMeta {
|
||||
/// The namespace the package lives in.
|
||||
pub namespace: EcoString,
|
||||
|
|
@ -321,6 +400,17 @@ pub struct PackageMeta {
|
|||
pub manifest: Option<PackageManifest>,
|
||||
}
|
||||
|
||||
impl PackageMeta {
|
||||
/// Returns the package's full name, including namespace and version.
|
||||
pub fn spec(&self) -> PackageSpec {
|
||||
PackageSpec {
|
||||
namespace: self.namespace.clone(),
|
||||
name: self.name.clone(),
|
||||
version: self.version.parse().expect("Invalid version format"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about a package.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
pub struct PackageMetaEnd {
|
||||
|
|
@ -329,7 +419,7 @@ pub struct PackageMetaEnd {
|
|||
}
|
||||
|
||||
/// Information about a package.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct FileMeta {
|
||||
package: Option<usize>,
|
||||
path: PathBuf,
|
||||
|
|
@ -344,7 +434,7 @@ struct ConvertResult {
|
|||
mod tests {
|
||||
use tinymist_world::package::{PackageRegistry, PackageSpec};
|
||||
|
||||
use super::{package_docs, PackageInfo};
|
||||
use super::{package_docs, package_docs_md, package_docs_typ, PackageInfo};
|
||||
use crate::tests::*;
|
||||
|
||||
fn test(pkg: PackageSpec) {
|
||||
|
|
@ -358,11 +448,23 @@ mod tests {
|
|||
};
|
||||
run_with_ctx(verse, path, &|a, _p| {
|
||||
let docs = package_docs(a, &pi).unwrap();
|
||||
let dest = format!(
|
||||
"../../target/{}-{}-{}.json",
|
||||
pi.namespace, pi.name, pi.version
|
||||
);
|
||||
std::fs::write(dest, serde_json::to_string_pretty(&docs).unwrap()).unwrap();
|
||||
let typ = package_docs_typ(&docs).unwrap();
|
||||
let dest = format!(
|
||||
"../../target/{}-{}-{}.typ",
|
||||
pi.namespace, pi.name, pi.version
|
||||
);
|
||||
std::fs::write(dest, typ).unwrap();
|
||||
let md = package_docs_md(&docs).unwrap();
|
||||
let dest = format!(
|
||||
"../../target/{}-{}-{}.md",
|
||||
pi.namespace, pi.name, pi.version
|
||||
);
|
||||
std::fs::write(dest, docs).unwrap();
|
||||
std::fs::write(dest, md).unwrap();
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
@ -381,7 +483,7 @@ mod tests {
|
|||
test(PackageSpec {
|
||||
namespace: "preview".into(),
|
||||
name: "touying".into(),
|
||||
version: "0.5.2".parse().unwrap(),
|
||||
version: "0.6.0".parse().unwrap(),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -735,7 +735,10 @@ impl ServerState {
|
|||
info: PackageInfo,
|
||||
) -> LspResult<impl Future<Output = LspResult<String>>> {
|
||||
self.within_package(info.clone(), move |a| {
|
||||
tinymist_query::docs::package_docs(a, &info)
|
||||
let doc = tinymist_query::docs::package_docs(a, &info)
|
||||
.map_err(map_string_err("failed to generate docs"))
|
||||
.map_err(internal_error)?;
|
||||
tinymist_query::docs::package_docs_md(&doc)
|
||||
.map_err(map_string_err("failed to generate docs"))
|
||||
.map_err(internal_error)
|
||||
})
|
||||
|
|
|
|||
323
typ/packages/package-docs/global.css
Normal file
323
typ/packages/package-docs/global.css
Normal file
|
|
@ -0,0 +1,323 @@
|
|||
@import url("https://fonts.googleapis.com/css2?family=Merriweather:ital,wght@0,300;0,700;0,900;1,300;1,700&display=swap");
|
||||
|
||||
main {
|
||||
margin: 2em;
|
||||
}
|
||||
:root.light {
|
||||
--main-color: #000;
|
||||
--main-hover-color: #222939;
|
||||
--raw-bg-color: rgba(101, 117, 133, 0.16);
|
||||
--main-bg-color: #fafcfc;
|
||||
--nav-bg-color: #fafcfc;
|
||||
--gray-color: #6d6d6d;
|
||||
--accent: oklch(51.51% 0.2307 257.85);
|
||||
--accent-dark: oklch(64.94% 0.1982 251.813);
|
||||
--black: #0f1219;
|
||||
}
|
||||
|
||||
:root {
|
||||
--mainLight: hsl(250, 68%, 74%);
|
||||
--textDetailAccent: var(--mainLight);
|
||||
--main-color: #dfdfd6;
|
||||
--main-hover-color: #fff;
|
||||
--gray-color: #939da3;
|
||||
--raw-bg-color: #65758529;
|
||||
--main-bg-color: #212737;
|
||||
--nav-bg-color: #212737;
|
||||
--accent: oklch(71.7% 0.1648 250.794);
|
||||
--accent-dark: oklch(51.51% 0.2307 257.85);
|
||||
--vp-font-family-mono: ui-monospace, "Menlo", "Monaco", "Consolas", "Liberation Mono",
|
||||
"Courier New", monospace;
|
||||
--vp-font-family-base:
|
||||
Merriweather,
|
||||
serif,
|
||||
"Inter var experimental",
|
||||
"Inter var",
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
"Segoe UI",
|
||||
Roboto,
|
||||
Oxygen,
|
||||
Ubuntu,
|
||||
Cantarell,
|
||||
"Fira Sans",
|
||||
"Droid Sans",
|
||||
"Helvetica Neue",
|
||||
sans-serif;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
text-align: justify;
|
||||
font-family: var(--vp-font-family-base);
|
||||
background: var(--main-bg-color);
|
||||
background-size: 100% 600px;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
color: var(--main-color);
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
h1 :target,
|
||||
h2 :target,
|
||||
h3 :target,
|
||||
h4 :target,
|
||||
h5 :target,
|
||||
h6 :target {
|
||||
scroll-margin-top: 1.25em;
|
||||
}
|
||||
h1 {
|
||||
font-size: 2.75em;
|
||||
margin-block-start: 0em;
|
||||
margin-block-end: 0.8888889em;
|
||||
margin-inline-start: 0px;
|
||||
margin-inline-end: 0px;
|
||||
line-height: 1.1111111;
|
||||
}
|
||||
h2 {
|
||||
font-size: 2em;
|
||||
margin-block-start: 1.6em;
|
||||
margin-block-end: 0.6em;
|
||||
margin-inline-start: 0px;
|
||||
margin-inline-end: 0px;
|
||||
line-height: 1.3333333;
|
||||
}
|
||||
h3 {
|
||||
font-size: 1.5em;
|
||||
margin-block-start: 1.5em;
|
||||
margin-block-end: 0.6em;
|
||||
margin-inline-start: 0px;
|
||||
margin-inline-end: 0px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
h4 {
|
||||
font-size: 1.25em;
|
||||
margin-block-start: 1.5em;
|
||||
margin-block-end: 0.6em;
|
||||
margin-inline-start: 0px;
|
||||
margin-inline-end: 0px;
|
||||
line-height: 1.6;
|
||||
}
|
||||
h5 {
|
||||
font-size: 1.1em;
|
||||
margin-block-start: 1.5em;
|
||||
margin-block-end: 0.5em;
|
||||
margin-inline-start: 0px;
|
||||
margin-inline-end: 0px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
p {
|
||||
margin-block-end: 0.5em;
|
||||
}
|
||||
strong,
|
||||
b {
|
||||
font-weight: 700;
|
||||
}
|
||||
a,
|
||||
.link {
|
||||
color: var(--accent);
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
a,
|
||||
.link {
|
||||
transition:
|
||||
color 0.1s,
|
||||
underline 0.1s;
|
||||
}
|
||||
a:hover,
|
||||
.link:hover {
|
||||
color: var(--accent-dark);
|
||||
text-decoration: underline solid 2px;
|
||||
}
|
||||
textarea {
|
||||
width: 100%;
|
||||
font-size: 16px;
|
||||
}
|
||||
input {
|
||||
font-size: 16px;
|
||||
}
|
||||
table {
|
||||
width: 100%;
|
||||
}
|
||||
img {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
border-radius: 8px;
|
||||
}
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: var(--vp-font-family-mono);
|
||||
}
|
||||
code {
|
||||
padding: 2px 5px;
|
||||
background-color: var(--raw-bg-color);
|
||||
border-radius: 2px;
|
||||
}
|
||||
pre {
|
||||
padding: 1.5em;
|
||||
border-radius: 8px;
|
||||
}
|
||||
pre > code {
|
||||
all: unset;
|
||||
}
|
||||
blockquote {
|
||||
border-left: 4px solid var(--accent);
|
||||
padding: 0 0 0 18px;
|
||||
margin: 0px;
|
||||
font-size: 1.333em;
|
||||
}
|
||||
hr {
|
||||
border: none;
|
||||
border-top: 1px solid var(--raw-bg-color);
|
||||
}
|
||||
|
||||
.sr-only {
|
||||
border: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: absolute !important;
|
||||
height: 1px;
|
||||
width: 1px;
|
||||
overflow: hidden;
|
||||
/* IE6, IE7 - a 0 height clip, off to the bottom right of the visible 1px box */
|
||||
clip: rect(1px 1px 1px 1px);
|
||||
/* maybe deprecated but we need to support legacy browsers */
|
||||
clip: rect(1px, 1px, 1px, 1px);
|
||||
/* modern browsers, clip-path works inwards from each corner */
|
||||
clip-path: inset(50%);
|
||||
/* added line to stop words getting smushed together (as they go onto separate lines and some screen readers do not understand line feeds as a space */
|
||||
white-space: nowrap;
|
||||
}
|
||||
nav a,
|
||||
.social-links a {
|
||||
text-decoration: none;
|
||||
color: var(--main-color);
|
||||
}
|
||||
nav a:hover,
|
||||
.social-links a:hover {
|
||||
color: var(--main-hover-color);
|
||||
}
|
||||
.icon svg {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
overflow: visible;
|
||||
}
|
||||
.icon svg path,
|
||||
.icon svg circle {
|
||||
fill: currentColor;
|
||||
}
|
||||
.theme-icon {
|
||||
cursor: pointer;
|
||||
}
|
||||
.dark .theme-icon.light {
|
||||
display: none;
|
||||
}
|
||||
.dark .theme-icon.dark {
|
||||
display: dark;
|
||||
}
|
||||
.theme-icon.light {
|
||||
display: dark;
|
||||
}
|
||||
.theme-icon.dark {
|
||||
display: none;
|
||||
}
|
||||
.dark .code-image.themed .light {
|
||||
display: none;
|
||||
}
|
||||
.dark .code-image.themed .dark {
|
||||
display: initial;
|
||||
}
|
||||
.code-image.themed .light {
|
||||
display: initial;
|
||||
}
|
||||
.code-image.themed .dark {
|
||||
display: none;
|
||||
}
|
||||
|
||||
figcaption {
|
||||
text-align: center;
|
||||
}
|
||||
.code-image svg {
|
||||
max-width: 100%;
|
||||
height: fit-content;
|
||||
}
|
||||
.inline-equation {
|
||||
display: inline-block;
|
||||
width: fit-content;
|
||||
margin: 0 0.15em;
|
||||
}
|
||||
.block-equation {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
margin: 1em 0;
|
||||
margin-top: 1.5em;
|
||||
padding: 0.5em 1em;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border-left: 3px solid var(--textDetailAccent);
|
||||
font-size: 1em;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.detail-header h4 {
|
||||
font-size: 1em;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.detail-header code {
|
||||
background-color: transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.detail-header span > p {
|
||||
display: inline-block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.type-int {
|
||||
color: #e7d9ff;
|
||||
}
|
||||
|
||||
.type-float {
|
||||
color: #e7d9ff;
|
||||
}
|
||||
|
||||
.type-builtin {
|
||||
color: #d2a8ff;
|
||||
}
|
||||
|
||||
.type-none {
|
||||
color: #ff6d00;
|
||||
}
|
||||
|
||||
.code-kw,
|
||||
.type-auto {
|
||||
color: #ff6d00;
|
||||
}
|
||||
|
||||
.code-func {
|
||||
color: #79c0ff;
|
||||
}
|
||||
|
||||
.code-op {
|
||||
color: #79c0ff;
|
||||
}
|
||||
|
||||
.type-inferred-as,
|
||||
.code-kw.type-inferred {
|
||||
transition: background-color 0.1s;
|
||||
}
|
||||
|
||||
.type-inferred-as:hover,
|
||||
.code-kw.type-inferred:hover {
|
||||
background-color: #344134;
|
||||
}
|
||||
0
typ/packages/package-docs/main.typ
Normal file
0
typ/packages/package-docs/main.typ
Normal file
278
typ/packages/package-docs/template.typ
Normal file
278
typ/packages/package-docs/template.typ
Normal file
|
|
@ -0,0 +1,278 @@
|
|||
// This is important for shiroa to produce a responsive layout
|
||||
// and multiple targets.
|
||||
#import "@preview/shiroa:0.2.2": (
|
||||
get-page-width, is-html-target, is-pdf-target, is-web-target, plain-text, shiroa-sys-target, target, templates,
|
||||
)
|
||||
#import templates: *
|
||||
#import "@preview/numbly:0.1.0": numbly
|
||||
#import "@preview/zebraw:0.5.2": zebraw, zebraw-init
|
||||
#import "theme.typ": *
|
||||
|
||||
// Metadata
|
||||
#let page-width = get-page-width()
|
||||
#let is-html-target = is-html-target()
|
||||
#let is-pdf-target = is-pdf-target()
|
||||
#let is-web-target = is-web-target()
|
||||
#let is-md-target = target == "md"
|
||||
#let sys-is-html-target = ("target" in dictionary(std))
|
||||
|
||||
#let part-counter = counter("shiroa-part-counter")
|
||||
/// Creates an embedded block typst frame.
|
||||
#let div-frame(content, attrs: (:)) = html.elem("div", html.frame(content), attrs: attrs)
|
||||
#let span-frame(content, attrs: (:)) = html.elem("span", html.frame(content), attrs: attrs)
|
||||
|
||||
// Theme (Colors)
|
||||
#let (
|
||||
style: theme-style,
|
||||
is-dark: is-dark-theme,
|
||||
is-light: is-light-theme,
|
||||
main-color: main-color,
|
||||
dash-color: dash-color,
|
||||
code-extra-colors: code-extra-colors,
|
||||
) = book-theme-from(toml("theme-style.toml"), xml: it => xml(it))
|
||||
#let gh-dark-fg = rgb("#f0f6fc")
|
||||
|
||||
// Fonts
|
||||
#let main-font = (
|
||||
"Charter",
|
||||
"Libertinus Serif",
|
||||
"Source Han Serif SC",
|
||||
// shiroa's embedded font
|
||||
)
|
||||
#let code-font = (
|
||||
"BlexMono Nerd Font Mono",
|
||||
// shiroa's embedded font
|
||||
"DejaVu Sans Mono",
|
||||
)
|
||||
|
||||
// Sizes
|
||||
#let main-size = if is-web-target {
|
||||
16pt
|
||||
} else {
|
||||
10.5pt
|
||||
}
|
||||
// ,
|
||||
#let heading-sizes = (22pt, 18pt, 14pt, 12pt, main-size, main-size, main-size)
|
||||
#let list-indent = 0.5em
|
||||
|
||||
#let raw-rules(body) = {
|
||||
let init-with-theme((code-extra-colors, is-dark)) = if is-dark {
|
||||
zebraw-init.with(
|
||||
// should vary by theme
|
||||
background-color: if code-extra-colors.bg != none {
|
||||
(code-extra-colors.bg, code-extra-colors.bg)
|
||||
},
|
||||
highlight-color: rgb("#3d59a1"),
|
||||
comment-color: rgb("#394b70"),
|
||||
lang-color: rgb("#3d59a1"),
|
||||
lang: false,
|
||||
numbering: false,
|
||||
)
|
||||
} else {
|
||||
zebraw-init.with(
|
||||
// should vary by theme
|
||||
background-color: if code-extra-colors.bg != none {
|
||||
(code-extra-colors.bg, code-extra-colors.bg)
|
||||
},
|
||||
lang: false,
|
||||
numbering: false,
|
||||
)
|
||||
}
|
||||
|
||||
/// HTML code block supported by zebraw.
|
||||
show: init-with-theme((code-extra-colors, is-dark-theme))
|
||||
|
||||
// code block setting
|
||||
set raw(theme: theme-style.code-theme) if theme-style.code-theme.len() > 0
|
||||
show raw: set text(font: code-font)
|
||||
show raw.where(block: true): it => context if shiroa-sys-target() == "paged" {
|
||||
rect(
|
||||
width: 100%,
|
||||
inset: (x: 4pt, y: 5pt),
|
||||
radius: 4pt,
|
||||
fill: code-extra-colors.bg,
|
||||
[
|
||||
#set text(fill: code-extra-colors.fg) if code-extra-colors.fg != none
|
||||
#set par(justify: false)
|
||||
// #place(right, text(luma(110), it.lang))
|
||||
#it
|
||||
],
|
||||
)
|
||||
} else {
|
||||
set text(fill: code-extra-colors.fg) if code-extra-colors.fg != none
|
||||
set par(justify: false)
|
||||
zebraw(
|
||||
block-width: 100%,
|
||||
// line-width: 100%,
|
||||
wrap: false,
|
||||
it,
|
||||
)
|
||||
}
|
||||
|
||||
body
|
||||
}
|
||||
|
||||
#let equation-rules(body) = {
|
||||
// equation setting
|
||||
show math.equation: set text(weight: 400)
|
||||
show math.equation.where(block: true): it => context if shiroa-sys-target() == "html" {
|
||||
div-frame(attrs: ("style": "display: flex; justify-content: center; overflow-x: auto;"), it)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
show math.equation.where(block: false): it => context if shiroa-sys-target() == "html" {
|
||||
span-frame(attrs: ("style": "overflow-x: auto;"), it)
|
||||
} else {
|
||||
it
|
||||
}
|
||||
|
||||
body
|
||||
}
|
||||
|
||||
#let md-equation-rules(body) = {
|
||||
// equation setting
|
||||
show math.equation: it => theme-box(
|
||||
tag: if it.block { "p" } else { "span" },
|
||||
theme => {
|
||||
set text(fill: if theme.is-dark { gh-dark-fg } else { theme.main-color })
|
||||
html.frame(it)
|
||||
},
|
||||
)
|
||||
|
||||
body
|
||||
}
|
||||
|
||||
/// The project function defines how your document looks.
|
||||
/// It takes your content and some metadata and formats it.
|
||||
/// Go ahead and customize it to your liking!
|
||||
#let project(title: "Tinymist Docs", authors: (), kind: "page", body) = {
|
||||
// set basic document metadata
|
||||
set document(
|
||||
author: authors,
|
||||
title: title,
|
||||
) if not is-pdf-target and not is-md-target
|
||||
|
||||
// todo dirty hack to check is main
|
||||
let is-main = title == "Tinymist Documentation"
|
||||
|
||||
// set web/pdf page properties
|
||||
set page(
|
||||
numbering: none,
|
||||
number-align: center,
|
||||
width: page-width,
|
||||
) if not (sys-is-html-target or is-html-target)
|
||||
set page(numbering: "1") if (not sys-is-html-target and is-pdf-target) and not is-main and kind == "page"
|
||||
|
||||
// remove margins for web target
|
||||
set page(
|
||||
margin: (
|
||||
// reserved beautiful top margin
|
||||
top: 20pt,
|
||||
// reserved for our heading style.
|
||||
// If you apply a different heading style, you may remove it.
|
||||
left: 20pt,
|
||||
// Typst is setting the page's bottom to the baseline of the last line of text. So bad :(.
|
||||
bottom: 0.5em,
|
||||
// remove rest margins.
|
||||
rest: 0pt,
|
||||
),
|
||||
height: auto,
|
||||
) if is-web-target and not is-html-target
|
||||
|
||||
// Set main text
|
||||
set text(
|
||||
font: main-font,
|
||||
size: main-size,
|
||||
fill: main-color,
|
||||
lang: "en",
|
||||
)
|
||||
|
||||
// Set main spacing
|
||||
set enum(
|
||||
indent: list-indent * 0.618,
|
||||
body-indent: list-indent,
|
||||
)
|
||||
set list(
|
||||
indent: list-indent * 0.618,
|
||||
body-indent: list-indent,
|
||||
)
|
||||
set par(leading: 0.7em)
|
||||
set block(spacing: 0.7em * 1.5)
|
||||
|
||||
// Set text, spacing for headings
|
||||
// Render a dash to hint headings instead of bolding it as well if it's for web.
|
||||
show heading: set text(weight: "regular") if is-web-target
|
||||
show heading: it => {
|
||||
set text(size: heading-sizes.at(it.level))
|
||||
|
||||
block(
|
||||
spacing: 0.7em * 1.5 * 1.2,
|
||||
below: 0.7em * 1.2,
|
||||
{
|
||||
// if is-web-target {
|
||||
// heading-hash(it, hash-color: dash-color)
|
||||
// }
|
||||
|
||||
it
|
||||
},
|
||||
)
|
||||
}
|
||||
set heading(
|
||||
numbering: (..numbers) => context {
|
||||
if part-counter.get().at(0) > 0 {
|
||||
numbering("1.", ..part-counter.get(), ..numbers)
|
||||
} else {
|
||||
h(-0.3em)
|
||||
}
|
||||
},
|
||||
) if is-pdf-target
|
||||
|
||||
// link setting
|
||||
show link: set text(fill: dash-color)
|
||||
|
||||
show: if is-md-target {
|
||||
md-equation-rules
|
||||
} else {
|
||||
equation-rules
|
||||
}
|
||||
|
||||
show: if is-md-target {
|
||||
it => it
|
||||
} else {
|
||||
raw-rules
|
||||
}
|
||||
|
||||
if not is-md-target {
|
||||
context if shiroa-sys-target() == "html" {
|
||||
show raw: it => html.elem("style", it.text)
|
||||
```css
|
||||
.pseudo-image svg {
|
||||
width: 100%
|
||||
}
|
||||
```
|
||||
}
|
||||
}
|
||||
|
||||
show <typst-raw-func>: it => {
|
||||
it.lines.at(0).body.children.slice(0, -2).join()
|
||||
}
|
||||
|
||||
if kind == "page" and is-pdf-target and not is-main {
|
||||
heading(level: 1, numbering: none, text(size: 32pt, title))
|
||||
}
|
||||
|
||||
// Main body.
|
||||
set par(justify: true)
|
||||
|
||||
body
|
||||
}
|
||||
|
||||
#let part-style(it) = {
|
||||
set text(size: heading-sizes.at(0))
|
||||
set text(weight: "bold")
|
||||
set text(fill: main-color)
|
||||
part-counter.step()
|
||||
|
||||
context heading(numbering: none, [Part #part-counter.display(numbly("{1}. "))#it])
|
||||
counter(heading).update(0)
|
||||
}
|
||||
30
typ/packages/package-docs/theme-style.toml
Normal file
30
typ/packages/package-docs/theme-style.toml
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
|
||||
[light]
|
||||
color-scheme = "light"
|
||||
main-color = "#000"
|
||||
dash-color = "#20609f"
|
||||
code-theme = ""
|
||||
|
||||
[rust]
|
||||
color-scheme = "light"
|
||||
main-color = "#262625"
|
||||
dash-color = "#2b79a2"
|
||||
code-theme = ""
|
||||
|
||||
[coal]
|
||||
color-scheme = "dark"
|
||||
main-color = "#98a3ad"
|
||||
dash-color = "#2b79a2"
|
||||
code-theme = "tokyo-night.tmTheme"
|
||||
|
||||
[navy]
|
||||
color-scheme = "dark"
|
||||
main-color = "#bcbdd0"
|
||||
dash-color = "#2b79a2"
|
||||
code-theme = "tokyo-night.tmTheme"
|
||||
|
||||
[ayu]
|
||||
color-scheme = "dark"
|
||||
main-color = "#c5c5c5"
|
||||
dash-color = "#0096cf"
|
||||
code-theme = "tokyo-night.tmTheme"
|
||||
55
typ/packages/package-docs/theme.typ
Normal file
55
typ/packages/package-docs/theme.typ
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#import "@preview/shiroa:0.2.3": templates, book-sys
|
||||
#import templates: *
|
||||
|
||||
#let is-md-target = book-sys.target == "md"
|
||||
#let sys-is-html-target = book-sys.sys-is-html-target
|
||||
|
||||
// Theme (Colors)
|
||||
#let dark-theme = book-theme-from(toml("theme-style.toml"), xml: it => xml(it), target: "web-ayu")
|
||||
#let light-theme = book-theme-from(
|
||||
toml("theme-style.toml"),
|
||||
xml: it => xml(it),
|
||||
target: if sys-is-html-target {
|
||||
"web-light"
|
||||
} else {
|
||||
"pdf"
|
||||
},
|
||||
)
|
||||
#let default-theme = if sys-is-html-target {
|
||||
dark-theme
|
||||
} else {
|
||||
light-theme
|
||||
}
|
||||
|
||||
#let theme-box(render, tag: "div", theme-tag: none) = if is-md-target {
|
||||
show: html.elem.with(tag)
|
||||
show: html.elem.with("picture")
|
||||
html.elem(
|
||||
"m1source",
|
||||
attrs: (media: "(prefers-color-scheme: dark)"),
|
||||
render(dark-theme),
|
||||
)
|
||||
render(light-theme)
|
||||
} else if sys-is-html-target {
|
||||
if theme-tag == none {
|
||||
theme-tag = tag
|
||||
}
|
||||
html.elem(
|
||||
tag,
|
||||
attrs: (class: "code-image themed"),
|
||||
{
|
||||
html.elem(
|
||||
theme-tag,
|
||||
render(dark-theme),
|
||||
attrs: (class: "dark"),
|
||||
)
|
||||
html.elem(
|
||||
theme-tag,
|
||||
render(light-theme),
|
||||
attrs: (class: "light"),
|
||||
)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
render(default-theme)
|
||||
}
|
||||
1308
typ/packages/package-docs/tokyo-night.tmTheme
Normal file
1308
typ/packages/package-docs/tokyo-night.tmTheme
Normal file
File diff suppressed because it is too large
Load diff
4
typ/packages/package-docs/typst.toml
Normal file
4
typ/packages/package-docs/typst.toml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
[package]
|
||||
name = "package-docs"
|
||||
version = "0.1.0"
|
||||
entrypoint = "main.typ"
|
||||
Loading…
Add table
Add a link
Reference in a new issue