mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-11-20 03:49:45 +00:00
feat: warning collector and logging for diagnostics in typlite (#2180)
Some checks are pending
tinymist::auto_tag / auto-tag (push) Waiting to run
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 / prepare-build (push) Waiting to run
tinymist::ci / announce (push) Blocked by required conditions
tinymist::ci / build (push) Blocked by required conditions
tinymist::gh_pages / build-gh-pages (push) Waiting to run
Some checks are pending
tinymist::auto_tag / auto-tag (push) Waiting to run
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 / prepare-build (push) Waiting to run
tinymist::ci / announce (push) Blocked by required conditions
tinymist::ci / build (push) Blocked by required conditions
tinymist::gh_pages / build-gh-pages (push) Waiting to run
This PR depends on #2173 and should be merged after it.
This commit is contained in:
parent
29a10c144e
commit
cee5bfa4e6
21 changed files with 288 additions and 55 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -4982,8 +4982,10 @@ dependencies = [
|
|||
"comemo",
|
||||
"docx-rs",
|
||||
"ecow",
|
||||
"env_logger",
|
||||
"image",
|
||||
"insta",
|
||||
"log",
|
||||
"regex",
|
||||
"resvg",
|
||||
"tinymist-derive",
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ pub(crate) fn convert_docs(
|
|||
));
|
||||
imports.join("; ")
|
||||
});
|
||||
let feat = TypliteFeat {
|
||||
let mut feat = TypliteFeat {
|
||||
color_theme: Some(ctx.analysis.color_theme),
|
||||
annotate_elem: true,
|
||||
soft_error: true,
|
||||
|
|
@ -67,10 +67,12 @@ pub(crate) fn convert_docs(
|
|||
w.map_shadow_by_id(w.main(), Bytes::from_string(content.to_owned()))?;
|
||||
// todo: bad performance
|
||||
w.take_db();
|
||||
let w = feat
|
||||
let (w, wrap_info) = feat
|
||||
.prepare_world(&w, Format::Md)
|
||||
.map_err(|e| eco_format!("failed to prepare world: {e}"))?;
|
||||
|
||||
feat.wrap_info = wrap_info;
|
||||
|
||||
let w = Arc::new(w);
|
||||
let res = typlite::Typlite::new(w.clone())
|
||||
.with_feature(feat)
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ typst.workspace = true
|
|||
typst-svg.workspace = true
|
||||
typst-syntax.workspace = true
|
||||
typst-html.workspace = true
|
||||
log.workspace = true
|
||||
env_logger = { workspace = true, optional = true }
|
||||
|
||||
# Feature: docx
|
||||
docx-rs = { workspace = true, optional = true }
|
||||
|
|
@ -51,7 +53,7 @@ clap = ["dep:clap"]
|
|||
# Note: this is the feature for typlite as a CLI, not for others.
|
||||
# `docx` is enabled in CLI mode, but not in library mode.
|
||||
# `fonts` is enabled in CLI mode.
|
||||
cli = ["clap", "clap/wrap_help", "docx", "fonts", "system"]
|
||||
cli = ["clap", "clap/wrap_help", "docx", "env_logger", "fonts", "system"]
|
||||
no-content-hint = ["tinymist-project/no-content-hint"]
|
||||
docx = ["docx-rs", "image", "resvg"]
|
||||
|
||||
|
|
|
|||
49
crates/typlite/src/diagnostics.rs
Normal file
49
crates/typlite/src/diagnostics.rs
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use log::warn;
|
||||
use tinymist_project::diag::print_diagnostics_to_string;
|
||||
use tinymist_project::{DiagnosticFormat, SourceWorld};
|
||||
use typst::diag::SourceDiagnostic;
|
||||
|
||||
/// Shared collector for Typst warnings emitted during conversion.
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct WarningCollector {
|
||||
inner: Arc<Mutex<Vec<SourceDiagnostic>>>,
|
||||
}
|
||||
|
||||
impl WarningCollector {
|
||||
/// Extend the collector with multiple warnings.
|
||||
pub fn extend<I>(&self, warnings: I)
|
||||
where
|
||||
I: IntoIterator<Item = SourceDiagnostic>,
|
||||
{
|
||||
let mut guard = self.inner.lock().expect("warning collector poisoned");
|
||||
guard.extend(warnings);
|
||||
}
|
||||
|
||||
/// Clone all collected warnings into a standalone vector.
|
||||
pub fn snapshot(&self) -> Vec<SourceDiagnostic> {
|
||||
let guard = self.inner.lock().expect("warning collector poisoned");
|
||||
guard.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// Render warnings into a human-readable string for the CLI.
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn render_warnings<'a>(
|
||||
world: &dyn SourceWorld,
|
||||
warnings: impl IntoIterator<Item = &'a SourceDiagnostic>,
|
||||
) -> Option<String> {
|
||||
let warnings: Vec<&SourceDiagnostic> = warnings.into_iter().collect();
|
||||
if warnings.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
match print_diagnostics_to_string(world, warnings.into_iter(), DiagnosticFormat::Human) {
|
||||
Ok(message) => Some(message.to_string()),
|
||||
Err(err) => {
|
||||
warn!("failed to render Typst warnings: {err}");
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ input_file: crates/typlite/src/fixtures/docs/nest_list.typ
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body><m1document><p>These again are dictionaries with the keys</p><ul><li><span><m1raw lang="" block="false" text="description"></m1raw></span> (optional): The description for the argument.</li></ul><m1parbreak></m1parbreak><p>See show-module() for outputting the results of this function.</p><m1parbreak></m1parbreak><ul><li>name (string): The name for the module.</li><li><p>label-prefix (auto, string): The label-prefix for internal function references. If <span><m1raw lang="" block="false" text="auto"></m1raw></span>, the label-prefix name will be the module name.</p><ul><li>nested something</li><li>nested something 2</li></ul></li></ul><p>-> string</p></m1document></body>
|
||||
<body><m1document><p>These again are dictionaries with the keys</p><ul><li><span><m1raw lang="" block="false" text="description"><code>description</code></m1raw></span> (optional): The description for the argument.</li></ul><m1parbreak></m1parbreak><p>See show-module() for outputting the results of this function.</p><m1parbreak></m1parbreak><ul><li>name (string): The name for the module.</li><li><p>label-prefix (auto, string): The label-prefix for internal function references. If <span><m1raw lang="" block="false" text="auto"><code>auto</code></m1raw></span>, the label-prefix name will be the module name.</p><ul><li>nested something</li><li>nested something 2</li></ul></li></ul><p>-> string</p></m1document></body>
|
||||
</html>
|
||||
|
||||
=====
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
source: crates/typlite/src/tests.rs
|
||||
expression: "conv(world, ConvKind::Md { for_docs: true })"
|
||||
input_file: crates/typlite/src/fixtures/docs/tidy.typ
|
||||
snapshot_kind: text
|
||||
---
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
|
@ -10,7 +9,7 @@ snapshot_kind: text
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body><m1document><p>These again are dictionaries with the keys</p><ul><li><span><m1raw lang="" block="false" text="description"></m1raw></span> (optional): The description for the argument.</li><li><span><m1raw lang="" block="false" text="types"></m1raw></span> (optional): A list of accepted argument types.</li><li><span><m1raw lang="" block="false" text="default"></m1raw></span> (optional): Default value for this argument.</li></ul><m1parbreak></m1parbreak><p>See show-module() for outputting the results of this function.</p><m1parbreak></m1parbreak><ul><li>content (string): Content of <span><m1raw lang="" block="false" text=".typ"></m1raw></span> file to analyze for docstrings.</li><li>name (string): The name for the module.</li><li>label-prefix (auto, string): The label-prefix for internal function references. If <span><m1raw lang="" block="false" text="auto"></m1raw></span>, the label-prefix name will be the module name.</li><li>require-all-parameters (boolean): Require that all parameters of a functions are documented and fail if some are not.</li><li>scope (dictionary): A dictionary of definitions that are then available in all function and parameter descriptions.</li><li>preamble (string): Code to prepend to all code snippets shown with <span><m1raw lang="" block="false" text="#example()"></m1raw></span>. This can for instance be used to import something from the scope.</li></ul><p>-> string</p></m1document></body>
|
||||
<body><m1document><p>These again are dictionaries with the keys</p><ul><li><span><m1raw lang="" block="false" text="description"><code>description</code></m1raw></span> (optional): The description for the argument.</li><li><span><m1raw lang="" block="false" text="types"><code>types</code></m1raw></span> (optional): A list of accepted argument types.</li><li><span><m1raw lang="" block="false" text="default"><code>default</code></m1raw></span> (optional): Default value for this argument.</li></ul><m1parbreak></m1parbreak><p>See show-module() for outputting the results of this function.</p><m1parbreak></m1parbreak><ul><li>content (string): Content of <span><m1raw lang="" block="false" text=".typ"><code>.typ</code></m1raw></span> file to analyze for docstrings.</li><li>name (string): The name for the module.</li><li>label-prefix (auto, string): The label-prefix for internal function references. If <span><m1raw lang="" block="false" text="auto"><code>auto</code></m1raw></span>, the label-prefix name will be the module name.</li><li>require-all-parameters (boolean): Require that all parameters of a functions are documented and fail if some are not.</li><li>scope (dictionary): A dictionary of definitions that are then available in all function and parameter descriptions.</li><li>preamble (string): Code to prepend to all code snippets shown with <span><m1raw lang="" block="false" text="#example()"><code>#example()</code></m1raw></span>. This can for instance be used to import something from the scope.</li></ul><p>-> string</p></m1document></body>
|
||||
</html>
|
||||
|
||||
=====
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ input_file: crates/typlite/src/fixtures/integration/figure_raw.typ
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body><m1document><m1figure caption=""><m1raw lang="md" block="true" text="markdown"></m1raw></m1figure></m1document></body>
|
||||
<body><m1document><m1figure caption=""><m1raw lang="md" block="true" text="markdown"><pre>markdown</pre></m1raw></m1figure></m1document></body>
|
||||
</html>
|
||||
|
||||
=====
|
||||
|
|
|
|||
|
|
@ -10,11 +10,10 @@ input_file: crates/typlite/src/fixtures/integration/issue-1845.typ
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body><m1document><m1grid><m1table><table><tr><td>Header</td><td>Row</td></tr><tr><td><m1raw lang="" block="true" text="Code line 1
|
||||
Code line 2"></m1raw></td><td>Regular text</td></tr></table></m1table></m1grid></m1document></body>
|
||||
Code line 2"><pre><p>Code line 1</p><m1linebreak></m1linebreak><p>Code line 2</p></pre></m1raw></td><td>Regular text</td></tr></table></m1table></m1grid></m1document></body>
|
||||
</html>
|
||||
|
||||
=====
|
||||
<!-- typlite warning: block content detected inside table cell; exported original HTML table -->
|
||||
<table>
|
||||
|
||||
<tr>
|
||||
|
|
@ -36,7 +35,23 @@ Row
|
|||
<m1raw lang="" block="true" text="Code line 1
|
||||
Code line 2">
|
||||
|
||||
<pre>
|
||||
|
||||
<p>
|
||||
|
||||
Code line 1
|
||||
|
||||
</p><m1linebreak>
|
||||
|
||||
|
||||
|
||||
</m1linebreak><p>
|
||||
|
||||
Code line 2
|
||||
|
||||
</p>
|
||||
|
||||
</pre>
|
||||
|
||||
</m1raw>
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ input_file: crates/typlite/src/fixtures/integration/outline.typ
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body><m1document><m1outline><m1heading level="1"><span style="display: inline-block;">Contents</span></m1heading><m1outentry level="2"><m1heading level="2"><span style="display: inline-block;">Heading 1</span></m1heading></m1outentry><m1outentry level="3"><m1heading level="3"><span style="display: inline-block;">Heading 2</span></m1heading></m1outentry></m1outline><m1parbreak></m1parbreak><m1heading level="2"><span style="display: inline-block;">Heading 1</span></m1heading><m1parbreak></m1parbreak><m1heading level="3"><span style="display: inline-block;">Heading 2</span></m1heading><m1parbreak></m1parbreak><p><span><m1link dest="https://example.com">This is a link to example.com</m1link></span></p><m1parbreak></m1parbreak><p>Inline <span><m1raw lang="" block="false" text="code"></m1raw></span> has <span><m1raw lang="" block="false" text="back-ticks around"></m1raw></span> it.</p><m1parbreak></m1parbreak><m1raw lang="cs" block="true" text="using System.IO.Compression;
|
||||
<body><m1document><m1outline><m1heading level="1"><span style="display: inline-block;">Contents</span></m1heading><m1outentry level="2"><m1heading level="2"><span style="display: inline-block;">Heading 1</span></m1heading></m1outentry><m1outentry level="3"><m1heading level="3"><span style="display: inline-block;">Heading 2</span></m1heading></m1outentry></m1outline><m1parbreak></m1parbreak><m1heading level="2"><span style="display: inline-block;">Heading 1</span></m1heading><m1parbreak></m1parbreak><m1heading level="3"><span style="display: inline-block;">Heading 2</span></m1heading><m1parbreak></m1parbreak><p><span><m1link dest="https://example.com">This is a link to example.com</m1link></span></p><m1parbreak></m1parbreak><p>Inline <span><m1raw lang="" block="false" text="code"><code>code</code></m1raw></span> has <span><m1raw lang="" block="false" text="back-ticks around"><code>back-ticks around</code></m1raw></span> it.</p><m1parbreak></m1parbreak><m1raw lang="cs" block="true" text="using System.IO.Compression;
|
||||
|
||||
#pragma warning disable 414, 3021
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ namespace MyApplication
|
|||
return new List<int>(new int[] { 1, 2, 3 })
|
||||
}
|
||||
}
|
||||
}"></m1raw><m1parbreak></m1parbreak><p>Math inline:</p><m1eqinline>redacted-frame</m1eqinline><p>and block:</p><m1eqblock>redacted-frame</m1eqblock><m1parbreak></m1parbreak><ul><li>First item</li><li><p>Second item</p><ol><li>First sub-item</li><li><p>Second sub-item</p><ul><li>First sub-sub-item</li></ul></li></ol></li></ul><m1parbreak></m1parbreak><dl><dt>First term</dt><dd>First definition</dd></dl><m1parbreak></m1parbreak><m1table><table><tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td><td>10</td><td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td><td>19</td></tr></table></m1table></m1document></body>
|
||||
}"><pre><p>using System.IO.Compression;</p><m1linebreak></m1linebreak><m1linebreak></m1linebreak><p>#pragma warning disable 414, 3021</p><m1linebreak></m1linebreak><m1linebreak></m1linebreak><p>namespace MyApplication</p><m1linebreak></m1linebreak><p>{</p><m1linebreak></m1linebreak><p> [Obsolete("...")]</p><m1linebreak></m1linebreak><p> class Program : IInterface</p><m1linebreak></m1linebreak><p> {</p><m1linebreak></m1linebreak><p> public static List<int> JustDoIt(int count)</p><m1linebreak></m1linebreak><p> {</p><m1linebreak></m1linebreak><p> Console.WriteLine($"Hello {Name}!");</p><m1linebreak></m1linebreak><p> return new List<int>(new int[] { 1, 2, 3 })</p><m1linebreak></m1linebreak><p> }</p><m1linebreak></m1linebreak><p> }</p><m1linebreak></m1linebreak><p>}</p></pre></m1raw><m1parbreak></m1parbreak><p>Math inline:</p><m1eqinline>redacted-frame</m1eqinline><p>and block:</p><m1eqblock>redacted-frame</m1eqblock><m1parbreak></m1parbreak><ul><li>First item</li><li><p>Second item</p><ol><li>First sub-item</li><li><p>Second sub-item</p><ul><li>First sub-sub-item</li></ul></li></ol></li></ul><m1parbreak></m1parbreak><dl><dt>First term</dt><dd>First definition</dd></dl><m1parbreak></m1parbreak><m1table><table><tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td><td>10</td><td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td><td>19</td></tr></table></m1table></m1document></body>
|
||||
</html>
|
||||
|
||||
=====
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
source: crates/typlite/src/tests.rs
|
||||
expression: "conv(world, false)"
|
||||
expression: "conv(world, ConvKind::Md { for_docs: false })"
|
||||
input_file: crates/typlite/src/fixtures/integration/raw_inline.typ
|
||||
---
|
||||
<!DOCTYPE html>
|
||||
|
|
@ -9,7 +9,7 @@ input_file: crates/typlite/src/fixtures/integration/raw_inline.typ
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body><m1document>Some inlined raw <span><m1raw lang="" block="false" text="a"></m1raw></span>, <span><m1raw lang="c" block="false" text="b"></m1raw></span></m1document></body>
|
||||
<body><m1document>Some inlined raw <span><m1raw lang="" block="false" text="a"><code>a</code></m1raw></span>, <span><m1raw lang="c" block="false" text="b"><code>b</code></m1raw></span></m1document></body>
|
||||
</html>
|
||||
|
||||
=====
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ input_file: crates/typlite/src/fixtures/integration/figure_raw.typ
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body><m1document><m1figure caption=""><m1raw lang="md" block="true" text="markdown"></m1raw></m1figure></m1document></body>
|
||||
<body><m1document><m1figure caption=""><m1raw lang="md" block="true" text="markdown"><pre>markdown</pre></m1raw></m1figure></m1document></body>
|
||||
</html>
|
||||
|
||||
=====
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ input_file: crates/typlite/src/fixtures/integration/issue-1845.typ
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body><m1document><m1grid><m1table><table><tr><td>Header</td><td>Row</td></tr><tr><td><m1raw lang="" block="true" text="Code line 1
|
||||
Code line 2"></m1raw></td><td>Regular text</td></tr></table></m1table></m1grid></m1document></body>
|
||||
Code line 2"><pre><p>Code line 1</p><m1linebreak></m1linebreak><p>Code line 2</p></pre></m1raw></td><td>Regular text</td></tr></table></m1table></m1grid></m1document></body>
|
||||
</html>
|
||||
|
||||
=====
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ input_file: crates/typlite/src/fixtures/integration/outline.typ
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body><m1document><m1outline><m1heading level="1"><span style="display: inline-block;">Contents</span></m1heading><m1outentry level="2"><m1heading level="2"><span style="display: inline-block;">Heading 1</span></m1heading></m1outentry><m1outentry level="3"><m1heading level="3"><span style="display: inline-block;">Heading 2</span></m1heading></m1outentry></m1outline><m1parbreak></m1parbreak><m1heading level="2"><span style="display: inline-block;">Heading 1</span></m1heading><m1parbreak></m1parbreak><m1heading level="3"><span style="display: inline-block;">Heading 2</span></m1heading><m1parbreak></m1parbreak><p><span><m1link dest="https://example.com">This is a link to example.com</m1link></span></p><m1parbreak></m1parbreak><p>Inline <span><m1raw lang="" block="false" text="code"></m1raw></span> has <span><m1raw lang="" block="false" text="back-ticks around"></m1raw></span> it.</p><m1parbreak></m1parbreak><m1raw lang="cs" block="true" text="using System.IO.Compression;
|
||||
<body><m1document><m1outline><m1heading level="1"><span style="display: inline-block;">Contents</span></m1heading><m1outentry level="2"><m1heading level="2"><span style="display: inline-block;">Heading 1</span></m1heading></m1outentry><m1outentry level="3"><m1heading level="3"><span style="display: inline-block;">Heading 2</span></m1heading></m1outentry></m1outline><m1parbreak></m1parbreak><m1heading level="2"><span style="display: inline-block;">Heading 1</span></m1heading><m1parbreak></m1parbreak><m1heading level="3"><span style="display: inline-block;">Heading 2</span></m1heading><m1parbreak></m1parbreak><p><span><m1link dest="https://example.com">This is a link to example.com</m1link></span></p><m1parbreak></m1parbreak><p>Inline <span><m1raw lang="" block="false" text="code"><code>code</code></m1raw></span> has <span><m1raw lang="" block="false" text="back-ticks around"><code>back-ticks around</code></m1raw></span> it.</p><m1parbreak></m1parbreak><m1raw lang="cs" block="true" text="using System.IO.Compression;
|
||||
|
||||
#pragma warning disable 414, 3021
|
||||
|
||||
|
|
@ -24,7 +24,7 @@ namespace MyApplication
|
|||
return new List<int>(new int[] { 1, 2, 3 })
|
||||
}
|
||||
}
|
||||
}"></m1raw><m1parbreak></m1parbreak><p>Math inline:</p><m1eqinline>redacted-frame</m1eqinline><p>and block:</p><m1eqblock>redacted-frame</m1eqblock><m1parbreak></m1parbreak><ul><li>First item</li><li><p>Second item</p><ol><li>First sub-item</li><li><p>Second sub-item</p><ul><li>First sub-sub-item</li></ul></li></ol></li></ul><m1parbreak></m1parbreak><dl><dt>First term</dt><dd>First definition</dd></dl><m1parbreak></m1parbreak><m1table><table><tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td><td>10</td><td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td><td>19</td></tr></table></m1table></m1document></body>
|
||||
}"><pre><p>using System.IO.Compression;</p><m1linebreak></m1linebreak><m1linebreak></m1linebreak><p>#pragma warning disable 414, 3021</p><m1linebreak></m1linebreak><m1linebreak></m1linebreak><p>namespace MyApplication</p><m1linebreak></m1linebreak><p>{</p><m1linebreak></m1linebreak><p> [Obsolete("...")]</p><m1linebreak></m1linebreak><p> class Program : IInterface</p><m1linebreak></m1linebreak><p> {</p><m1linebreak></m1linebreak><p> public static List<int> JustDoIt(int count)</p><m1linebreak></m1linebreak><p> {</p><m1linebreak></m1linebreak><p> Console.WriteLine($"Hello {Name}!");</p><m1linebreak></m1linebreak><p> return new List<int>(new int[] { 1, 2, 3 })</p><m1linebreak></m1linebreak><p> }</p><m1linebreak></m1linebreak><p> }</p><m1linebreak></m1linebreak><p>}</p></pre></m1raw><m1parbreak></m1parbreak><p>Math inline:</p><m1eqinline>redacted-frame</m1eqinline><p>and block:</p><m1eqblock>redacted-frame</m1eqblock><m1parbreak></m1parbreak><ul><li>First item</li><li><p>Second item</p><ol><li>First sub-item</li><li><p>Second sub-item</p><ul><li>First sub-sub-item</li></ul></li></ol></li></ul><m1parbreak></m1parbreak><dl><dt>First term</dt><dd>First definition</dd></dl><m1parbreak></m1parbreak><m1table><table><tr><td>0</td><td>1</td><td>2</td><td>3</td><td>4</td><td>5</td><td>6</td><td>7</td><td>8</td><td>9</td><td>10</td><td>11</td><td>12</td><td>13</td><td>14</td><td>15</td><td>16</td><td>17</td><td>18</td><td>19</td></tr></table></m1table></m1document></body>
|
||||
</html>
|
||||
|
||||
=====
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ input_file: crates/typlite/src/fixtures/integration/raw_inline.typ
|
|||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
</head>
|
||||
<body><m1document>Some inlined raw <span><m1raw lang="" block="false" text="a"></m1raw></span>, <span><m1raw lang="c" block="false" text="b"></m1raw></span></m1document></body>
|
||||
<body><m1document>Some inlined raw <span><m1raw lang="" block="false" text="a"><code>a</code></m1raw></span>, <span><m1raw lang="c" block="false" text="b"><code>b</code></m1raw></span></m1document></body>
|
||||
</html>
|
||||
|
||||
=====
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
pub mod attributes;
|
||||
pub mod common;
|
||||
mod diagnostics;
|
||||
mod error;
|
||||
pub mod parser;
|
||||
pub mod tags;
|
||||
|
|
@ -22,11 +23,15 @@ use tinymist_project::vfs::WorkspaceResolver;
|
|||
use tinymist_project::{EntryReader, LspWorld, TaskInputs};
|
||||
use tinymist_std::error::prelude::*;
|
||||
use typst::World;
|
||||
use typst::WorldExt;
|
||||
use typst::diag::SourceDiagnostic;
|
||||
use typst::foundations::Bytes;
|
||||
use typst::html::HtmlDocument;
|
||||
use typst_syntax::Span;
|
||||
use typst_syntax::VirtualPath;
|
||||
|
||||
pub use crate::common::Format;
|
||||
use crate::diagnostics::WarningCollector;
|
||||
use crate::parser::HtmlToAstParser;
|
||||
use crate::writer::WriterFactory;
|
||||
use typst_syntax::FileId;
|
||||
|
|
@ -47,6 +52,7 @@ pub struct MarkdownDocument {
|
|||
world: Arc<LspWorld>,
|
||||
feat: TypliteFeat,
|
||||
ast: Option<Node>,
|
||||
warnings: WarningCollector,
|
||||
}
|
||||
|
||||
impl MarkdownDocument {
|
||||
|
|
@ -57,6 +63,7 @@ impl MarkdownDocument {
|
|||
world,
|
||||
feat,
|
||||
ast: None,
|
||||
warnings: WarningCollector::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -72,15 +79,69 @@ impl MarkdownDocument {
|
|||
world,
|
||||
feat,
|
||||
ast: Some(ast),
|
||||
warnings: WarningCollector::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace the backing warning collector, preserving shared state with
|
||||
/// other components of the pipeline.
|
||||
pub(crate) fn with_warning_collector(mut self, collector: WarningCollector) -> Self {
|
||||
self.warnings = collector;
|
||||
self
|
||||
}
|
||||
|
||||
/// Get a snapshot of all collected warnings so far.
|
||||
pub fn warnings(&self) -> Vec<SourceDiagnostic> {
|
||||
let warnings = self.warnings.snapshot();
|
||||
if let Some(info) = &self.feat.wrap_info {
|
||||
warnings
|
||||
.into_iter()
|
||||
.filter_map(|diag| self.remap_diagnostic(diag, info))
|
||||
.collect()
|
||||
} else {
|
||||
warnings
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal accessor for sharing the collector with the parser.
|
||||
fn warning_collector(&self) -> WarningCollector {
|
||||
self.warnings.clone()
|
||||
}
|
||||
|
||||
fn remap_diagnostic(
|
||||
&self,
|
||||
mut diagnostic: SourceDiagnostic,
|
||||
info: &WrapInfo,
|
||||
) -> Option<SourceDiagnostic> {
|
||||
if let Some(span) = info.remap_span(self.world.as_ref(), diagnostic.span) {
|
||||
diagnostic.span = span;
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
|
||||
diagnostic.trace = diagnostic
|
||||
.trace
|
||||
.into_iter()
|
||||
.filter_map(
|
||||
|mut spanned| match info.remap_span(self.world.as_ref(), spanned.span) {
|
||||
Some(span) => {
|
||||
spanned.span = span;
|
||||
Some(spanned)
|
||||
}
|
||||
None => None,
|
||||
},
|
||||
)
|
||||
.collect();
|
||||
|
||||
Some(diagnostic)
|
||||
}
|
||||
|
||||
/// Parse HTML document to AST
|
||||
pub fn parse(&self) -> tinymist_std::Result<Node> {
|
||||
if let Some(ast) = &self.ast {
|
||||
return Ok(ast.clone());
|
||||
}
|
||||
let parser = HtmlToAstParser::new(self.feat.clone(), &self.world);
|
||||
let parser = HtmlToAstParser::new(self.feat.clone(), &self.world, self.warning_collector());
|
||||
parser.parse(&self.base.root).context_ut("failed to parse")
|
||||
}
|
||||
|
||||
|
|
@ -141,6 +202,38 @@ pub enum ColorTheme {
|
|||
Dark,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct WrapInfo {
|
||||
/// The synthetic wrapper file that hosts the original Typst source.
|
||||
pub wrap_file_id: FileId,
|
||||
/// The user's actual Typst source file.
|
||||
pub original_file_id: FileId,
|
||||
/// Number of UTF-8 bytes injected ahead of the original source.
|
||||
pub prefix_len_bytes: usize,
|
||||
}
|
||||
|
||||
impl WrapInfo {
|
||||
/// Translate a span from the wrapper file back into the original file.
|
||||
pub fn remap_span(&self, world: &dyn typst::World, span: Span) -> Option<Span> {
|
||||
if span.id() != Some(self.wrap_file_id) {
|
||||
return Some(span);
|
||||
}
|
||||
|
||||
let range = world.range(span)?;
|
||||
let start = range.start.checked_sub(self.prefix_len_bytes)?;
|
||||
let end = range.end.checked_sub(self.prefix_len_bytes)?;
|
||||
|
||||
let original_source = world.source(self.original_file_id).ok()?;
|
||||
let original_len = original_source.len_bytes();
|
||||
|
||||
if start >= original_len || end > original_len {
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(Span::from_range(self.original_file_id, start..end))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct TypliteFeat {
|
||||
/// The preferred color theme.
|
||||
|
|
@ -178,6 +271,8 @@ pub struct TypliteFeat {
|
|||
/// It resembles the regular typst show rule function, like `#show:
|
||||
/// article`.
|
||||
pub processor: Option<String>,
|
||||
/// Optional mapping from the wrapper file back to the original source.
|
||||
pub wrap_info: Option<WrapInfo>,
|
||||
}
|
||||
|
||||
impl TypliteFeat {
|
||||
|
|
@ -185,7 +280,7 @@ impl TypliteFeat {
|
|||
&self,
|
||||
world: &LspWorld,
|
||||
format: Format,
|
||||
) -> tinymist_std::Result<LspWorld> {
|
||||
) -> tinymist_std::Result<(LspWorld, Option<WrapInfo>)> {
|
||||
let entry = world.entry_state();
|
||||
let main = entry.main();
|
||||
let current = main.context("no main file in workspace")?;
|
||||
|
|
@ -242,19 +337,18 @@ impl TypliteFeat {
|
|||
Bytes::from_string(include_str!("markdown.typ")),
|
||||
)
|
||||
.context_ut("cannot map markdown.typ")?;
|
||||
let original_source = world
|
||||
.source(current)
|
||||
.context_ut("cannot fetch main source")?
|
||||
.text()
|
||||
.to_owned();
|
||||
|
||||
const WRAP_PREFIX: &str =
|
||||
"#import \"@local/_markdown:0.1.0\": md-doc, example; #show: md-doc\n";
|
||||
let wrap_content = format!("{WRAP_PREFIX}{original_source}");
|
||||
|
||||
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()
|
||||
)),
|
||||
)
|
||||
.map_shadow_by_id(wrap_main_id, Bytes::from_string(wrap_content))
|
||||
.context_ut("cannot map source for main file")?;
|
||||
|
||||
if let Some(main_content) = main_content {
|
||||
|
|
@ -263,7 +357,13 @@ impl TypliteFeat {
|
|||
.context_ut("cannot map source for main file")?;
|
||||
}
|
||||
|
||||
Ok(world)
|
||||
let wrap_info = Some(WrapInfo {
|
||||
wrap_file_id: wrap_main_id,
|
||||
original_file_id: current,
|
||||
prefix_len_bytes: WRAP_PREFIX.len(),
|
||||
});
|
||||
|
||||
Ok((world, wrap_info))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -319,9 +419,11 @@ impl Typlite {
|
|||
}
|
||||
|
||||
/// Convert the content to a markdown document.
|
||||
pub fn convert_doc(self, format: Format) -> tinymist_std::Result<MarkdownDocument> {
|
||||
let world = Arc::new(self.feat.prepare_world(&self.world, format)?);
|
||||
pub fn convert_doc(mut self, format: Format) -> tinymist_std::Result<MarkdownDocument> {
|
||||
let (prepared_world, wrap_info) = self.feat.prepare_world(&self.world, format)?;
|
||||
self.feat.wrap_info = wrap_info;
|
||||
let feat = self.feat.clone();
|
||||
let world = Arc::new(prepared_world);
|
||||
Self::convert_doc_prepared(feat, format, world)
|
||||
}
|
||||
|
||||
|
|
@ -331,11 +433,22 @@ impl Typlite {
|
|||
format: Format,
|
||||
world: Arc<LspWorld>,
|
||||
) -> tinymist_std::Result<MarkdownDocument> {
|
||||
// todo: ignoring warnings
|
||||
let base = typst::compile(&world).output?;
|
||||
let compiled = typst::compile(&world);
|
||||
let collector = WarningCollector::default();
|
||||
collector.extend(
|
||||
compiled
|
||||
.warnings
|
||||
.iter()
|
||||
.filter(|&diag| {
|
||||
diag.message.as_str()
|
||||
!= "html export is under active development and incomplete"
|
||||
})
|
||||
.cloned(),
|
||||
);
|
||||
let base = compiled.output?;
|
||||
let mut feat = feat;
|
||||
feat.target = format;
|
||||
Ok(MarkdownDocument::new(base, world.clone(), feat))
|
||||
Ok(MarkdownDocument::new(base, world.clone(), feat).with_warning_collector(collector))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ pub struct CompileArgs {
|
|||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let _ = env_logger::try_init();
|
||||
// Parse command line arguments
|
||||
let args = CompileArgs::parse();
|
||||
|
||||
|
|
@ -103,6 +104,8 @@ fn run(args: CompileArgs, world: Arc<LspWorld>) -> Result<()> {
|
|||
Format::Docx => Bytes::new(doc.to_docx()?),
|
||||
};
|
||||
|
||||
let warnings = doc.warnings();
|
||||
|
||||
if is_stdout {
|
||||
std::io::stdout()
|
||||
.write_all(result.as_slice())
|
||||
|
|
@ -111,6 +114,11 @@ fn run(args: CompileArgs, world: Arc<LspWorld>) -> Result<()> {
|
|||
bail!("failed to write file {output_path:?}: {err}");
|
||||
}
|
||||
|
||||
if !warnings.is_empty() {
|
||||
print_diagnostics(world.as_ref(), warnings.iter(), DiagnosticFormat::Human)
|
||||
.context_ut("print warnings")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
#let md-emph(body) = html.elem("span", html.elem("m1emph", body))
|
||||
#let md-highlight(body) = html.elem("span", html.elem("m1highlight", body))
|
||||
#let md-strike(body) = html.elem("span", html.elem("m1strike", body))
|
||||
#let md-raw(lang: none, block: false, text) = {
|
||||
#let md-raw(lang: none, block: false, text: "", body) = {
|
||||
let body = html.elem(
|
||||
"m1raw",
|
||||
attrs: (
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
block: bool-str(block),
|
||||
text: text,
|
||||
),
|
||||
"",
|
||||
body,
|
||||
)
|
||||
|
||||
if block {
|
||||
|
|
@ -190,7 +190,7 @@
|
|||
// todo: icc?
|
||||
show image: it => if-not-paged(it, md-image(src: it.source, alt: it.alt))
|
||||
|
||||
show raw: it => if-not-paged(it, md-raw(lang: it.lang, block: it.block, it.text))
|
||||
show raw: it => if-not-paged(it, md-raw(lang: it.lang, block: it.block, text: it.text, it))
|
||||
show link: it => if-not-paged(it, md-link(dest: it.dest, it.body))
|
||||
show ref: it => if-not-paged(it, md-ref(it))
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
use std::sync::Arc;
|
||||
|
||||
use typst::diag::SourceDiagnostic;
|
||||
use typst_syntax::Span;
|
||||
|
||||
use cmark_writer::WriteResult;
|
||||
use cmark_writer::ast::{CustomNode, HtmlAttribute, HtmlElement as CmarkHtmlElement, Node};
|
||||
use cmark_writer::writer::InlineWriterProxy;
|
||||
|
|
@ -13,6 +16,7 @@ use crate::Result;
|
|||
use crate::TypliteFeat;
|
||||
use crate::attributes::{AlertsAttr, HeadingAttr, RawAttr, TypliteAttrsParser, md_attr};
|
||||
use crate::common::{AlertNode, CenterNode, VerbatimNode};
|
||||
use crate::diagnostics::WarningCollector;
|
||||
use crate::tags::md_tag;
|
||||
|
||||
use super::{list::ListParser, table::TableParser};
|
||||
|
|
@ -25,10 +29,15 @@ pub struct HtmlToAstParser {
|
|||
pub list_level: usize,
|
||||
pub blocks: Vec<Node>,
|
||||
pub inline_buffer: Vec<Node>,
|
||||
pub(crate) warnings: WarningCollector,
|
||||
}
|
||||
|
||||
impl HtmlToAstParser {
|
||||
pub fn new(feat: TypliteFeat, world: &Arc<LspWorld>) -> Self {
|
||||
pub(crate) fn new(
|
||||
feat: TypliteFeat,
|
||||
world: &Arc<LspWorld>,
|
||||
warnings: WarningCollector,
|
||||
) -> Self {
|
||||
Self {
|
||||
feat,
|
||||
world: world.clone(),
|
||||
|
|
@ -36,6 +45,7 @@ impl HtmlToAstParser {
|
|||
list_level: 0,
|
||||
blocks: Vec::new(),
|
||||
inline_buffer: Vec::new(),
|
||||
warnings,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -197,6 +207,12 @@ impl HtmlToAstParser {
|
|||
let tag_name = element.tag.resolve().to_string();
|
||||
|
||||
if !tag_name.starts_with("m1") {
|
||||
// self.warn_at(
|
||||
// Some(element.span),
|
||||
// eco_format!(
|
||||
// "unsupported HTML element `<{tag_name}>`; exported as raw HTML"
|
||||
// ),
|
||||
// );
|
||||
let html_element = self.create_html_element(element)?;
|
||||
self.inline_buffer.push(html_element);
|
||||
} else {
|
||||
|
|
@ -290,6 +306,23 @@ impl HtmlToAstParser {
|
|||
|
||||
Ok((inline, blocks))
|
||||
}
|
||||
|
||||
pub(crate) fn warn_at(&mut self, span: Option<Span>, message: EcoString) {
|
||||
let span = span.unwrap_or_else(Span::detached);
|
||||
let span = self
|
||||
.feat
|
||||
.wrap_info
|
||||
.as_ref()
|
||||
.and_then(|info| self.remap_span_from_wrapper(span, info))
|
||||
.unwrap_or(span);
|
||||
|
||||
let diag = SourceDiagnostic::warning(span, message);
|
||||
self.warnings.extend(std::iter::once(diag));
|
||||
}
|
||||
|
||||
fn remap_span_from_wrapper(&self, span: Span, info: &crate::WrapInfo) -> Option<Span> {
|
||||
info.remap_span(self.world.as_ref(), span)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ use std::sync::{Arc, LazyLock};
|
|||
use base64::Engine;
|
||||
use cmark_writer::ast::{HtmlAttribute, HtmlElement as CmarkHtmlElement, Node};
|
||||
use ecow::{EcoString, eco_format};
|
||||
use log::debug;
|
||||
use tinymist_project::diag::print_diagnostics_to_string;
|
||||
use tinymist_project::{EntryReader, MEMORY_MAIN_ENTRY, TaskInputs, base::ShadowApi};
|
||||
use typst::{
|
||||
|
|
@ -210,7 +211,7 @@ impl HtmlToAstParser {
|
|||
});
|
||||
|
||||
if self.feat.remove_html {
|
||||
eprintln!("Removing idoc element due to remove_html feature");
|
||||
debug!("remove_html feature active, dropping inline document element");
|
||||
// todo: make error silent is not good.
|
||||
return Node::Text(EcoString::new());
|
||||
}
|
||||
|
|
@ -278,12 +279,12 @@ impl HtmlToAstParser {
|
|||
)
|
||||
.unwrap();
|
||||
|
||||
//todo: ignoring warnings
|
||||
let doc = typst::compile(&world);
|
||||
let doc = match doc.output {
|
||||
let compiled = typst::compile(&world);
|
||||
self.warnings.extend(compiled.warnings.iter().cloned());
|
||||
let doc = match compiled.output {
|
||||
Ok(doc) => doc,
|
||||
Err(e) => {
|
||||
let diag = doc.warnings.iter().chain(e.iter());
|
||||
let diag = compiled.warnings.iter().chain(e.iter());
|
||||
|
||||
let e = print_diagnostics_to_string(
|
||||
&world,
|
||||
|
|
|
|||
|
|
@ -30,6 +30,12 @@ impl TableParser {
|
|||
// Check if the table contains rowspan or colspan attributes
|
||||
// If it does, fall back to using HtmlElement
|
||||
if Self::table_has_complex_cells(table) {
|
||||
parser.warn_at(
|
||||
Some(table.span),
|
||||
eco_format!(
|
||||
"table contains rowspan or colspan attributes; exported original HTML table"
|
||||
),
|
||||
);
|
||||
return parser.create_html_element(table).map(Some);
|
||||
}
|
||||
|
||||
|
|
@ -48,15 +54,8 @@ impl TableParser {
|
|||
)?;
|
||||
|
||||
if fallback_to_html {
|
||||
eprintln!(
|
||||
"[typlite] warning: block content detected inside table cell; exporting original HTML table"
|
||||
);
|
||||
let html =
|
||||
Self::serialize_html_element(parser, table).map_err(|e| e.to_string())?;
|
||||
let html = eco_format!(
|
||||
"<!-- typlite warning: block content detected inside table cell; exported original HTML table -->\n{}",
|
||||
html
|
||||
);
|
||||
return Ok(Some(Node::HtmlBlock(html)));
|
||||
}
|
||||
|
||||
|
|
@ -226,6 +225,12 @@ impl TableParser {
|
|||
let (cell_content, block_content) = parser.capture_children(cell)?;
|
||||
|
||||
if !block_content.is_empty() {
|
||||
parser.warn_at(
|
||||
Some(cell.span),
|
||||
eco_format!(
|
||||
"block content detected inside table cell; exported original HTML table"
|
||||
),
|
||||
);
|
||||
*fallback_to_html = true;
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ use base64::Engine;
|
|||
use cmark_writer::ast::{ListItem, Node};
|
||||
use docx_rs::*;
|
||||
use ecow::EcoString;
|
||||
use log::{debug, warn};
|
||||
use std::fs;
|
||||
use std::io::Cursor;
|
||||
|
||||
|
|
@ -252,11 +253,14 @@ impl DocxWriter {
|
|||
}
|
||||
node if node.is_custom_type::<VerbatimNode>() => {
|
||||
let node = node.as_custom_type::<VerbatimNode>().unwrap();
|
||||
eprintln!("Warning: `m1verbatim` is ignored {:?}.", node.content);
|
||||
warn!(
|
||||
"ignoring `m1verbatim` content in DOCX export: {:?}",
|
||||
node.content
|
||||
);
|
||||
}
|
||||
// Other inline element types
|
||||
_ => {
|
||||
eprintln!("other inline element: {node:?}");
|
||||
debug!("unhandled inline node in DOCX export: {node:?}");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue