mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-08-04 10:18:16 +00:00
refactor: remove unused code (#1728)
This commit is contained in:
parent
6b7ca47f23
commit
d85bd7428a
26 changed files with 22 additions and 1598 deletions
|
@ -1,10 +1,8 @@
|
|||
use std::path::Path;
|
||||
use std::sync::{Arc, LazyLock};
|
||||
use std::sync::Arc;
|
||||
|
||||
use ecow::{eco_format, EcoString};
|
||||
use tinymist_world::{EntryReader, ShadowApi, TaskInputs};
|
||||
use typlite::scopes::Scopes;
|
||||
use typlite::value::Value;
|
||||
use typlite::TypliteFeat;
|
||||
use typst::diag::StrResult;
|
||||
use typst::foundations::Bytes;
|
||||
|
@ -13,9 +11,6 @@ use typst::World;
|
|||
use crate::analysis::SharedContext;
|
||||
|
||||
pub(crate) fn convert_docs(ctx: &SharedContext, content: &str) -> StrResult<EcoString> {
|
||||
static DOCS_LIB: LazyLock<Arc<Scopes<Value>>> =
|
||||
LazyLock::new(|| Arc::new(typlite::library::docstring_lib()));
|
||||
|
||||
let entry = ctx.world.entry_state();
|
||||
let entry = entry.select_in_workspace(Path::new("__tinymist_docs__.typ"));
|
||||
|
||||
|
@ -28,7 +23,6 @@ pub(crate) fn convert_docs(ctx: &SharedContext, content: &str) -> StrResult<EcoS
|
|||
w.take_db();
|
||||
|
||||
let conv = typlite::Typlite::new(Arc::new(w))
|
||||
.with_library(DOCS_LIB.clone())
|
||||
.with_feature(TypliteFeat {
|
||||
color_theme: Some(ctx.analysis.color_theme),
|
||||
annotate_elem: true,
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
source: crates/typlite/src/tests.rs
|
||||
expression: hash
|
||||
input_file: crates/typlite/src/fixtures/integration/base.typ
|
||||
---
|
||||
siphash128_13:f242a739ddf7cdce8041455cd09bf221
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
source: crates/typlite/src/tests.rs
|
||||
expression: hash
|
||||
input_file: crates/typlite/src/fixtures/integration/enum.typ
|
||||
---
|
||||
siphash128_13:120c2e9245d767d648fd52a8564c9efc
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
source: crates/typlite/src/tests.rs
|
||||
expression: hash
|
||||
input_file: crates/typlite/src/fixtures/integration/enum2.typ
|
||||
---
|
||||
siphash128_13:120c2e9245d767d648fd52a8564c9efc
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
source: crates/typlite/src/tests.rs
|
||||
expression: hash
|
||||
input_file: crates/typlite/src/fixtures/integration/figure_caption.typ
|
||||
---
|
||||
siphash128_13:17d544d88231b74b1119c35f627026b
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
source: crates/typlite/src/tests.rs
|
||||
expression: hash
|
||||
input_file: crates/typlite/src/fixtures/integration/figure_image.typ
|
||||
---
|
||||
siphash128_13:89ee713812f00bde9ac174f72c81760
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
source: crates/typlite/src/tests.rs
|
||||
expression: hash
|
||||
input_file: crates/typlite/src/fixtures/integration/figure_image_alt.typ
|
||||
---
|
||||
siphash128_13:89ee713812f00bde9ac174f72c81760
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
source: crates/typlite/src/tests.rs
|
||||
expression: hash
|
||||
input_file: crates/typlite/src/fixtures/integration/image.typ
|
||||
---
|
||||
siphash128_13:89ee713812f00bde9ac174f72c81760
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
source: crates/typlite/src/tests.rs
|
||||
expression: hash
|
||||
input_file: crates/typlite/src/fixtures/integration/image_alt.typ
|
||||
---
|
||||
siphash128_13:89ee713812f00bde9ac174f72c81760
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
source: crates/typlite/src/tests.rs
|
||||
expression: hash
|
||||
input_file: crates/typlite/src/fixtures/integration/link.typ
|
||||
---
|
||||
siphash128_13:35e614ded7c81c7fb6781d77872add56
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
source: crates/typlite/src/tests.rs
|
||||
expression: hash
|
||||
input_file: crates/typlite/src/fixtures/integration/link2.typ
|
||||
---
|
||||
siphash128_13:2374bfc8248e276ed1549f5d6a8b4a40
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
source: crates/typlite/src/tests.rs
|
||||
expression: hash
|
||||
input_file: crates/typlite/src/fixtures/integration/link3.typ
|
||||
---
|
||||
siphash128_13:5d5f436195b9b0b0f206881bc4d810f8
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
source: crates/typlite/src/tests.rs
|
||||
expression: hash
|
||||
input_file: crates/typlite/src/fixtures/integration/list.typ
|
||||
---
|
||||
siphash128_13:dd68d2d40ddf137ad77719e71c56a19e
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
source: crates/typlite/src/tests.rs
|
||||
expression: hash
|
||||
input_file: crates/typlite/src/fixtures/integration/math_block.typ
|
||||
---
|
||||
siphash128_13:ca4f0e6c5b2afee90d9736cb2d3bd6ba
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
source: crates/typlite/src/tests.rs
|
||||
expression: hash
|
||||
input_file: crates/typlite/src/fixtures/integration/math_block2.typ
|
||||
---
|
||||
siphash128_13:1c9f3489f7742ef572998ff2b4fd5abd
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
source: crates/typlite/src/tests.rs
|
||||
expression: hash
|
||||
input_file: crates/typlite/src/fixtures/integration/math_inline.typ
|
||||
---
|
||||
siphash128_13:2ac3d241b41c4ee23a122b73e43c8063
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
source: crates/typlite/src/tests.rs
|
||||
expression: hash
|
||||
input_file: crates/typlite/src/fixtures/integration/outline.typ
|
||||
---
|
||||
siphash128_13:549cf83e9b77d8ae061c95ceb4f93ef6
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
source: crates/typlite/src/tests.rs
|
||||
expression: hash
|
||||
input_file: crates/typlite/src/fixtures/integration/raw_inline.typ
|
||||
---
|
||||
siphash128_13:fe468826fde99ac8a0e77767d4045199
|
|
@ -1,6 +0,0 @@
|
|||
---
|
||||
source: crates/typlite/src/tests.rs
|
||||
expression: hash
|
||||
input_file: crates/typlite/src/fixtures/integration/table.typ
|
||||
---
|
||||
siphash128_13:ce1b6f668016a12edf304ab7f38aea42
|
|
@ -5,10 +5,7 @@ pub mod common;
|
|||
mod error;
|
||||
pub mod library;
|
||||
pub mod parser;
|
||||
pub mod scopes;
|
||||
pub mod tags;
|
||||
pub mod value;
|
||||
pub mod worker;
|
||||
pub mod writer;
|
||||
|
||||
use std::path::PathBuf;
|
||||
|
@ -136,8 +133,6 @@ pub struct TypliteFeat {
|
|||
pub struct Typlite {
|
||||
/// The universe to use for the conversion.
|
||||
world: Arc<LspWorld>,
|
||||
/// library to use for the conversion.
|
||||
library: Option<Arc<scopes::Scopes<value::Value>>>,
|
||||
/// Features for the conversion.
|
||||
feat: TypliteFeat,
|
||||
/// The format to use for the conversion.
|
||||
|
@ -152,18 +147,11 @@ impl Typlite {
|
|||
pub fn new(world: Arc<LspWorld>) -> Self {
|
||||
Self {
|
||||
world,
|
||||
library: None,
|
||||
feat: Default::default(),
|
||||
format: Format::Md,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set library to use for the conversion.
|
||||
pub fn with_library(mut self, library: Arc<scopes::Scopes<value::Value>>) -> Self {
|
||||
self.library = Some(library);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set conversion feature
|
||||
pub fn with_feature(mut self, feat: TypliteFeat) -> Self {
|
||||
self.feat = feat;
|
||||
|
|
|
@ -1,258 +1,18 @@
|
|||
//! # Typlite Library
|
||||
|
||||
use crate::{scopes::Scopes, tinymist_std::typst::diag::EcoString, worker::TypliteWorker};
|
||||
|
||||
use super::*;
|
||||
use ecow::eco_format;
|
||||
use typst_syntax::{ast, SyntaxKind, SyntaxNode};
|
||||
use value::*;
|
||||
|
||||
mod docstring;
|
||||
pub use docstring::docstring_lib;
|
||||
|
||||
pub fn library() -> Scopes<Value> {
|
||||
let mut scopes = Scopes::new();
|
||||
scopes.define("link", link as RawFunc);
|
||||
scopes.define("kbd", kbd as RawFunc);
|
||||
scopes.define("md-alter", md_alter as RawFunc);
|
||||
scopes.define("image", image as RawFunc);
|
||||
scopes.define("figure", figure as RawFunc);
|
||||
scopes.define("raw", raw as RawFunc);
|
||||
scopes.define("strike", strike as RawFunc);
|
||||
scopes.define("pad", pad as RawFunc);
|
||||
scopes.define("note-box", note as RawFunc);
|
||||
scopes.define("tip-box", tip as RawFunc);
|
||||
scopes.define("important-box", important_box as RawFunc);
|
||||
scopes.define("warning-box", warning_box as RawFunc);
|
||||
scopes.define("caution-box", caution_box as RawFunc);
|
||||
scopes.define("table", table as RawFunc);
|
||||
scopes.define("grid", grid as RawFunc);
|
||||
scopes
|
||||
}
|
||||
|
||||
/// Evaluates a link.
|
||||
pub fn link(mut args: Args) -> Result<Value> {
|
||||
let dest = get_pos_named!(args, dest: EcoString);
|
||||
let body = get_pos_named!(args, body: Content);
|
||||
|
||||
Ok(Value::Content(eco_format!("[{body}]({dest})")))
|
||||
}
|
||||
|
||||
/// Evaluates an image.
|
||||
pub fn image(mut args: Args) -> Result<Value> {
|
||||
let path = get_pos_named!(args, path: EcoString);
|
||||
let alt = get_named!(args, alt: EcoString := "");
|
||||
|
||||
Ok(Value::Image { path, alt })
|
||||
}
|
||||
|
||||
/// Evaluates a figure.
|
||||
pub fn figure(mut args: Args) -> Result<Value> {
|
||||
let body = get_pos_named!(args, path: Value);
|
||||
let caption = get_named!(args, caption: Option<Value>).map(TypliteWorker::value);
|
||||
|
||||
match (body, caption) {
|
||||
(Value::Image { path, alt }, None) => Ok(Value::Content(eco_format!(""))),
|
||||
(Value::Image { path, alt }, Some(caption)) if args.vm.feat.gfm => Ok(Value::Content(
|
||||
eco_format!(""),
|
||||
)),
|
||||
(Value::Image { path, alt }, Some(caption)) => {
|
||||
Ok(Value::Content(eco_format!("")))
|
||||
}
|
||||
_ => Err("figure only accepts image as body".into()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluates a raw.
|
||||
pub fn raw(mut args: Args) -> Result<Value> {
|
||||
let content = get_pos_named!(args, content: EcoString);
|
||||
|
||||
let max_consecutive_backticks = content
|
||||
.chars()
|
||||
.fold((0, 0), |(max, count), c| {
|
||||
if c == '`' {
|
||||
(max, count + 1)
|
||||
} else {
|
||||
(max.max(count), 0)
|
||||
}
|
||||
})
|
||||
.0;
|
||||
|
||||
Ok(Value::Content(eco_format!(
|
||||
"{backticks}\n{content}\n{backticks}",
|
||||
backticks = "`".repeat((max_consecutive_backticks + 1).max(3)),
|
||||
)))
|
||||
}
|
||||
|
||||
/// Evaluates a strike.
|
||||
pub fn strike(mut args: Args) -> Result<Value> {
|
||||
let body = get_pos_named!(args, body: Content);
|
||||
|
||||
Ok(Value::Content(eco_format!("~~{body}~~")))
|
||||
}
|
||||
|
||||
/// Evaluates a padded content.
|
||||
pub fn pad(mut args: Args) -> Result<Value> {
|
||||
Ok(get_pos_named!(args, path: Value))
|
||||
}
|
||||
|
||||
/// Evaluates a `kbd` element.
|
||||
pub fn kbd(mut args: Args) -> Result<Value> {
|
||||
let key = get_pos_named!(args, key: EcoString);
|
||||
|
||||
Ok(Value::Content(eco_format!("<kbd>{key}</kbd>")))
|
||||
}
|
||||
|
||||
/// Evaluates a markdown alteration.
|
||||
pub fn md_alter(mut args: Args) -> Result<Value> {
|
||||
let _: () = get_pos_named!(args, left: ());
|
||||
let right = get_pos_named!(args, right: LazyContent);
|
||||
|
||||
Ok(Value::Content(right.0))
|
||||
}
|
||||
|
||||
/// Evaluates a note.
|
||||
pub fn note(mut args: Args) -> Result<Value> {
|
||||
let body = get_pos_named!(args, body: Content);
|
||||
|
||||
Ok(note_box("NOTE", body))
|
||||
}
|
||||
|
||||
/// Evaluates a tip note box.
|
||||
pub fn tip(mut args: Args) -> Result<Value> {
|
||||
let body = get_pos_named!(args, body: Content);
|
||||
|
||||
Ok(note_box("TIP", body))
|
||||
}
|
||||
|
||||
/// Creates a important note box.
|
||||
pub fn important_box(mut args: Args) -> Result<Value> {
|
||||
let body = get_pos_named!(args, body: Content);
|
||||
|
||||
Ok(note_box("IMPORTANT", body))
|
||||
}
|
||||
|
||||
/// Creates a warning note box.
|
||||
pub fn warning_box(mut args: Args) -> Result<Value> {
|
||||
let body = get_pos_named!(args, body: Content);
|
||||
|
||||
Ok(note_box("WARNING", body))
|
||||
}
|
||||
|
||||
/// Creates a caution note box.
|
||||
pub fn caution_box(mut args: Args) -> Result<Value> {
|
||||
let body = get_pos_named!(args, body: Content);
|
||||
|
||||
Ok(note_box("CAUTION", body))
|
||||
}
|
||||
|
||||
fn note_box(title: &str, body: Content) -> Value {
|
||||
let mut res = EcoString::new();
|
||||
res.push_str("> [!");
|
||||
res.push_str(title);
|
||||
res.push_str("]\n");
|
||||
let body = body.0;
|
||||
for line in body.lines() {
|
||||
res.push_str("> ");
|
||||
res.push_str(line);
|
||||
res.push('\n');
|
||||
}
|
||||
|
||||
Value::Content(res)
|
||||
}
|
||||
|
||||
/// Evaluates a table.
|
||||
pub fn table(args: Args) -> Result<Value> {
|
||||
table_eval(args, &EcoString::from("table"))
|
||||
}
|
||||
|
||||
/// Evaluates a grid.
|
||||
pub fn grid(args: Args) -> Result<Value> {
|
||||
table_eval(args, &EcoString::from("grid"))
|
||||
}
|
||||
|
||||
fn table_eval(mut args: Args, kind: &EcoString) -> Result<Value> {
|
||||
let columns = if let Some(columns) = args.get_named_("columns") {
|
||||
match columns.kind() {
|
||||
SyntaxKind::Array => {
|
||||
let array: ast::Array = args.get_named_("columns").unwrap().cast().unwrap();
|
||||
array.items().count()
|
||||
}
|
||||
SyntaxKind::Int => {
|
||||
let int_val: ast::Int = args.get_named_("columns").unwrap().cast().unwrap();
|
||||
int_val.get().try_into().unwrap()
|
||||
}
|
||||
other => return Err(format!("invalid columns argument of type {:?}", other).into()),
|
||||
}
|
||||
} else {
|
||||
1
|
||||
};
|
||||
|
||||
let header_field = SyntaxNode::inner(
|
||||
SyntaxKind::FieldAccess,
|
||||
vec![
|
||||
SyntaxNode::leaf(SyntaxKind::Ident, kind),
|
||||
SyntaxNode::leaf(SyntaxKind::Dot, "."),
|
||||
SyntaxNode::leaf(SyntaxKind::Ident, "header"),
|
||||
],
|
||||
);
|
||||
let footer_field = SyntaxNode::inner(
|
||||
SyntaxKind::FieldAccess,
|
||||
vec![
|
||||
SyntaxNode::leaf(SyntaxKind::Ident, kind),
|
||||
SyntaxNode::leaf(SyntaxKind::Dot, "."),
|
||||
SyntaxNode::leaf(SyntaxKind::Ident, "footer"),
|
||||
],
|
||||
);
|
||||
|
||||
let mut header: Vec<EcoString> = Vec::new();
|
||||
let mut cells: Vec<EcoString> = Vec::new();
|
||||
|
||||
while let Some(pos_arg) = args.pos.pop() {
|
||||
if pos_arg.kind() != SyntaxKind::FuncCall {
|
||||
let evaluated = args.vm.eval(pos_arg)?;
|
||||
cells.push(TypliteWorker::value(evaluated));
|
||||
} else {
|
||||
let func_call: ast::FuncCall = pos_arg.cast().unwrap();
|
||||
let first_child = pos_arg.children().next().unwrap();
|
||||
|
||||
if header_field.spanless_eq(first_child) {
|
||||
let mut header_args = Args::new(args.vm, func_call.args());
|
||||
while let Some(arg) = header_args.pos.pop() {
|
||||
let evaluated = header_args.vm.eval(arg)?;
|
||||
header.push(TypliteWorker::value(evaluated));
|
||||
}
|
||||
} else {
|
||||
let evaluated = args.vm.eval(pos_arg)?;
|
||||
cells.push(TypliteWorker::value(evaluated));
|
||||
}
|
||||
if footer_field.spanless_eq(first_child) {
|
||||
let mut footer_args = Args::new(args.vm, func_call.args());
|
||||
while let Some(arg) = footer_args.pos.pop() {
|
||||
let evaluated = footer_args.vm.eval(arg)?;
|
||||
cells.push(TypliteWorker::value(evaluated));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut res = EcoString::from("<table>\n");
|
||||
if !header.is_empty() {
|
||||
res.push_str(" <thead>\n <tr>\n");
|
||||
for cell in &header {
|
||||
res.push_str(&eco_format!(" <th>{}</th>\n", cell));
|
||||
}
|
||||
res.push_str(" </tr>\n </thead>\n");
|
||||
}
|
||||
res.push_str(" <tbody>\n");
|
||||
for row in cells.chunks(columns) {
|
||||
res.push_str(" <tr>\n");
|
||||
for cell in row {
|
||||
res.push_str(&eco_format!(" <td>{}</td>\n", cell));
|
||||
}
|
||||
res.push_str(" </tr>\n");
|
||||
}
|
||||
res.push_str(" </tbody>\n</table>");
|
||||
|
||||
Ok(Value::Content(res))
|
||||
}
|
||||
// scopes.define("link", link as RawFunc);
|
||||
// scopes.define("kbd", kbd as RawFunc);
|
||||
// scopes.define("md-alter", md_alter as RawFunc);
|
||||
// scopes.define("image", image as RawFunc);
|
||||
// scopes.define("figure", figure as RawFunc);
|
||||
// scopes.define("raw", raw as RawFunc);
|
||||
// scopes.define("strike", strike as RawFunc);
|
||||
// scopes.define("pad", pad as RawFunc);
|
||||
// scopes.define("note-box", note as RawFunc);
|
||||
// scopes.define("tip-box", tip as RawFunc);
|
||||
// scopes.define("important-box", important_box as RawFunc);
|
||||
// scopes.define("warning-box", warning_box as RawFunc);
|
||||
// scopes.define("caution-box", caution_box as RawFunc);
|
||||
// scopes.define("table", table as RawFunc);
|
||||
// scopes.define("grid", grid as RawFunc);
|
||||
// pub fn cross_link(mut args: Args) -> typlite::Result<Value>
|
||||
|
|
|
@ -1,59 +0,0 @@
|
|||
use super::*;
|
||||
|
||||
pub fn docstring_lib() -> Scopes<Value> {
|
||||
let mut scopes = library();
|
||||
|
||||
scopes.define("example", example as RawFunc);
|
||||
|
||||
scopes
|
||||
}
|
||||
|
||||
/// Evaluate a `example`.
|
||||
pub fn example(mut args: Args) -> Result<Value> {
|
||||
let body = get_pos_named!(args, body: &SyntaxNode);
|
||||
let body = body
|
||||
.cast::<ast::Raw>()
|
||||
.ok_or_else(|| format!("expected raw, found {:?}", body.kind()))?;
|
||||
|
||||
let lang = body.lang().map(|l| l.get().as_str()).unwrap_or("typ");
|
||||
|
||||
// Handle example docs specially.
|
||||
// <https://github.com/typst/typst/blob/070e3144b33e9a9e9839c138df2b0a13dde7abc7/docs/src/html.rs#L355>
|
||||
let mut display = String::new();
|
||||
let mut compile = String::new();
|
||||
for line in body.lines() {
|
||||
let line = line.get();
|
||||
if let Some(suffix) = line.strip_prefix(">>>") {
|
||||
compile.push_str(suffix);
|
||||
compile.push('\n');
|
||||
} else if let Some(suffix) = line.strip_prefix("<<< ") {
|
||||
display.push_str(suffix);
|
||||
display.push('\n');
|
||||
} else {
|
||||
display.push_str(line);
|
||||
display.push('\n');
|
||||
compile.push_str(line);
|
||||
compile.push('\n');
|
||||
}
|
||||
}
|
||||
|
||||
let mut s = EcoString::new();
|
||||
|
||||
s.push_str("```");
|
||||
s.push_str(lang);
|
||||
s.push('\n');
|
||||
s.push_str(&display);
|
||||
s.push('\n');
|
||||
s.push_str("```");
|
||||
s.push('\n');
|
||||
|
||||
if !args.vm.feat.remove_html {
|
||||
let is_code = lang == "typc";
|
||||
let rendered =
|
||||
args.vm
|
||||
.render_code("", &compile, !is_code, "left", r#"width="500px""#, false)?;
|
||||
s.push_str(&TypliteWorker::value(rendered));
|
||||
}
|
||||
|
||||
Ok(Value::Content(s))
|
||||
}
|
|
@ -7,9 +7,8 @@ use std::{
|
|||
};
|
||||
|
||||
use clap::Parser;
|
||||
use ecow::{eco_format, EcoString};
|
||||
use tinymist_project::WorldProvider;
|
||||
use typlite::{common::Format, value::*, TypliteFeat};
|
||||
use typlite::{common::Format, TypliteFeat};
|
||||
use typlite::{CompileOnceArgs, Typlite};
|
||||
|
||||
/// Common arguments of compile, watch, and query.
|
||||
|
@ -62,12 +61,10 @@ fn main() -> typlite::Result<()> {
|
|||
let universe = args.compile.resolve().map_err(|err| format!("{err:?}"))?;
|
||||
let world = universe.snapshot();
|
||||
|
||||
let converter = Typlite::new(Arc::new(world))
|
||||
.with_library(lib())
|
||||
.with_feature(TypliteFeat {
|
||||
assets_path: assets_path.clone(),
|
||||
..Default::default()
|
||||
});
|
||||
let converter = Typlite::new(Arc::new(world)).with_feature(TypliteFeat {
|
||||
assets_path: assets_path.clone(),
|
||||
..Default::default()
|
||||
});
|
||||
let doc = match converter.convert_doc() {
|
||||
Ok(doc) => doc,
|
||||
Err(err) => return Err(format!("failed to convert document: {err}").into()),
|
||||
|
@ -168,31 +165,3 @@ fn main() -> typlite::Result<()> {
|
|||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn lib() -> Arc<typlite::scopes::Scopes<Value>> {
|
||||
let mut scopes = typlite::library::docstring_lib();
|
||||
|
||||
// todo: how to import this function correctly?
|
||||
scopes.define("cross-link", cross_link as RawFunc);
|
||||
|
||||
Arc::new(scopes)
|
||||
}
|
||||
|
||||
/// Evaluate a `cross-link`.
|
||||
pub fn cross_link(mut args: Args) -> typlite::Result<Value> {
|
||||
let dest = get_pos_named!(args, dest: EcoString);
|
||||
let body = get_pos_named!(args, body: Content);
|
||||
|
||||
let dest = std::path::Path::new(dest.as_str()).with_extension("html");
|
||||
let mut dest = dest.as_path();
|
||||
|
||||
// strip leading `/` from the path
|
||||
if let Ok(s) = dest.strip_prefix("/") {
|
||||
dest = s;
|
||||
}
|
||||
|
||||
Ok(Value::Content(eco_format!(
|
||||
"[{body}](https://myriad-dreamin.github.io/tinymist/{dest})",
|
||||
dest = dest.to_string_lossy()
|
||||
)))
|
||||
}
|
||||
|
|
|
@ -1,105 +0,0 @@
|
|||
//! Variable scopes.
|
||||
|
||||
use super::*;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// A single scope.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Scope<T> {
|
||||
map: HashMap<String, T>,
|
||||
}
|
||||
|
||||
impl<T> Default for Scope<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Scope<T> {
|
||||
/// Create a new, empty scope.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
map: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Define a variable in this scope.
|
||||
pub fn define(&mut self, name: String, val: T) {
|
||||
self.map.insert(name, val);
|
||||
}
|
||||
|
||||
/// Try to access a variable immutably.
|
||||
pub fn get(&self, var: &str) -> Option<&T> {
|
||||
self.map.get(var)
|
||||
}
|
||||
|
||||
/// Try to access a variable mutably.
|
||||
pub fn get_mut(&mut self, var: &str) -> Option<&mut T> {
|
||||
self.map.get_mut(var)
|
||||
}
|
||||
}
|
||||
|
||||
/// A stack of scopes.
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct Scopes<T> {
|
||||
/// The active scope.
|
||||
pub top: Scope<T>,
|
||||
/// The stack of lower scopes.
|
||||
pub scopes: Vec<Scope<T>>,
|
||||
}
|
||||
|
||||
impl<T> Scopes<T> {
|
||||
/// Create a new, empty hierarchy of scopes.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
top: Scope::new(),
|
||||
scopes: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
/// Enter a new scope.
|
||||
pub fn enter(&mut self) {
|
||||
self.scopes.push(std::mem::take(&mut self.top));
|
||||
}
|
||||
|
||||
/// Exit the topmost scope.
|
||||
///
|
||||
/// This panics if no scope was entered.
|
||||
pub fn exit(&mut self) {
|
||||
self.top = self.scopes.pop().expect("no pushed scope");
|
||||
}
|
||||
|
||||
/// Try to access a variable immutably.
|
||||
pub fn get(&self, var: &str) -> Result<&T> {
|
||||
std::iter::once(&self.top)
|
||||
.chain(self.scopes.iter().rev())
|
||||
.find_map(|scope| scope.get(var))
|
||||
.ok_or_else(|| unknown_variable(var))
|
||||
}
|
||||
|
||||
/// Try to access a variable immutably in math.
|
||||
pub fn get_in_math(&self, var: &str) -> Result<&T> {
|
||||
std::iter::once(&self.top)
|
||||
.chain(self.scopes.iter().rev())
|
||||
.find_map(|scope| scope.get(var))
|
||||
.ok_or_else(|| unknown_variable(var))
|
||||
}
|
||||
|
||||
/// Try to access a variable mutably.
|
||||
pub fn get_mut(&mut self, var: &str) -> Result<&mut T> {
|
||||
std::iter::once(&mut self.top)
|
||||
.chain(&mut self.scopes.iter_mut().rev())
|
||||
.find_map(|scope| scope.get_mut(var))
|
||||
.ok_or_else(|| unknown_variable(var))
|
||||
}
|
||||
|
||||
/// Define a variable in the current scope.
|
||||
pub fn define(&mut self, arg: &str, v: impl Into<T>) {
|
||||
self.top.define(arg.to_string(), v.into());
|
||||
}
|
||||
}
|
||||
|
||||
/// The error message when a variable is not found.
|
||||
fn unknown_variable(var: &str) -> Error {
|
||||
format!("unknown variable: {var}").into()
|
||||
}
|
|
@ -1,191 +0,0 @@
|
|||
//! # Typlite Values
|
||||
|
||||
use crate::tinymist_std::typst::diag::EcoString;
|
||||
use crate::worker::TypliteWorker;
|
||||
use core::fmt;
|
||||
use typst_syntax::{
|
||||
ast::{self, AstNode},
|
||||
SyntaxNode,
|
||||
};
|
||||
|
||||
use crate::*;
|
||||
|
||||
pub type RawFunc = fn(Args) -> Result<Value>;
|
||||
|
||||
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
|
||||
pub enum Value {
|
||||
None,
|
||||
RawFunc(RawFunc),
|
||||
Str(EcoString),
|
||||
Content(EcoString),
|
||||
Image { path: EcoString, alt: EcoString },
|
||||
}
|
||||
|
||||
impl From<RawFunc> for Value {
|
||||
fn from(func: RawFunc) -> Self {
|
||||
Self::RawFunc(func)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Content(pub EcoString);
|
||||
|
||||
impl fmt::Display for Content {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct LazyContent(pub EcoString);
|
||||
|
||||
impl fmt::Display for LazyContent {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Args<'a> {
|
||||
pub vm: &'a mut TypliteWorker,
|
||||
pub args: ast::Args<'a>,
|
||||
pub pos: Vec<&'a SyntaxNode>,
|
||||
}
|
||||
|
||||
impl<'a> Args<'a> {
|
||||
pub fn new(worker: &'a mut TypliteWorker, args: ast::Args<'a>) -> Self {
|
||||
let pos = args
|
||||
.items()
|
||||
.filter_map(|item| match item {
|
||||
ast::Arg::Pos(pos) => Some(pos.to_untyped()),
|
||||
_ => None,
|
||||
})
|
||||
.rev()
|
||||
.collect();
|
||||
Self {
|
||||
vm: worker,
|
||||
args,
|
||||
pos,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_named_(&mut self, key: &str) -> Option<&'a SyntaxNode> {
|
||||
// find named
|
||||
for item in self.args.items() {
|
||||
if let ast::Arg::Named(named) = item {
|
||||
if named.name().get() == key {
|
||||
return Some(named.expr().to_untyped());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn get(&mut self, key: &str) -> Result<&'a SyntaxNode> {
|
||||
if let Some(named) = self.get_named_(key) {
|
||||
return Ok(named);
|
||||
}
|
||||
|
||||
// find positional
|
||||
Ok(self
|
||||
.pos
|
||||
.pop()
|
||||
.ok_or_else(|| format!("missing positional arguments: {key}"))?)
|
||||
}
|
||||
|
||||
pub fn parse<T: Eval<'a>>(&mut self, node: &'a SyntaxNode) -> Result<T> {
|
||||
T::eval(node, self.vm)
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! get_pos_named {
|
||||
(
|
||||
$args:expr,
|
||||
$key:ident: $ty:ty
|
||||
) => {{
|
||||
let raw = $args.get(stringify!($key))?;
|
||||
$args.parse::<$ty>(raw)?
|
||||
}};
|
||||
}
|
||||
pub use get_pos_named;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! get_named {
|
||||
(
|
||||
$args:expr,
|
||||
$key:ident: Option<$ty:ty>
|
||||
) => {{
|
||||
if let Some(raw) = $args.get_named_(stringify!($key)) {
|
||||
Some($args.parse::<$ty>(raw)?)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}};
|
||||
(
|
||||
$args:expr,
|
||||
$key:ident: $ty:ty
|
||||
) => {{
|
||||
let raw = $args.get_named(stringify!($key))?;
|
||||
$args.parse::<$ty>(raw)?
|
||||
}};
|
||||
(
|
||||
$args:expr,
|
||||
$key:ident: $ty:ty := $default:expr
|
||||
) => {{
|
||||
if let Some(raw) = $args.get_named_(stringify!($key)) {
|
||||
$args.parse::<$ty>(raw)?
|
||||
} else {
|
||||
$default.into()
|
||||
}
|
||||
}};
|
||||
}
|
||||
pub use get_named;
|
||||
|
||||
/// Evaluate an expression.
|
||||
pub trait Eval<'a>: Sized {
|
||||
/// Evaluate the expression to the output value.
|
||||
fn eval(node: &'a SyntaxNode, vm: &mut TypliteWorker) -> Result<Self>;
|
||||
}
|
||||
|
||||
impl<'a> Eval<'a> for () {
|
||||
fn eval(_node: &'a SyntaxNode, _vm: &mut TypliteWorker) -> Result<Self> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Eval<'a> for &'a SyntaxNode {
|
||||
fn eval(node: &'a SyntaxNode, _vm: &mut TypliteWorker) -> Result<Self> {
|
||||
Ok(node)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Eval<'a> for EcoString {
|
||||
fn eval(node: &'a SyntaxNode, _vm: &mut TypliteWorker) -> Result<Self> {
|
||||
let node: ast::Str = node
|
||||
.cast()
|
||||
.ok_or_else(|| format!("expected string, found {:?}", node.kind()))?;
|
||||
Ok(node.get())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Eval<'a> for Value {
|
||||
fn eval(node: &'a SyntaxNode, vm: &mut TypliteWorker) -> Result<Self> {
|
||||
vm.eval(node)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Eval<'a> for Content {
|
||||
fn eval(node: &'a SyntaxNode, vm: &mut TypliteWorker) -> Result<Self> {
|
||||
Ok(Self(vm.convert(node)?))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Eval<'a> for LazyContent {
|
||||
fn eval(node: &'a SyntaxNode, vm: &mut TypliteWorker) -> Result<Self> {
|
||||
let node = match node.cast() {
|
||||
Some(s @ ast::Closure { .. }) => s.body().to_untyped(),
|
||||
None => node,
|
||||
};
|
||||
|
||||
Ok(Self(vm.convert(node)?))
|
||||
}
|
||||
}
|
|
@ -1,824 +0,0 @@
|
|||
use std::fmt::{self, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{Arc, LazyLock};
|
||||
|
||||
use base64::Engine;
|
||||
use ecow::{eco_format, EcoString};
|
||||
use tinymist_analysis;
|
||||
use tinymist_project::base::ShadowApi;
|
||||
use tinymist_project::{EntryReader, LspWorld};
|
||||
use typst::foundations::{Bytes, Dict, IntoValue};
|
||||
use typst::layout::Abs;
|
||||
use typst::syntax::{FileId, Source, SyntaxKind, SyntaxNode};
|
||||
use typst::utils::LazyHash;
|
||||
use typst::World;
|
||||
use typst::WorldExt;
|
||||
use typst_syntax::ast::{self, AstNode};
|
||||
use typst_syntax::ast::{Emph, Equation, Heading, Raw, Strong};
|
||||
|
||||
use crate::scopes::Scopes;
|
||||
use crate::tinymist_std::path::unix_slash;
|
||||
use crate::value::{Args, Value};
|
||||
use crate::worker::SyntaxKind::Text;
|
||||
use crate::Result;
|
||||
use crate::TypliteFeat;
|
||||
use crate::WorkspaceResolver;
|
||||
|
||||
/// Typlite worker for converting syntax nodes to markdown
|
||||
#[derive(Clone)]
|
||||
pub struct TypliteWorker {
|
||||
pub current: FileId,
|
||||
pub scopes: Arc<Scopes<Value>>,
|
||||
pub world: Arc<LspWorld>,
|
||||
pub list_depth: usize,
|
||||
pub prepend_code: EcoString,
|
||||
pub assets_numbering: usize,
|
||||
/// Features for the conversion.
|
||||
pub feat: TypliteFeat,
|
||||
}
|
||||
|
||||
impl TypliteWorker {
|
||||
/// Convert the content to a markdown string.
|
||||
pub fn convert(&mut self, node: &SyntaxNode) -> Result<EcoString> {
|
||||
Ok(Self::value(self.eval(node)?))
|
||||
}
|
||||
|
||||
/// Eval the content
|
||||
pub fn eval(&mut self, node: &SyntaxNode) -> Result<Value> {
|
||||
use SyntaxKind::*;
|
||||
let res = match node.kind() {
|
||||
RawLang | RawDelim | RawTrimmed => Err("converting clause")?,
|
||||
|
||||
Math | MathIdent | MathAlignPoint | MathDelimited | MathAttach | MathPrimes
|
||||
| MathFrac | MathRoot | MathShorthand | MathText => Err("converting math node")?,
|
||||
|
||||
// Error nodes
|
||||
Error => Err(node.clone().into_text().to_string())?,
|
||||
None | End => Ok(Value::None),
|
||||
|
||||
// Non-leaf nodes
|
||||
Markup => self.reduce(node),
|
||||
Code => self.reduce(node),
|
||||
Equation => self.equation(node),
|
||||
CodeBlock => {
|
||||
let code_block: ast::CodeBlock = node.cast().unwrap();
|
||||
self.eval(code_block.body().to_untyped())
|
||||
}
|
||||
ContentBlock => {
|
||||
let content_block: ast::ContentBlock = node.cast().unwrap();
|
||||
self.eval(content_block.body().to_untyped())
|
||||
}
|
||||
Parenthesized => {
|
||||
let parenthesized: ast::Parenthesized = node.cast().unwrap();
|
||||
self.eval(parenthesized.expr().to_untyped())
|
||||
}
|
||||
|
||||
// Text nodes
|
||||
Text | Space | Parbreak => Self::str(node),
|
||||
Linebreak => Self::char('\n'),
|
||||
|
||||
// Semantic nodes
|
||||
Escape => Self::escape(node),
|
||||
Shorthand => Self::shorthand(node),
|
||||
SmartQuote => Self::str(node),
|
||||
Strong => self.strong(node),
|
||||
Emph => self.emph(node),
|
||||
Raw => Self::raw(node),
|
||||
Link => self.link(node),
|
||||
Label => Self::label(node),
|
||||
Ref => Self::label_ref(node),
|
||||
RefMarker => Self::ref_marker(node),
|
||||
Heading => self.heading(node),
|
||||
HeadingMarker => Self::str(node),
|
||||
ListItem => self.list_item(node),
|
||||
ListMarker => Self::str(node),
|
||||
EnumItem => self.enum_item(node),
|
||||
EnumMarker => Self::str(node),
|
||||
TermItem => self.term_item(node),
|
||||
TermMarker => Self::str(node),
|
||||
|
||||
// Punctuation
|
||||
// Hash => Self::char('#'),
|
||||
Hash => Ok(Value::None),
|
||||
LeftBrace => Self::char('{'),
|
||||
RightBrace => Self::char('}'),
|
||||
LeftBracket => Self::char('['),
|
||||
RightBracket => Self::char(']'),
|
||||
LeftParen => Self::char('('),
|
||||
RightParen => Self::char(')'),
|
||||
Comma => Self::char(','),
|
||||
Semicolon => Ok(Value::None),
|
||||
Colon => Self::char(':'),
|
||||
Star => Self::char('*'),
|
||||
Underscore => Self::char('_'),
|
||||
Dollar => Self::char('$'),
|
||||
Plus => Self::char('+'),
|
||||
Minus => Self::char('-'),
|
||||
Slash => Self::char('/'),
|
||||
Hat => Self::char('^'),
|
||||
Prime => Self::char('\''),
|
||||
Dot => Self::char('.'),
|
||||
Eq => Self::char('='),
|
||||
Lt => Self::char('<'),
|
||||
Gt => Self::char('>'),
|
||||
|
||||
// Compound punctuation
|
||||
EqEq => Self::str(node),
|
||||
ExclEq => Self::str(node),
|
||||
LtEq => Self::str(node),
|
||||
GtEq => Self::str(node),
|
||||
PlusEq => Self::str(node),
|
||||
HyphEq => Self::str(node),
|
||||
StarEq => Self::str(node),
|
||||
SlashEq => Self::str(node),
|
||||
Dots => Self::str(node),
|
||||
Arrow => Self::str(node),
|
||||
Root => Self::str(node),
|
||||
|
||||
// Keywords
|
||||
Auto => Self::str(node),
|
||||
Not => Self::str(node),
|
||||
And => Self::str(node),
|
||||
Or => Self::str(node),
|
||||
Let => Self::str(node),
|
||||
Set => Self::str(node),
|
||||
Show => Self::str(node),
|
||||
Context => Self::str(node),
|
||||
If => Self::str(node),
|
||||
Else => Self::str(node),
|
||||
For => Self::str(node),
|
||||
In => Self::str(node),
|
||||
While => Self::str(node),
|
||||
Break => Self::str(node),
|
||||
Continue => Self::str(node),
|
||||
Return => Self::str(node),
|
||||
Import => Self::str(node),
|
||||
Include => Self::str(node),
|
||||
As => Self::str(node),
|
||||
|
||||
LetBinding => self.let_binding(node),
|
||||
FieldAccess => self.field_access(node),
|
||||
FuncCall => self.func_call(node),
|
||||
Contextual => self.contextual(node),
|
||||
|
||||
// Clause nodes
|
||||
Named => Ok(Value::None),
|
||||
Keyed => Ok(Value::None),
|
||||
Unary => Ok(Value::None),
|
||||
Binary => Ok(Value::None),
|
||||
Spread => Ok(Value::None),
|
||||
ImportItems => Ok(Value::None),
|
||||
ImportItemPath => Ok(Value::None),
|
||||
RenamedImportItem => Ok(Value::None),
|
||||
Closure => Ok(Value::None),
|
||||
Args => Ok(Value::None),
|
||||
Params => Ok(Value::None),
|
||||
|
||||
// Ignored code expressions
|
||||
Ident => Ok(Value::None),
|
||||
Bool => Ok(Value::None),
|
||||
Int => Ok(Value::None),
|
||||
Float => Ok(Value::None),
|
||||
Numeric => Ok(Value::None),
|
||||
Str => Ok(Value::Str({
|
||||
let s: ast::Str = node.cast().unwrap();
|
||||
s.get()
|
||||
})),
|
||||
Array => Ok(Value::None),
|
||||
Dict => Ok(Value::None),
|
||||
|
||||
// Ignored code expressions
|
||||
SetRule => Ok(Value::None),
|
||||
ShowRule => Ok(Value::None),
|
||||
Destructuring => Ok(Value::None),
|
||||
DestructAssignment => Ok(Value::None),
|
||||
|
||||
Conditional => Ok(Value::None),
|
||||
WhileLoop => Ok(Value::None),
|
||||
ForLoop => Ok(Value::None),
|
||||
LoopBreak => Ok(Value::None),
|
||||
LoopContinue => Ok(Value::None),
|
||||
FuncReturn => Ok(Value::None),
|
||||
|
||||
ModuleImport => Ok(Value::None),
|
||||
ModuleInclude => self.include(node),
|
||||
|
||||
// Ignored comments
|
||||
LineComment => Ok(Value::None),
|
||||
BlockComment => Ok(Value::None),
|
||||
Shebang => Ok(Value::None),
|
||||
};
|
||||
if res.clone()? == Value::None
|
||||
&& !matches!(
|
||||
node.kind(),
|
||||
Ident | Bool | Int | Float | Numeric | Str | Array | Dict
|
||||
)
|
||||
{
|
||||
self.prepend_code += node.clone().into_text();
|
||||
if node.kind() != Hash {
|
||||
self.prepend_code += "\n"
|
||||
};
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn reduce(&mut self, node: &SyntaxNode) -> Result<Value> {
|
||||
let mut s = EcoString::new();
|
||||
|
||||
for child in node.children() {
|
||||
s.push_str(&Self::value(self.eval(child)?));
|
||||
}
|
||||
|
||||
Ok(Value::Content(s))
|
||||
}
|
||||
|
||||
pub fn to_raw_block(&mut self, node: &SyntaxNode, inline: bool) -> Result<Value> {
|
||||
let content = node.clone().into_text();
|
||||
|
||||
let s = if inline {
|
||||
let mut s = EcoString::with_capacity(content.len() + 2);
|
||||
s.push_str("`");
|
||||
s.push_str(&content);
|
||||
s.push_str("`");
|
||||
s
|
||||
} else {
|
||||
let mut s = EcoString::with_capacity(content.len() + 15);
|
||||
s.push_str("```");
|
||||
let lang = match node.cast::<ast::Expr>() {
|
||||
Some(ast::Expr::Text(..) | ast::Expr::Space(..)) => "typ",
|
||||
Some(..) => "typc",
|
||||
None => "typ",
|
||||
};
|
||||
s.push_str(lang);
|
||||
s.push('\n');
|
||||
s.push_str(&content);
|
||||
s.push('\n');
|
||||
s.push_str("```");
|
||||
s
|
||||
};
|
||||
|
||||
Ok(Value::Content(s))
|
||||
}
|
||||
|
||||
pub fn render(
|
||||
&mut self,
|
||||
prepend_node: &SyntaxNode,
|
||||
node: &SyntaxNode,
|
||||
inline: bool,
|
||||
) -> Result<Value> {
|
||||
self.assets_numbering += 1;
|
||||
let prepend_code = prepend_node.clone().into_text();
|
||||
let code = node.clone().into_text();
|
||||
// if let Some(assets_src_path) = &self.feat.assets_src_path {
|
||||
// let file_name = assets_src_path
|
||||
// .join(self.assets_numbering.to_string())
|
||||
// .with_extension("typ");
|
||||
// if let Err(e) = std::fs::write(&file_name, format!("#{{\n// render_code\n{}\n}}", code))
|
||||
// {
|
||||
// return Err(format!("failed to write code to file: {}", e).into());
|
||||
// }
|
||||
// }
|
||||
self.render_code(&prepend_code, &code, false, "center", "", inline)
|
||||
}
|
||||
|
||||
pub fn render_code(
|
||||
&mut self,
|
||||
prepend_code: &str,
|
||||
code: &str,
|
||||
is_markup: bool,
|
||||
align: &str,
|
||||
extra_attrs: &str,
|
||||
inline: bool,
|
||||
) -> Result<Value> {
|
||||
let theme = self.feat.color_theme;
|
||||
|
||||
// let code_file_name = if let Some(assets_src_path) = &self.feat.assets_src_path {
|
||||
// Some(
|
||||
// assets_src_path
|
||||
// .join(self.assets_numbering.to_string())
|
||||
// .with_extension("typ"),
|
||||
// )
|
||||
// } else {
|
||||
// None
|
||||
// };
|
||||
|
||||
let code_file_name = None;
|
||||
|
||||
let mut render = |theme| self.render_inner(prepend_code, code, is_markup, theme);
|
||||
|
||||
let mut content = EcoString::new();
|
||||
|
||||
let inline_attrs = if inline {
|
||||
r#" style="vertical-align: -0.35em""#
|
||||
} else {
|
||||
""
|
||||
};
|
||||
|
||||
let write_error = |content: &mut EcoString, err: &str| {
|
||||
let err = err.replace("`", r#"\`"#);
|
||||
let _ = write!(content, "```\nRender Error\n{err}\n```");
|
||||
};
|
||||
|
||||
let write_image = |content: &mut EcoString,
|
||||
file_name: &std::path::Path,
|
||||
code_file_name: Option<&PathBuf>,
|
||||
inline_attrs: &str,
|
||||
extra_attrs: &str| {
|
||||
if let Some(code_file_name) = code_file_name {
|
||||
let _ = write!(
|
||||
content,
|
||||
r#"<a href="{}"><img{inline_attrs} alt="typst-block" src="{}" {extra_attrs}/></a>"#,
|
||||
code_file_name.display(),
|
||||
file_name.display()
|
||||
);
|
||||
} else {
|
||||
let _ = write!(
|
||||
content,
|
||||
r#"<img{inline_attrs} alt="typst-block" src="{}" {extra_attrs}/>"#,
|
||||
file_name.display()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
let write_picture = |content: &mut EcoString,
|
||||
dark_file_name: &std::path::Path,
|
||||
light_file_name: &std::path::Path,
|
||||
code_file_name: Option<&PathBuf>,
|
||||
inline_attrs: &str,
|
||||
extra_attrs: &str| {
|
||||
if let Some(code_file_name) = code_file_name {
|
||||
let _ = write!(
|
||||
content,
|
||||
r#"<a href="{}"><picture><source media="(prefers-color-scheme: dark)" srcset="{}"><img{inline_attrs} alt="typst-block" src="{}" {extra_attrs}/></picture></a>"#,
|
||||
code_file_name.display(),
|
||||
dark_file_name.display(),
|
||||
light_file_name.display()
|
||||
);
|
||||
} else {
|
||||
let _ = write!(
|
||||
content,
|
||||
r#"<picture><source media="(prefers-color-scheme: dark)" srcset="{}"><img{inline_attrs} alt="typst-block" src="{}" {extra_attrs}/></picture>"#,
|
||||
dark_file_name.display(),
|
||||
light_file_name.display()
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
match theme {
|
||||
Some(theme) => {
|
||||
let data = match render(theme) {
|
||||
Ok(data) => data,
|
||||
Err(err) if self.feat.soft_error => {
|
||||
write_error(&mut content, &err.to_string());
|
||||
return Ok(Value::Content(content));
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
|
||||
if !inline {
|
||||
let _ = write!(content, r#"<p align="{align}">"#);
|
||||
}
|
||||
if let Some(assets_path) = &self.feat.assets_path {
|
||||
let file_name =
|
||||
assets_path.join(format!("{}_{:?}.svg", self.assets_numbering, theme));
|
||||
std::fs::write(&file_name, &data)
|
||||
.map_err(|e| format!("failed to write SVG to file: {}", e))?;
|
||||
|
||||
write_image(
|
||||
&mut content,
|
||||
&file_name,
|
||||
code_file_name.as_ref(),
|
||||
inline_attrs,
|
||||
extra_attrs,
|
||||
);
|
||||
} else {
|
||||
let _ = write!(
|
||||
content,
|
||||
r#"<img{inline_attrs} alt="typst-block" src="data:image/svg+xml;base64,{data}" {extra_attrs}/>"#
|
||||
);
|
||||
}
|
||||
if !inline {
|
||||
content.push_str("</p>");
|
||||
}
|
||||
}
|
||||
None => {
|
||||
let dark = match render(crate::ColorTheme::Dark) {
|
||||
Ok(d) => d,
|
||||
Err(err) if self.feat.soft_error => {
|
||||
write_error(&mut content, &err.to_string());
|
||||
return Ok(Value::Content(content));
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
let light = match render(crate::ColorTheme::Light) {
|
||||
Ok(l) => l,
|
||||
Err(err) if self.feat.soft_error => {
|
||||
write_error(&mut content, &err.to_string());
|
||||
return Ok(Value::Content(content));
|
||||
}
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
|
||||
if !inline {
|
||||
let _ = write!(content, r#"<p align="{align}">"#);
|
||||
}
|
||||
if let Some(assets_path) = &self.feat.assets_path {
|
||||
let dark_file_name = assets_path.join(format!(
|
||||
"{}_{:?}.svg",
|
||||
self.assets_numbering,
|
||||
crate::ColorTheme::Dark
|
||||
));
|
||||
let light_file_name = assets_path.join(format!(
|
||||
"{}_{:?}.svg",
|
||||
self.assets_numbering,
|
||||
crate::ColorTheme::Light
|
||||
));
|
||||
|
||||
write_picture(
|
||||
&mut content,
|
||||
&dark_file_name,
|
||||
&light_file_name,
|
||||
code_file_name.as_ref(),
|
||||
inline_attrs,
|
||||
extra_attrs,
|
||||
);
|
||||
} else {
|
||||
let _ = write!(
|
||||
content,
|
||||
r#"<picture><source media="(prefers-color-scheme: dark)" srcset="data:image/svg+xml;base64,{dark}"><img{inline_attrs} alt="typst-block" src="data:image/svg+xml;base64,{light}" {extra_attrs}/></picture>"#
|
||||
);
|
||||
}
|
||||
if !inline {
|
||||
content.push_str("</p>");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Value::Content(content))
|
||||
}
|
||||
|
||||
fn render_inner(
|
||||
&mut self,
|
||||
prepend_code: &str,
|
||||
code: &str,
|
||||
is_markup: bool,
|
||||
theme: crate::ColorTheme,
|
||||
) -> Result<String> {
|
||||
static DARK_THEME_INPUT: LazyLock<Arc<LazyHash<Dict>>> = LazyLock::new(|| {
|
||||
Arc::new(LazyHash::new(Dict::from_iter(std::iter::once((
|
||||
"x-color-theme".into(),
|
||||
"dark".into_value(),
|
||||
)))))
|
||||
});
|
||||
|
||||
let code = WrapCode(code, is_markup);
|
||||
let inputs = match theme {
|
||||
crate::ColorTheme::Dark => Some(DARK_THEME_INPUT.clone()),
|
||||
crate::ColorTheme::Light => None,
|
||||
};
|
||||
let code = eco_format!(
|
||||
r##"{prepend_code}
|
||||
#set page(width: auto, height: auto, margin: (y: 0.45em, rest: 0em), fill: none);
|
||||
#set text(fill: rgb("#c0caf5")) if sys.inputs.at("x-color-theme", default: none) == "dark";
|
||||
{code}"##
|
||||
);
|
||||
let main = Bytes::new(code.as_bytes().to_owned());
|
||||
|
||||
let path = Path::new("__render__.typ");
|
||||
let entry = self.world.entry_state().select_in_workspace(path);
|
||||
let mut world = self.world.task(tinymist_project::TaskInputs {
|
||||
entry: Some(entry),
|
||||
inputs,
|
||||
});
|
||||
world.take_db();
|
||||
world.map_shadow_by_id(world.main(), main).unwrap();
|
||||
|
||||
let document = typst::compile(&world).output;
|
||||
let document = document.map_err(|diagnostics| {
|
||||
let mut err = String::new();
|
||||
let _ = write!(err, "compiling node: ");
|
||||
let write_span = |span: typst_syntax::Span, err: &mut String| {
|
||||
let file = span.id().map(|id| match id.package() {
|
||||
Some(package) if WorkspaceResolver::is_package_file(id) => {
|
||||
format!("{package}:{}", unix_slash(id.vpath().as_rooted_path()))
|
||||
}
|
||||
Some(_) | None => unix_slash(id.vpath().as_rooted_path()),
|
||||
});
|
||||
let range = world.range(span);
|
||||
match (file, range) {
|
||||
(Some(file), Some(range)) => {
|
||||
let _ = write!(err, "{file:?}:{range:?}");
|
||||
}
|
||||
(Some(file), None) => {
|
||||
let _ = write!(err, "{file:?}");
|
||||
}
|
||||
(None, Some(range)) => {
|
||||
let _ = write!(err, "{range:?}");
|
||||
}
|
||||
_ => {
|
||||
let _ = write!(err, "unknown location");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for s in diagnostics.iter() {
|
||||
match s.severity {
|
||||
typst::diag::Severity::Error => {
|
||||
let _ = write!(err, "error: ");
|
||||
}
|
||||
typst::diag::Severity::Warning => {
|
||||
let _ = write!(err, "warning: ");
|
||||
}
|
||||
}
|
||||
|
||||
err.push_str(&s.message);
|
||||
err.push_str(" at ");
|
||||
write_span(s.span, &mut err);
|
||||
|
||||
for hint in s.hints.iter() {
|
||||
err.push_str("\nHint: ");
|
||||
err.push_str(hint);
|
||||
}
|
||||
|
||||
for trace in &s.trace {
|
||||
write!(err, "\nTrace: {} at ", trace.v).unwrap();
|
||||
write_span(trace.span, &mut err);
|
||||
}
|
||||
|
||||
err.push('\n');
|
||||
}
|
||||
|
||||
err
|
||||
})?;
|
||||
|
||||
let svg_payload = typst_svg::svg_merged(&document, Abs::zero());
|
||||
|
||||
if let Some(assets_path) = &self.feat.assets_path {
|
||||
let file_name = assets_path.join(format!("{}_{:?}.svg", self.assets_numbering, theme));
|
||||
if let Err(e) = std::fs::write(&file_name, &svg_payload) {
|
||||
return Err(format!("failed to write SVG to file: {}", e).into());
|
||||
}
|
||||
Ok(file_name.to_string_lossy().to_string())
|
||||
} else {
|
||||
Ok(base64::engine::general_purpose::STANDARD.encode(svg_payload))
|
||||
}
|
||||
}
|
||||
|
||||
fn char(arg: char) -> Result<Value> {
|
||||
Ok(Value::Content(arg.into()))
|
||||
}
|
||||
|
||||
fn str(node: &SyntaxNode) -> Result<Value> {
|
||||
Ok(Value::Content(node.clone().into_text()))
|
||||
}
|
||||
|
||||
pub fn value(res: Value) -> EcoString {
|
||||
match res {
|
||||
Value::None => EcoString::new(),
|
||||
Value::Content(content) => content,
|
||||
Value::Str(s) => s,
|
||||
Value::Image { path, alt } => eco_format!(""),
|
||||
_ => eco_format!("{res:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
fn escape(node: &SyntaxNode) -> Result<Value> {
|
||||
// todo: escape characters
|
||||
Self::str(node)
|
||||
}
|
||||
|
||||
fn shorthand(node: &SyntaxNode) -> Result<Value> {
|
||||
// todo: shorthands
|
||||
Self::str(node)
|
||||
}
|
||||
|
||||
fn strong(&mut self, node: &SyntaxNode) -> Result<Value> {
|
||||
let mut s = EcoString::new();
|
||||
|
||||
let strong = node.cast::<Strong>().unwrap();
|
||||
s.push_str("**");
|
||||
s.push_str(&Self::value(self.eval(strong.body().to_untyped())?));
|
||||
s.push_str("**");
|
||||
|
||||
Ok(Value::Content(s))
|
||||
}
|
||||
|
||||
fn emph(&mut self, node: &SyntaxNode) -> Result<Value> {
|
||||
let mut s = EcoString::new();
|
||||
let emph = node.cast::<Emph>().unwrap();
|
||||
s.push('_');
|
||||
s.push_str(&Self::value(self.eval(emph.body().to_untyped())?));
|
||||
s.push('_');
|
||||
Ok(Value::Content(s))
|
||||
}
|
||||
|
||||
fn heading(&mut self, node: &SyntaxNode) -> Result<Value> {
|
||||
let mut s = EcoString::new();
|
||||
let heading = node.cast::<Heading>().unwrap();
|
||||
let level = heading.depth();
|
||||
for _ in 0..level.get() {
|
||||
s.push('#');
|
||||
}
|
||||
s.push(' ');
|
||||
s.push_str(&Self::value(self.eval(heading.body().to_untyped())?));
|
||||
Ok(Value::Content(s))
|
||||
}
|
||||
|
||||
fn raw(node: &SyntaxNode) -> Result<Value> {
|
||||
let mut s = EcoString::new();
|
||||
let raw = node.cast::<Raw>().unwrap();
|
||||
|
||||
// Raw codes with typlite language will not be treated as a code block but
|
||||
// directly output into the Markdown result.
|
||||
if let Some(lang) = raw.lang() {
|
||||
if &EcoString::from("typlite") == lang.get() {
|
||||
for line in raw.lines() {
|
||||
s.push_str(&Self::value(Self::str(line.to_untyped())?));
|
||||
s.push('\n');
|
||||
}
|
||||
return Ok(Value::Content(s));
|
||||
}
|
||||
}
|
||||
|
||||
if raw.block() {
|
||||
s.push_str(&Self::value(Self::str(node)?));
|
||||
return Ok(Value::Content(s));
|
||||
}
|
||||
s.push('`');
|
||||
for line in raw.lines() {
|
||||
s.push_str(&Self::value(Self::str(line.to_untyped())?));
|
||||
}
|
||||
s.push('`');
|
||||
Ok(Value::Content(s))
|
||||
}
|
||||
|
||||
fn link(&mut self, node: &SyntaxNode) -> Result<Value> {
|
||||
// GFM supports autolinks
|
||||
if self.feat.gfm {
|
||||
return Self::str(node);
|
||||
}
|
||||
let mut s = EcoString::new();
|
||||
s.push('[');
|
||||
s.push_str(&Self::value(Self::str(node)?));
|
||||
s.push(']');
|
||||
s.push('(');
|
||||
s.push_str(&Self::value(Self::str(node)?));
|
||||
s.push(')');
|
||||
|
||||
Ok(Value::Content(s))
|
||||
}
|
||||
|
||||
fn label(_node: &SyntaxNode) -> Result<Value> {
|
||||
Result::Ok(Value::None)
|
||||
}
|
||||
|
||||
fn label_ref(node: &SyntaxNode) -> Result<Value> {
|
||||
Self::str(node)
|
||||
}
|
||||
|
||||
fn ref_marker(node: &SyntaxNode) -> Result<Value> {
|
||||
Self::str(node)
|
||||
}
|
||||
|
||||
fn list_item(&mut self, node: &SyntaxNode) -> Result<Value> {
|
||||
let mut s = EcoString::new();
|
||||
|
||||
let list_item = node.cast::<ast::ListItem>().unwrap();
|
||||
|
||||
for _ in 0..self.list_depth {
|
||||
s.push_str(" ");
|
||||
}
|
||||
|
||||
s.push_str("- ");
|
||||
if self.feat.annotate_elem {
|
||||
let _ = write!(s, "<!-- typlite:begin:list-item {} -->", self.list_depth);
|
||||
self.list_depth += 1;
|
||||
}
|
||||
s.push_str(&Self::value(self.eval(list_item.body().to_untyped())?));
|
||||
if self.feat.annotate_elem {
|
||||
self.list_depth -= 1;
|
||||
let _ = write!(s, "<!-- typlite:end:list-item {} -->", self.list_depth);
|
||||
}
|
||||
|
||||
Ok(Value::Content(s))
|
||||
}
|
||||
|
||||
fn enum_item(&mut self, node: &SyntaxNode) -> Result<Value> {
|
||||
let enum_item = node.cast::<ast::EnumItem>().unwrap();
|
||||
let mut s = EcoString::new();
|
||||
|
||||
for _ in 0..self.list_depth {
|
||||
s.push_str(" ");
|
||||
}
|
||||
|
||||
if self.feat.annotate_elem {
|
||||
let _ = write!(s, "<!-- typlite:begin:enum-item {} -->", self.list_depth);
|
||||
self.list_depth += 1;
|
||||
}
|
||||
|
||||
if let Some(num) = enum_item.number() {
|
||||
s.push_str(&format!("{}. ", num));
|
||||
} else {
|
||||
s.push_str("1. ");
|
||||
}
|
||||
|
||||
s.push_str(&Self::value(self.eval(enum_item.body().to_untyped())?));
|
||||
|
||||
if self.feat.annotate_elem {
|
||||
self.list_depth -= 1;
|
||||
let _ = write!(s, "<!-- typlite:end:enum-item {} -->", self.list_depth);
|
||||
}
|
||||
|
||||
Ok(Value::Content(s))
|
||||
}
|
||||
|
||||
fn term_item(&mut self, node: &SyntaxNode) -> Result<Value> {
|
||||
self.reduce(node)
|
||||
}
|
||||
|
||||
fn equation(&mut self, node: &SyntaxNode) -> Result<Value> {
|
||||
let equation: Equation = node.cast().unwrap();
|
||||
|
||||
if self.feat.remove_html {
|
||||
return self.to_raw_block(node, !equation.block());
|
||||
}
|
||||
|
||||
self.render(&SyntaxNode::leaf(Text, ""), node, !equation.block())
|
||||
}
|
||||
|
||||
fn let_binding(&self, node: &SyntaxNode) -> Result<Value> {
|
||||
let _ = node;
|
||||
|
||||
Ok(Value::None)
|
||||
}
|
||||
|
||||
fn field_access(&self, node: &SyntaxNode) -> Result<Value> {
|
||||
let _ = node;
|
||||
|
||||
Ok(Value::None)
|
||||
}
|
||||
|
||||
fn func_call(&mut self, node: &SyntaxNode) -> Result<Value> {
|
||||
let c: ast::FuncCall = node.cast().unwrap();
|
||||
|
||||
let callee = match c.callee() {
|
||||
ast::Expr::Ident(callee) => self.scopes.get(callee.get()),
|
||||
ast::Expr::FieldAccess(..) => return Ok(Value::None),
|
||||
_ => return Ok(Value::None),
|
||||
}?;
|
||||
|
||||
let Value::RawFunc(func) = callee else {
|
||||
return Err("callee is not a function")?;
|
||||
};
|
||||
|
||||
func(Args::new(self, c.args()))
|
||||
}
|
||||
|
||||
fn contextual(&mut self, node: &SyntaxNode) -> Result<Value> {
|
||||
if self.feat.remove_html {
|
||||
return self.to_raw_block(node, false);
|
||||
}
|
||||
// Trim the last `#` in the prepend code. (#context)
|
||||
self.prepend_code = self.prepend_code.trim_end_matches('#').into();
|
||||
self.render(
|
||||
&SyntaxNode::leaf(node.kind(), self.prepend_code.clone()),
|
||||
node,
|
||||
false,
|
||||
)
|
||||
}
|
||||
|
||||
fn include(&self, node: &SyntaxNode) -> Result<Value> {
|
||||
let include: ast::ModuleInclude = node.cast().unwrap();
|
||||
|
||||
let path = include.source();
|
||||
let src =
|
||||
tinymist_analysis::syntax::find_source_by_expr(self.world.as_ref(), self.current, path)
|
||||
.ok_or_else(|| format!("failed to find source on path {path:?}"))?;
|
||||
|
||||
self.clone().sub_file(src).map(Value::Content)
|
||||
}
|
||||
|
||||
fn sub_file(mut self, src: Source) -> Result<EcoString> {
|
||||
self.current = src.id();
|
||||
self.convert(src.root())
|
||||
}
|
||||
}
|
||||
|
||||
struct WrapCode<'a>(&'a str, bool);
|
||||
|
||||
impl fmt::Display for WrapCode<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let is_markup = self.1;
|
||||
if is_markup {
|
||||
f.write_str("#[")?;
|
||||
} else {
|
||||
f.write_str("#{")?;
|
||||
}
|
||||
f.write_str(self.0)?;
|
||||
if is_markup {
|
||||
f.write_str("]")
|
||||
} else {
|
||||
f.write_str("}")
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue