mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-07 21:15:03 +00:00
feat: pretty errors in docstrings (#1876)
Some checks are pending
tinymist::ci / E2E Tests (linux-x64 on ubuntu-latest) (push) Blocked by required conditions
tinymist::ci / Duplicate Actions Detection (push) Waiting to run
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Waiting to run
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Waiting to run
tinymist::ci / E2E Tests (darwin-arm64 on macos-latest) (push) Blocked by required conditions
tinymist::ci / E2E Tests (linux-x64 on ubuntu-22.04) (push) Blocked by required conditions
tinymist::ci / E2E Tests (win32-x64 on windows-2022) (push) Blocked by required conditions
tinymist::ci / E2E Tests (win32-x64 on windows-latest) (push) Blocked by required conditions
tinymist::ci / prepare-build (push) Waiting to run
tinymist::ci / build-binary (push) Blocked by required conditions
tinymist::ci / build-vsc-assets (push) Blocked by required conditions
tinymist::ci / build-vscode (push) Blocked by required conditions
tinymist::ci / build-vscode-others (push) Blocked by required conditions
tinymist::ci / publish-vscode (push) Blocked by required conditions
tinymist::gh_pages / build-gh-pages (push) Waiting to run
Some checks are pending
tinymist::ci / E2E Tests (linux-x64 on ubuntu-latest) (push) Blocked by required conditions
tinymist::ci / Duplicate Actions Detection (push) Waiting to run
tinymist::ci / Check Clippy, Formatting, Completion, Documentation, and Tests (Linux) (push) Waiting to run
tinymist::ci / Check Minimum Rust version and Tests (Windows) (push) Waiting to run
tinymist::ci / E2E Tests (darwin-arm64 on macos-latest) (push) Blocked by required conditions
tinymist::ci / E2E Tests (linux-x64 on ubuntu-22.04) (push) Blocked by required conditions
tinymist::ci / E2E Tests (win32-x64 on windows-2022) (push) Blocked by required conditions
tinymist::ci / E2E Tests (win32-x64 on windows-latest) (push) Blocked by required conditions
tinymist::ci / prepare-build (push) Waiting to run
tinymist::ci / build-binary (push) Blocked by required conditions
tinymist::ci / build-vsc-assets (push) Blocked by required conditions
tinymist::ci / build-vscode (push) Blocked by required conditions
tinymist::ci / build-vscode-others (push) Blocked by required conditions
tinymist::ci / publish-vscode (push) Blocked by required conditions
tinymist::gh_pages / build-gh-pages (push) Waiting to run
* feat: print doc errors
* fix: test errors on windows
* fix: tests on windows again
* fix: tests on windows again 2
* Revert "fix: tests on windows again 2"
This reverts commit 63973dcc1f
.
* fix: tests on windows again 3
This commit is contained in:
parent
9787432029
commit
4426b31ed7
18 changed files with 488 additions and 132 deletions
|
@ -3,9 +3,12 @@ use std::sync::Arc;
|
|||
|
||||
use ecow::{eco_format, EcoString};
|
||||
use tinymist_std::path::unix_slash;
|
||||
use tinymist_world::system::print_diagnostics_to_string;
|
||||
use tinymist_world::vfs::WorkspaceResolver;
|
||||
use tinymist_world::{EntryReader, EntryState, ShadowApi, TaskInputs};
|
||||
use typlite::TypliteFeat;
|
||||
use tinymist_world::{
|
||||
DiagnosticFormat, EntryReader, EntryState, ShadowApi, SourceWorld, TaskInputs,
|
||||
};
|
||||
use typlite::{Format, TypliteFeat};
|
||||
use typst::diag::StrResult;
|
||||
use typst::foundations::Bytes;
|
||||
use typst::syntax::FileId;
|
||||
|
@ -19,7 +22,7 @@ pub(crate) fn convert_docs(
|
|||
source_fid: Option<FileId>,
|
||||
) -> StrResult<EcoString> {
|
||||
let mut entry = ctx.world.entry_state();
|
||||
let (contextual_content, import_context) = if let Some(fid) = source_fid {
|
||||
let import_context = source_fid.map(|fid| {
|
||||
let root = ctx
|
||||
.world
|
||||
.vfs()
|
||||
|
@ -42,16 +45,15 @@ pub(crate) fn convert_docs(
|
|||
"#import {:?}: *",
|
||||
unix_slash(fid.vpath().as_rooted_path())
|
||||
));
|
||||
let imports = imports.join("\n");
|
||||
let content_with_import: String = if !imports.is_empty() {
|
||||
format!("{imports}\n\n{content}")
|
||||
} else {
|
||||
content.to_owned()
|
||||
};
|
||||
|
||||
(content_with_import, Some(imports))
|
||||
} else {
|
||||
(content.to_owned(), None)
|
||||
imports.join("; ")
|
||||
});
|
||||
let feat = TypliteFeat {
|
||||
color_theme: Some(ctx.analysis.color_theme),
|
||||
annotate_elem: true,
|
||||
soft_error: true,
|
||||
remove_html: ctx.analysis.remove_html,
|
||||
import_context,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let entry = entry.select_in_workspace(Path::new("__tinymist_docs__.typ"));
|
||||
|
@ -61,21 +63,39 @@ pub(crate) fn convert_docs(
|
|||
inputs: None,
|
||||
});
|
||||
|
||||
w.map_shadow_by_id(w.main(), Bytes::from_string(contextual_content))?;
|
||||
// todo: bad performance: content.to_owned()
|
||||
w.map_shadow_by_id(w.main(), Bytes::from_string(content.to_owned()))?;
|
||||
// todo: bad performance
|
||||
w.take_db();
|
||||
let w = feat
|
||||
.prepare_world(&w, Format::Md)
|
||||
.map_err(|e| eco_format!("failed to prepare world: {e}"))?;
|
||||
|
||||
let conv = typlite::Typlite::new(Arc::new(w))
|
||||
.with_feature(TypliteFeat {
|
||||
color_theme: Some(ctx.analysis.color_theme),
|
||||
annotate_elem: true,
|
||||
soft_error: true,
|
||||
remove_html: ctx.analysis.remove_html,
|
||||
import_context,
|
||||
..Default::default()
|
||||
})
|
||||
.convert()
|
||||
.map_err(|err| eco_format!("failed to convert to markdown: {err}"))?;
|
||||
let w = Arc::new(w);
|
||||
let res = typlite::Typlite::new(w.clone())
|
||||
.with_feature(feat)
|
||||
.convert();
|
||||
let conv = print_diag_or_error(w.as_ref(), res)?;
|
||||
|
||||
Ok(conv.replace("```example", "```typ"))
|
||||
}
|
||||
|
||||
fn print_diag_or_error<T>(
|
||||
world: &impl SourceWorld,
|
||||
result: tinymist_std::Result<T>,
|
||||
) -> StrResult<T> {
|
||||
match result {
|
||||
Ok(v) => Ok(v),
|
||||
Err(err) => {
|
||||
if let Some(diagnostics) = err.diagnostics() {
|
||||
return Err(print_diagnostics_to_string(
|
||||
world,
|
||||
diagnostics.iter(),
|
||||
DiagnosticFormat::Human,
|
||||
)?);
|
||||
}
|
||||
|
||||
Err(eco_format!("failed to convert docs: {err}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
/// *
|
||||
#let my-fun(mode: "typ", setting: it => it, note) = {
|
||||
touying-fn-wrapper(utils.my-fun, mode: mode, setting: setting, note)
|
||||
}
|
||||
|
||||
#(/* ident after */ my-fun);
|
|
@ -0,0 +1,9 @@
|
|||
/// Good doc
|
||||
#let my-fun(mode: "typ", setting: it => it, note) = {
|
||||
touying-fn-wrapper(utils.my-fun, mode: mode, setting: setting, note)
|
||||
}
|
||||
|
||||
#(/* ident after */ my-fun);
|
||||
|
||||
|
||||
#my-f
|
|
@ -0,0 +1,14 @@
|
|||
/// Good doc
|
||||
///
|
||||
/// #example(```
|
||||
/// my-fun()
|
||||
/// ```)
|
||||
///
|
||||
#let my-fun(mode: "typ", setting: it => it, note) = {
|
||||
touying-fn-wrapper(utils.my-fun, mode: mode, setting: setting, note)
|
||||
}
|
||||
|
||||
#(/* ident after */ my-fun);
|
||||
|
||||
|
||||
#my-f
|
|
@ -0,0 +1,58 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/hover.rs
|
||||
expression: content
|
||||
input_file: crates/tinymist-query/src/fixtures/hover/error_in_doc.typ
|
||||
---
|
||||
Range: 5:20:5:26
|
||||
|
||||
```typc
|
||||
let my-fun(
|
||||
note: any,
|
||||
mode: str = "typ",
|
||||
setting: (any) => any = Closure(..),
|
||||
) = none;
|
||||
```
|
||||
|
||||
|
||||
======
|
||||
|
||||
|
||||
```
|
||||
failed to parse docs: error: unclosed delimiter
|
||||
┌─ /dummy-root/__wrap_md_main.typ:2:0
|
||||
│
|
||||
2 │ *
|
||||
│ ^
|
||||
|
||||
|
||||
```
|
||||
|
||||
```typ
|
||||
*
|
||||
```
|
||||
|
||||
# Positional Parameters
|
||||
|
||||
## note
|
||||
|
||||
```typc
|
||||
type:
|
||||
```
|
||||
|
||||
|
||||
|
||||
# Named Parameters
|
||||
|
||||
## mode
|
||||
|
||||
```typc
|
||||
type: "typ"
|
||||
```
|
||||
|
||||
|
||||
|
||||
## setting (named)
|
||||
|
||||
```typc
|
||||
type: (any) => any
|
||||
```
|
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/hover.rs
|
||||
expression: content
|
||||
input_file: crates/tinymist-query/src/fixtures/hover/error_in_module.typ
|
||||
---
|
||||
Range: 5:20:5:26
|
||||
|
||||
```typc
|
||||
let my-fun(
|
||||
note: any,
|
||||
mode: str = "typ",
|
||||
setting: (any) => any = Closure(..),
|
||||
) = none;
|
||||
```
|
||||
|
||||
|
||||
======
|
||||
|
||||
|
||||
Good doc
|
||||
|
||||
# Positional Parameters
|
||||
|
||||
## note
|
||||
|
||||
```typc
|
||||
type:
|
||||
```
|
||||
|
||||
|
||||
|
||||
# Named Parameters
|
||||
|
||||
## mode
|
||||
|
||||
```typc
|
||||
type: "typ"
|
||||
```
|
||||
|
||||
|
||||
|
||||
## setting (named)
|
||||
|
||||
```typc
|
||||
type: (any) => any
|
||||
```
|
|
@ -0,0 +1,64 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/hover.rs
|
||||
expression: content
|
||||
input_file: crates/tinymist-query/src/fixtures/hover/error_in_module_example.typ
|
||||
---
|
||||
Range: 10:20:10:26
|
||||
|
||||
```typc
|
||||
let my-fun(
|
||||
note: any,
|
||||
mode: str = "typ",
|
||||
setting: (any) => any = Closure(..),
|
||||
) = none;
|
||||
```
|
||||
|
||||
|
||||
======
|
||||
|
||||
|
||||
Good doc
|
||||
|
||||
```typ
|
||||
my-fun()
|
||||
```
|
||||
|
||||
Error compiling idoc: error: unknown variable: my-f
|
||||
┌─ /dummy-root/s0.typ:14:1
|
||||
│
|
||||
14 │ #my-f
|
||||
│ ^^^^
|
||||
│
|
||||
= hint: if you meant to use subtraction, try adding spaces around the minus sign: \`my - f\`
|
||||
|
||||
help: error occurred while importing this module
|
||||
┌─ /dummy-root/\_\_main\_\_.typ:1:8
|
||||
│
|
||||
1 │ #import "/s0.typ": \*
|
||||
│ ^^^^^^^^^
|
||||
|
||||
# Positional Parameters
|
||||
|
||||
## note
|
||||
|
||||
```typc
|
||||
type:
|
||||
```
|
||||
|
||||
|
||||
|
||||
# Named Parameters
|
||||
|
||||
## mode
|
||||
|
||||
```typc
|
||||
type: "typ"
|
||||
```
|
||||
|
||||
|
||||
|
||||
## setting (named)
|
||||
|
||||
```typc
|
||||
type: (any) => any
|
||||
```
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/hover.rs
|
||||
expression: content
|
||||
input_file: crates/tinymist-query/src/fixtures/hover/annotate_docs_error.typ
|
||||
input_file: crates/tinymist-query/src/fixtures/hover/example.typ
|
||||
---
|
||||
Range: 12:20:12:32
|
||||
|
|
@ -1,2 +1,6 @@
|
|||
#let tmpl2(stroke) = text(stroke: stroke)
|
||||
#tmpl2(/* position */)
|
||||
/// *
|
||||
#let my-fun(mode: "typ", setting: it => it, note) = {
|
||||
touying-fn-wrapper(utils.my-fun, mode: mode, setting: setting, note)
|
||||
}
|
||||
|
||||
#(/* ident after */ my-fun);
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
---
|
||||
source: crates/tinymist-query/src/hover.rs
|
||||
expression: content
|
||||
input_file: crates/tinymist-query/src/fixtures/playground/base.typ
|
||||
---
|
||||
Range: 5:20:5:26
|
||||
|
||||
```typc
|
||||
let my-fun(
|
||||
note: any,
|
||||
mode: str = "typ",
|
||||
setting: (any) => any = Closure(..),
|
||||
) = none;
|
||||
```
|
||||
|
||||
|
||||
======
|
||||
|
||||
|
||||
```
|
||||
failed to parse docs: error: unclosed delimiter
|
||||
┌─ /dummy-root/__wrap_md_main.typ:2:0
|
||||
│
|
||||
2 │ *
|
||||
│ ^
|
||||
|
||||
|
||||
```
|
||||
|
||||
```typ
|
||||
*
|
||||
```
|
||||
|
||||
# Positional Parameters
|
||||
|
||||
## note
|
||||
|
||||
```typc
|
||||
type:
|
||||
```
|
||||
|
||||
|
||||
|
||||
# Named Parameters
|
||||
|
||||
## mode
|
||||
|
||||
```typc
|
||||
type: "typ"
|
||||
```
|
||||
|
||||
|
||||
|
||||
## setting (named)
|
||||
|
||||
```typc
|
||||
type: (any) => any
|
||||
```
|
|
@ -8,6 +8,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
use regex::{Regex, Replacer};
|
||||
use serde_json::{ser::PrettyFormatter, Serializer, Value};
|
||||
use tinymist_project::{LspCompileSnapshot, LspComputeGraph};
|
||||
use tinymist_std::path::unix_slash;
|
||||
|
@ -333,13 +334,14 @@ impl JsonRepr {
|
|||
}
|
||||
|
||||
pub fn md_content(v: &str) -> Cow<'_, str> {
|
||||
static REG: LazyLock<regex::Regex> =
|
||||
LazyLock::new(|| regex::Regex::new(r#"data:image/svg\+xml;base64,([^"]+)"#).unwrap());
|
||||
static REG: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r#"data:image/svg\+xml;base64,([^"]+)"#).unwrap());
|
||||
static REG2: LazyLock<Regex> =
|
||||
LazyLock::new(|| Regex::new(r#"C:\\?\\dummy-root\\?\\"#).unwrap());
|
||||
let v = REG.replace_all(v, |_captures: ®ex::Captures| {
|
||||
"data:image-hash/svg+xml;base64,redacted"
|
||||
});
|
||||
|
||||
v
|
||||
REG2.replace_all_cow(v, "/dummy-root/")
|
||||
}
|
||||
|
||||
pub fn range(v: impl serde::Serialize) -> String {
|
||||
|
@ -360,8 +362,7 @@ impl fmt::Display for JsonRepr {
|
|||
|
||||
let res = String::from_utf8(ser.into_inner().into_inner().unwrap()).unwrap();
|
||||
// replace Span(number) to Span(..)
|
||||
static REG: LazyLock<regex::Regex> =
|
||||
LazyLock::new(|| regex::Regex::new(r#"Span\((\d+)\)"#).unwrap());
|
||||
static REG: LazyLock<Regex> = LazyLock::new(|| Regex::new(r#"Span\((\d+)\)"#).unwrap());
|
||||
let res = REG.replace_all(&res, "Span(..)");
|
||||
f.write_str(&res)
|
||||
}
|
||||
|
@ -470,3 +471,18 @@ impl fmt::Display for HashRepr<JsonRepr> {
|
|||
write!(f, "sha256:{}", hex::encode(hash))
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension methods for `Regex` that operate on `Cow<str>` instead of `&str`.
|
||||
pub trait RegexCowExt {
|
||||
/// [`Regex::replace_all`], but taking text as `Cow<str>` instead of `&str`.
|
||||
fn replace_all_cow<'t, R: Replacer>(&self, text: Cow<'t, str>, rep: R) -> Cow<'t, str>;
|
||||
}
|
||||
|
||||
impl RegexCowExt for Regex {
|
||||
fn replace_all_cow<'t, R: Replacer>(&self, text: Cow<'t, str>, rep: R) -> Cow<'t, str> {
|
||||
match self.replace_all(&text, rep) {
|
||||
Cow::Owned(result) => Cow::Owned(result),
|
||||
Cow::Borrowed(_) => text,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
use std::io::IsTerminal;
|
||||
use ecow::EcoString;
|
||||
|
||||
use std::io::IsTerminal;
|
||||
use std::str::FromStr;
|
||||
|
||||
use codespan_reporting::term::termcolor::{ColorChoice, NoColor, StandardStream, WriteColor};
|
||||
use codespan_reporting::{
|
||||
diagnostic::{Diagnostic, Label},
|
||||
term::{
|
||||
self,
|
||||
termcolor::{ColorChoice, StandardStream},
|
||||
},
|
||||
term,
|
||||
};
|
||||
use tinymist_std::Result;
|
||||
use tinymist_vfs::FileId;
|
||||
use typst::diag::{eco_format, Severity, SourceDiagnostic};
|
||||
use typst::diag::{eco_format, Severity, SourceDiagnostic, StrResult};
|
||||
use typst::syntax::Span;
|
||||
use typst::WorldExt;
|
||||
|
||||
use crate::{CodeSpanReportWorld, DiagnosticFormat, SourceWorld};
|
||||
|
||||
|
@ -30,13 +30,41 @@ pub fn print_diagnostics<'d, 'files>(
|
|||
errors: impl Iterator<Item = &'d SourceDiagnostic>,
|
||||
diagnostic_format: DiagnosticFormat,
|
||||
) -> Result<(), codespan_reporting::files::Error> {
|
||||
let world = CodeSpanReportWorld::new(world);
|
||||
|
||||
let mut w = match diagnostic_format {
|
||||
DiagnosticFormat::Human => color_stream(),
|
||||
DiagnosticFormat::Short => StandardStream::stderr(ColorChoice::Never),
|
||||
};
|
||||
|
||||
print_diagnostics_to(world, errors, &mut w, diagnostic_format)
|
||||
}
|
||||
|
||||
/// Print diagnostic messages to the terminal.
|
||||
pub fn print_diagnostics_to_string<'d, 'files>(
|
||||
world: &'files dyn SourceWorld,
|
||||
errors: impl Iterator<Item = &'d SourceDiagnostic>,
|
||||
diagnostic_format: DiagnosticFormat,
|
||||
) -> StrResult<EcoString> {
|
||||
let mut w = NoColor::new(vec![]);
|
||||
|
||||
print_diagnostics_to(world, errors, &mut w, diagnostic_format)
|
||||
.map_err(|e| eco_format!("failed to print diagnostics to string: {e}"))?;
|
||||
let output = EcoString::from_str(
|
||||
std::str::from_utf8(&w.into_inner())
|
||||
.map_err(|e| eco_format!("failed to convert diagnostics to string: {e}"))?,
|
||||
)
|
||||
.unwrap_or_default();
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
/// Print diagnostic messages to the terminal.
|
||||
pub fn print_diagnostics_to<'d, 'files>(
|
||||
world: &'files dyn SourceWorld,
|
||||
errors: impl Iterator<Item = &'d SourceDiagnostic>,
|
||||
w: &mut impl WriteColor,
|
||||
diagnostic_format: DiagnosticFormat,
|
||||
) -> Result<(), codespan_reporting::files::Error> {
|
||||
let world = CodeSpanReportWorld::new(world);
|
||||
|
||||
let mut config = term::Config {
|
||||
tab_width: 2,
|
||||
..Default::default()
|
||||
|
@ -60,7 +88,7 @@ pub fn print_diagnostics<'d, 'files>(
|
|||
)
|
||||
.with_labels(label(world.world, diagnostic.span).into_iter().collect());
|
||||
|
||||
term::emit(&mut w, &config, &world, &diag)?;
|
||||
term::emit(w, &config, &world, &diag)?;
|
||||
|
||||
// Stacktrace-like helper diagnostics.
|
||||
for point in &diagnostic.trace {
|
||||
|
@ -69,7 +97,7 @@ pub fn print_diagnostics<'d, 'files>(
|
|||
.with_message(message)
|
||||
.with_labels(label(world.world, point.span).into_iter().collect());
|
||||
|
||||
term::emit(&mut w, &config, &world, &help)?;
|
||||
term::emit(w, &config, &world, &help)?;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -78,5 +106,5 @@ pub fn print_diagnostics<'d, 'files>(
|
|||
|
||||
/// Create a label for a span.
|
||||
fn label(world: &dyn SourceWorld, span: Span) -> Option<Label<FileId>> {
|
||||
Some(Label::primary(span.id()?, world.range(span)?))
|
||||
Some(Label::primary(span.id()?, world.source_range(span)?))
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@ use typst::{
|
|||
syntax::{Source, Span, VirtualPath},
|
||||
text::{Font, FontBook},
|
||||
utils::LazyHash,
|
||||
Features, Library, World,
|
||||
Features, Library, World, WorldExt,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
@ -858,6 +858,10 @@ pub trait SourceWorld: World {
|
|||
self.source(id)
|
||||
.expect("file id does not point to any source file")
|
||||
}
|
||||
|
||||
fn source_range(&self, span: Span) -> Option<std::ops::Range<usize>> {
|
||||
self.range(span)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F: CompilerFeat> SourceWorld for CompilerWorld<F> {
|
||||
|
|
|
@ -178,6 +178,93 @@ pub struct TypliteFeat {
|
|||
pub processor: Option<String>,
|
||||
}
|
||||
|
||||
impl TypliteFeat {
|
||||
pub fn prepare_world(
|
||||
&self,
|
||||
world: &LspWorld,
|
||||
format: Format,
|
||||
) -> tinymist_std::Result<LspWorld> {
|
||||
let entry = world.entry_state();
|
||||
let main = entry.main();
|
||||
let current = main.context("no main file in workspace")?;
|
||||
|
||||
if WorkspaceResolver::is_package_file(current) {
|
||||
bail!("package file is not supported");
|
||||
}
|
||||
|
||||
let wrap_main_id = current.join("__wrap_md_main.typ");
|
||||
|
||||
let (main_id, main_content) = match self.processor.as_ref() {
|
||||
None => (wrap_main_id, None),
|
||||
Some(processor) => {
|
||||
let main_id = current.join("__md_main.typ");
|
||||
let content = format!(
|
||||
r#"#import {processor:?}: article
|
||||
#article(include "__wrap_md_main.typ")"#
|
||||
);
|
||||
|
||||
(main_id, Some(Bytes::from_string(content)))
|
||||
}
|
||||
};
|
||||
|
||||
let mut dict = TypstDict::new();
|
||||
dict.insert("x-target".into(), Str("md".into()));
|
||||
if format == Format::Text || self.remove_html {
|
||||
dict.insert("x-remove-html".into(), Str("true".into()));
|
||||
}
|
||||
|
||||
let task_inputs = TaskInputs {
|
||||
entry: Some(entry.select_in_workspace(main_id.vpath().as_rooted_path())),
|
||||
inputs: Some(Arc::new(LazyHash::new(dict))),
|
||||
};
|
||||
|
||||
let mut world = world.task(task_inputs).html_task().into_owned();
|
||||
|
||||
let markdown_id = FileId::new(
|
||||
Some(
|
||||
typst_syntax::package::PackageSpec::from_str("@local/_markdown:0.1.0")
|
||||
.context_ut("failed to import markdown package")?,
|
||||
),
|
||||
VirtualPath::new("lib.typ"),
|
||||
);
|
||||
|
||||
world
|
||||
.map_shadow_by_id(
|
||||
markdown_id.join("typst.toml"),
|
||||
Bytes::from_string(include_str!("markdown-typst.toml")),
|
||||
)
|
||||
.context_ut("cannot map markdown-typst.toml")?;
|
||||
world
|
||||
.map_shadow_by_id(
|
||||
markdown_id,
|
||||
Bytes::from_string(include_str!("markdown.typ")),
|
||||
)
|
||||
.context_ut("cannot map markdown.typ")?;
|
||||
|
||||
world
|
||||
.map_shadow_by_id(
|
||||
wrap_main_id,
|
||||
Bytes::from_string(format!(
|
||||
r#"#import "@local/_markdown:0.1.0": md-doc, example; #show: md-doc
|
||||
{}"#,
|
||||
world
|
||||
.source(current)
|
||||
.context_ut("failed to get main file content")?
|
||||
.text()
|
||||
)),
|
||||
)
|
||||
.context_ut("cannot map source for main file")?;
|
||||
|
||||
if let Some(main_content) = main_content {
|
||||
world
|
||||
.map_shadow_by_id(main_id, main_content)
|
||||
.context_ut("cannot map source for main file")?;
|
||||
}
|
||||
|
||||
Ok(world)
|
||||
}
|
||||
}
|
||||
|
||||
/// Task builder for converting a typst document to Markdown.
|
||||
pub struct Typlite {
|
||||
/// The universe to use for the conversion.
|
||||
|
@ -231,91 +318,22 @@ impl Typlite {
|
|||
|
||||
/// Convert the content to a markdown document.
|
||||
pub fn convert_doc(self, format: Format) -> tinymist_std::Result<MarkdownDocument> {
|
||||
let entry = self.world.entry_state();
|
||||
let main = entry.main();
|
||||
let current = main.context("no main file in workspace")?;
|
||||
let world_origin = self.world.clone();
|
||||
let world = self.world;
|
||||
|
||||
if WorkspaceResolver::is_package_file(current) {
|
||||
bail!("package file is not supported");
|
||||
}
|
||||
|
||||
let wrap_main_id = current.join("__wrap_md_main.typ");
|
||||
|
||||
let (main_id, main_content) = match self.feat.processor.as_ref() {
|
||||
None => (wrap_main_id, None),
|
||||
Some(processor) => {
|
||||
let main_id = current.join("__md_main.typ");
|
||||
let content = format!(
|
||||
r#"#import {processor:?}: article
|
||||
#article(include "__wrap_md_main.typ")"#
|
||||
);
|
||||
|
||||
(main_id, Some(Bytes::from_string(content)))
|
||||
}
|
||||
};
|
||||
|
||||
let mut dict = TypstDict::new();
|
||||
dict.insert("x-target".into(), Str("md".into()));
|
||||
if format == Format::Text || self.feat.remove_html {
|
||||
dict.insert("x-remove-html".into(), Str("true".into()));
|
||||
}
|
||||
|
||||
let task_inputs = TaskInputs {
|
||||
entry: Some(entry.select_in_workspace(main_id.vpath().as_rooted_path())),
|
||||
inputs: Some(Arc::new(LazyHash::new(dict))),
|
||||
};
|
||||
|
||||
let mut world = world.task(task_inputs).html_task().into_owned();
|
||||
|
||||
let markdown_id = FileId::new(
|
||||
Some(
|
||||
typst_syntax::package::PackageSpec::from_str("@local/markdown:0.1.0")
|
||||
.context_ut("failed to import markdown package")?,
|
||||
),
|
||||
VirtualPath::new("lib.typ"),
|
||||
);
|
||||
|
||||
world
|
||||
.map_shadow_by_id(
|
||||
markdown_id.join("typst.toml"),
|
||||
Bytes::from_string(include_str!("markdown-typst.toml")),
|
||||
)
|
||||
.context_ut("cannot map markdown-typst.toml")?;
|
||||
world
|
||||
.map_shadow_by_id(
|
||||
markdown_id,
|
||||
Bytes::from_string(include_str!("markdown.typ")),
|
||||
)
|
||||
.context_ut("cannot map markdown.typ")?;
|
||||
|
||||
world
|
||||
.map_shadow_by_id(
|
||||
wrap_main_id,
|
||||
Bytes::from_string(format!(
|
||||
r#"#import "@local/markdown:0.1.0": md-doc, example
|
||||
#show: md-doc
|
||||
{}"#,
|
||||
world
|
||||
.source(current)
|
||||
.context_ut("failed to get main file content")?
|
||||
.text()
|
||||
)),
|
||||
)
|
||||
.context_ut("cannot map source for main file")?;
|
||||
|
||||
if let Some(main_content) = main_content {
|
||||
world
|
||||
.map_shadow_by_id(main_id, main_content)
|
||||
.context_ut("cannot map source for main file")?;
|
||||
}
|
||||
let world = Arc::new(self.feat.prepare_world(&self.world, format)?);
|
||||
let feat = self.feat.clone();
|
||||
Self::convert_doc_prepared(feat, format, world)
|
||||
}
|
||||
|
||||
/// Convert the content to a markdown document.
|
||||
pub fn convert_doc_prepared(
|
||||
feat: TypliteFeat,
|
||||
format: Format,
|
||||
world: Arc<LspWorld>,
|
||||
) -> tinymist_std::Result<MarkdownDocument> {
|
||||
// todo: ignoring warnings
|
||||
let base = typst::compile(&world).output?;
|
||||
let mut feat = self.feat;
|
||||
let mut feat = feat;
|
||||
feat.target = format;
|
||||
Ok(MarkdownDocument::new(base, world_origin, feat))
|
||||
Ok(MarkdownDocument::new(base, world.clone(), feat))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[package]
|
||||
name = "markdown"
|
||||
name = "_markdown"
|
||||
version = "0.1.0"
|
||||
entrypoint = "lib.typ"
|
||||
description = "Markdown support for typst."
|
||||
|
|
|
@ -7,6 +7,7 @@ use std::sync::{Arc, LazyLock};
|
|||
use base64::Engine;
|
||||
use cmark_writer::ast::{HtmlAttribute, HtmlElement as CmarkHtmlElement, Node};
|
||||
use ecow::{eco_format, EcoString};
|
||||
use tinymist_project::system::print_diagnostics_to_string;
|
||||
use tinymist_project::{base::ShadowApi, EntryReader, TaskInputs, MEMORY_MAIN_ENTRY};
|
||||
use typst::{
|
||||
foundations::{Bytes, Dict, IntoValue},
|
||||
|
@ -277,12 +278,22 @@ impl HtmlToAstParser {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
//todo: ignoring warnings
|
||||
let doc = typst::compile(&world);
|
||||
let doc = match doc.output {
|
||||
Ok(doc) => doc,
|
||||
Err(e) => {
|
||||
let diag = doc.warnings.iter().chain(e.iter());
|
||||
|
||||
let e = print_diagnostics_to_string(
|
||||
&world,
|
||||
diag,
|
||||
tinymist_project::DiagnosticFormat::Human,
|
||||
)
|
||||
.unwrap_or_else(|e| e);
|
||||
|
||||
if self.feat.soft_error {
|
||||
return Node::Text(eco_format!("Error compiling idoc: {e:?}"));
|
||||
return Node::Text(eco_format!("Error compiling idoc: {e}"));
|
||||
} else {
|
||||
// Construct error node
|
||||
return Node::HtmlElement(CmarkHtmlElement {
|
||||
|
@ -291,7 +302,7 @@ impl HtmlToAstParser {
|
|||
name: EcoString::inline("class"),
|
||||
value: EcoString::inline("error"),
|
||||
}],
|
||||
children: vec![Node::Text(eco_format!("Error compiling idoc: {e:?}"))],
|
||||
children: vec![Node::Text(eco_format!("Error compiling idoc: {e}"))],
|
||||
self_closing: false,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -256,7 +256,7 @@ impl DocxWriter {
|
|||
}
|
||||
// Other inline element types
|
||||
_ => {
|
||||
println!("other inline element: {:?}", node);
|
||||
eprintln!("other inline element: {:?}", node);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue