Improve error handling: render errors through typst
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
/target
|
/target
|
||||||
/.direnv
|
/.direnv
|
||||||
*.mgc
|
*.mgc
|
||||||
|
*.typ
|
||||||
|
|||||||
27
Cargo.lock
generated
27
Cargo.lock
generated
@@ -309,6 +309,17 @@ dependencies = [
|
|||||||
"thiserror",
|
"thiserror",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "codespan-reporting"
|
||||||
|
version = "0.13.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "af491d569909a7e4dee0ad7db7f5341fef5c614d5b8ec8cf765732aba3cff681"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"termcolor",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "codex"
|
name = "codex"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -2424,6 +2435,15 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termcolor"
|
||||||
|
version = "1.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755"
|
||||||
|
dependencies = [
|
||||||
|
"winapi-util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thin-vec"
|
name = "thin-vec"
|
||||||
version = "0.2.14"
|
version = "0.2.14"
|
||||||
@@ -3082,6 +3102,12 @@ version = "0.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
|
checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unsafe-libyaml"
|
name = "unsafe-libyaml"
|
||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
@@ -3533,6 +3559,7 @@ version = "0.1.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"bindgen",
|
"bindgen",
|
||||||
"cairo-rs",
|
"cairo-rs",
|
||||||
|
"codespan-reporting",
|
||||||
"pkg-config",
|
"pkg-config",
|
||||||
"system-deps",
|
"system-deps",
|
||||||
"typst",
|
"typst",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ typst = "0.14.0"
|
|||||||
typst-render = "0.14.0"
|
typst-render = "0.14.0"
|
||||||
cairo-rs = "0.21.2"
|
cairo-rs = "0.21.2"
|
||||||
typst-kit = { version = "0.14.0", features = ["embed-fonts", "vendor-openssl"] }
|
typst-kit = { version = "0.14.0", features = ["embed-fonts", "vendor-openssl"] }
|
||||||
|
codespan-reporting = "0.13.1"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
bindgen = "0.72.1"
|
bindgen = "0.72.1"
|
||||||
|
|||||||
24
src/lib.rs
24
src/lib.rs
@@ -4,13 +4,11 @@ use std::{
|
|||||||
path::Path,
|
path::Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use ::typst::layout::Page;
|
||||||
use cairo::{Format, ImageSurface};
|
use cairo::{Format, ImageSurface};
|
||||||
|
|
||||||
use crate::zathura::{
|
use crate::zathura::{
|
||||||
ZathuraResult, cairo_t, zathura_document_get_data, zathura_document_get_path,
|
cairo_t, zathura_document_get_data, zathura_document_get_path, zathura_document_s, zathura_document_set_data, zathura_document_set_number_of_pages, zathura_page_get_data, zathura_page_get_document, zathura_page_get_index, zathura_page_set_data, zathura_page_set_height, zathura_page_set_width, zathura_page_t, ZathuraResult
|
||||||
zathura_document_s, zathura_document_set_data, zathura_document_set_number_of_pages,
|
|
||||||
zathura_page_get_document, zathura_page_get_index, zathura_page_set_height,
|
|
||||||
zathura_page_set_width, zathura_page_t,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
mod typst;
|
mod typst;
|
||||||
@@ -61,13 +59,10 @@ unsafe extern "C" fn document_open(doc: *mut zathura_document_s) -> ZathuraResul
|
|||||||
CStr::from_ptr(zathura_document_get_path(doc)).to_bytes(),
|
CStr::from_ptr(zathura_document_get_path(doc)).to_bytes(),
|
||||||
))
|
))
|
||||||
};
|
};
|
||||||
let Ok(document) = TypstDocument::load(path) else {
|
let document = Box::new(TypstDocument::load(path));
|
||||||
return ZathuraResult::InvalidArguments;
|
|
||||||
};
|
|
||||||
let document = Box::new(document);
|
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
zathura_document_set_number_of_pages(doc, document.doc.pages.len() as _);
|
zathura_document_set_number_of_pages(doc, document.doc.pages.len() as u32);
|
||||||
zathura_document_set_data(doc, Box::into_raw(document) as _);
|
zathura_document_set_data(doc, Box::into_raw(document) as _);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,9 +80,11 @@ unsafe extern "C" fn page_init(page: *mut zathura_page_t) -> ZathuraResult {
|
|||||||
let pageno = unsafe { zathura_page_get_index(page) };
|
let pageno = unsafe { zathura_page_get_index(page) };
|
||||||
let doc = unsafe { zathura_page_get_document(page) };
|
let doc = unsafe { zathura_page_get_document(page) };
|
||||||
let typst_doc = unsafe { &mut *(zathura_document_get_data(doc) as *mut TypstDocument) };
|
let typst_doc = unsafe { &mut *(zathura_document_get_data(doc) as *mut TypstDocument) };
|
||||||
let typst_page = &mut typst_doc.doc.pages[pageno as usize];
|
|
||||||
|
let typst_page: &Page = &typst_doc.doc.pages[pageno as usize];
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
|
zathura_page_set_data(page, typst_page as *const _ as _);
|
||||||
zathura_page_set_width(page, typst_page.frame.width().to_pt());
|
zathura_page_set_width(page, typst_page.frame.width().to_pt());
|
||||||
zathura_page_set_height(page, typst_page.frame.height().to_pt());
|
zathura_page_set_height(page, typst_page.frame.height().to_pt());
|
||||||
};
|
};
|
||||||
@@ -105,10 +102,7 @@ unsafe extern "C" fn page_render_cairo(
|
|||||||
cairo: *mut cairo_t,
|
cairo: *mut cairo_t,
|
||||||
_printing: bool,
|
_printing: bool,
|
||||||
) -> ZathuraResult {
|
) -> ZathuraResult {
|
||||||
let pageno = unsafe { zathura_page_get_index(page) };
|
let typst_page: &Page = unsafe { &*(zathura_page_get_data(page) as *mut _)};
|
||||||
let doc = unsafe { zathura_page_get_document(page) };
|
|
||||||
let typst_doc = unsafe { &mut *(zathura_document_get_data(doc) as *mut TypstDocument) };
|
|
||||||
let typst_page = &mut typst_doc.doc.pages[pageno as usize];
|
|
||||||
|
|
||||||
let context = unsafe { cairo::Context::from_raw_none(cairo) };
|
let context = unsafe { cairo::Context::from_raw_none(cairo) };
|
||||||
let surface = context.target();
|
let surface = context.target();
|
||||||
@@ -153,9 +147,9 @@ unsafe extern "C" fn page_render_cairo(
|
|||||||
|
|
||||||
ZathuraResult::OK
|
ZathuraResult::OK
|
||||||
}
|
}
|
||||||
// TODO: handle compile errors
|
|
||||||
// TODO: render warnings
|
// TODO: render warnings
|
||||||
// TODO: text/link/... handling
|
// TODO: text/link/... handling
|
||||||
|
// TODO: better caching
|
||||||
// TODO: nixify compilation of magicdb (file -Cm ~/.magic:/nix/store/vi7ya34k19nid2m0dmkljqip5572g0bi-file-5.45/share/misc/magic.mgc)
|
// TODO: nixify compilation of magicdb (file -Cm ~/.magic:/nix/store/vi7ya34k19nid2m0dmkljqip5572g0bi-file-5.45/share/misc/magic.mgc)
|
||||||
/*
|
/*
|
||||||
0 string/cW /*\ doc:\ typst\ */ typst text document
|
0 string/cW /*\ doc:\ typst\ */ typst text document
|
||||||
|
|||||||
170
src/typst.rs
170
src/typst.rs
@@ -2,15 +2,21 @@ use std::{
|
|||||||
collections::{HashMap, hash_map::Entry},
|
collections::{HashMap, hash_map::Entry},
|
||||||
hash::Hash,
|
hash::Hash,
|
||||||
io::{self, ErrorKind},
|
io::{self, ErrorKind},
|
||||||
|
iter,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::{LazyLock, Mutex},
|
sync::{LazyLock, Mutex},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use codespan_reporting::{
|
||||||
|
diagnostic::{Diagnostic, Label},
|
||||||
|
files::Files,
|
||||||
|
term::{Config, DisplayStyle, emit_to_write_style, termcolor::Ansi},
|
||||||
|
};
|
||||||
use typst::{
|
use typst::{
|
||||||
Library, LibraryExt,
|
Library, LibraryExt, WorldExt,
|
||||||
diag::{FileError, FileResult, SourceDiagnostic, Warned},
|
diag::{FileError, FileResult, SourceDiagnostic, Warned},
|
||||||
foundations::{Bytes, Datetime},
|
foundations::{Bytes, Datetime},
|
||||||
syntax::{FileId, Source, Span, VirtualPath},
|
syntax::{FileId, Lines, Source, Span, VirtualPath},
|
||||||
text::{Font, FontBook},
|
text::{Font, FontBook},
|
||||||
utils::LazyHash,
|
utils::LazyHash,
|
||||||
};
|
};
|
||||||
@@ -120,6 +126,16 @@ impl World {
|
|||||||
file_cache: Default::default(),
|
file_cache: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn new_with_source(name: impl AsRef<Path>, contents: String) -> Self {
|
||||||
|
let main = FileId::new_fake(VirtualPath::new(name));
|
||||||
|
Self {
|
||||||
|
main,
|
||||||
|
root: PathBuf::new(),
|
||||||
|
source_cache: Mutex::new(HashMap::from([(main, Source::new(main, contents.clone()))])),
|
||||||
|
file_cache: Mutex::new(HashMap::from([(main, Bytes::from_string(contents))])),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct TypstDocument {
|
pub struct TypstDocument {
|
||||||
@@ -127,21 +143,153 @@ pub struct TypstDocument {
|
|||||||
pub warnings: Vec<SourceDiagnostic>,
|
pub warnings: Vec<SourceDiagnostic>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TypstDocument {
|
#[allow(clippy::result_large_err)]
|
||||||
pub fn load(path: &Path) -> Result<Self, Vec<SourceDiagnostic>> {
|
fn compile(path: &Path) -> Result<TypstDocument, (Option<World>, Vec<SourceDiagnostic>)> {
|
||||||
let Warned { output, warnings } = typst::compile(
|
let world = World::new(path).map_err(|e| {
|
||||||
&World::new(path)
|
(
|
||||||
.map_err(|e| vec![SourceDiagnostic::error(Span::detached(), e.to_string())])?,
|
None,
|
||||||
);
|
vec![SourceDiagnostic::error(Span::detached(), e.to_string())],
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
let Warned { output, warnings } = typst::compile(&world);
|
||||||
match output {
|
match output {
|
||||||
Ok(doc) => Ok(Self {
|
Ok(doc) => Ok(TypstDocument {
|
||||||
doc,
|
doc,
|
||||||
warnings: warnings.to_vec(),
|
warnings: warnings.to_vec(),
|
||||||
}),
|
}),
|
||||||
Err(mut errors) => {
|
Err(mut errors) => {
|
||||||
errors.extend(warnings);
|
errors.extend(warnings);
|
||||||
Err(errors.to_vec())
|
Err((Some(world), errors.to_vec()))
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TypstDocument {
|
||||||
|
pub fn load(path: &Path) -> Self {
|
||||||
|
let compiled = compile(path);
|
||||||
|
match compiled {
|
||||||
|
Ok(doc) => doc,
|
||||||
|
Err((world, errors)) => diagnostics(
|
||||||
|
&world.unwrap_or_else(|| World::new_with_source("error", String::new())),
|
||||||
|
errors,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Files<'_> for World {
|
||||||
|
type FileId = FileId;
|
||||||
|
|
||||||
|
type Name = String;
|
||||||
|
|
||||||
|
type Source = Lines<String>;
|
||||||
|
|
||||||
|
fn name(&self, id: Self::FileId) -> Result<Self::Name, codespan_reporting::files::Error> {
|
||||||
|
let vpath = id.vpath();
|
||||||
|
Ok(format!(
|
||||||
|
"{}{}",
|
||||||
|
id.package()
|
||||||
|
.map(|p| format!("{}/", p.name))
|
||||||
|
.unwrap_or_default(),
|
||||||
|
vpath.as_rootless_path().display()
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn source(&self, id: Self::FileId) -> Result<Self::Source, codespan_reporting::files::Error> {
|
||||||
|
typst::World::source(self, id)
|
||||||
|
.map(|s| s.lines().clone())
|
||||||
|
.map_err(|_| codespan_reporting::files::Error::FileMissing)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_index(
|
||||||
|
&self,
|
||||||
|
id: Self::FileId,
|
||||||
|
byte_index: usize,
|
||||||
|
) -> Result<usize, codespan_reporting::files::Error> {
|
||||||
|
let source = Files::source(self, id)?;
|
||||||
|
source.byte_to_line(byte_index).ok_or_else(|| {
|
||||||
|
codespan_reporting::files::Error::IndexTooLarge {
|
||||||
|
given: byte_index,
|
||||||
|
max: source.len_bytes(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn line_range(
|
||||||
|
&self,
|
||||||
|
id: Self::FileId,
|
||||||
|
line_index: usize,
|
||||||
|
) -> Result<std::ops::Range<usize>, codespan_reporting::files::Error> {
|
||||||
|
let source = Files::source(self, id)?;
|
||||||
|
source.line_to_range(line_index).ok_or_else(|| {
|
||||||
|
codespan_reporting::files::Error::LineTooLarge {
|
||||||
|
given: line_index,
|
||||||
|
max: source.len_lines(),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn diagnostics(world: &World, errors: impl IntoIterator<Item = SourceDiagnostic>) -> TypstDocument {
|
||||||
|
let config = &Config {
|
||||||
|
display_style: DisplayStyle::Rich,
|
||||||
|
tab_width: 2,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut binaries = HashMap::new();
|
||||||
|
let mut i = 0;
|
||||||
|
let errors = errors
|
||||||
|
.into_iter()
|
||||||
|
.map(|diag| {
|
||||||
|
iter::once(
|
||||||
|
match diag.severity {
|
||||||
|
typst::diag::Severity::Error => Diagnostic::error(),
|
||||||
|
typst::diag::Severity::Warning => Diagnostic::warning(),
|
||||||
|
}
|
||||||
|
.with_message(diag.message)
|
||||||
|
.with_notes(
|
||||||
|
diag.hints
|
||||||
|
.into_iter()
|
||||||
|
.map(|e| format!("hint: {e}"))
|
||||||
|
.collect(),
|
||||||
|
)
|
||||||
|
.with_labels(label(world, diag.span).into_iter().collect()),
|
||||||
|
)
|
||||||
|
.chain(diag.trace.into_iter().map(|point| {
|
||||||
|
Diagnostic::help()
|
||||||
|
.with_message(point.v)
|
||||||
|
.with_labels(label(world, point.span).into_iter().collect())
|
||||||
|
}))
|
||||||
|
.map(|out_diag| {
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
emit_to_write_style(&mut Ansi::new(&mut buf), config, world, &out_diag)
|
||||||
|
.expect("Failed to format diagnostic");
|
||||||
|
i += 1;
|
||||||
|
binaries.insert(
|
||||||
|
FileId::new(None, VirtualPath::new(format!("/error-{i}"))),
|
||||||
|
Bytes::new(buf),
|
||||||
|
);
|
||||||
|
format!("#ansi-render(read(\"error-{i}\"), theme: terminal-themes.one-half-light, font: none)\n")
|
||||||
|
})
|
||||||
|
.collect::<String>()
|
||||||
|
})
|
||||||
|
.map(|s| format!("==\n{s}"))
|
||||||
|
.collect::<String>();
|
||||||
|
|
||||||
|
let world = World::new_with_source("errors.typ", format!("#import \"@preview/ansi-render:0.8.0\": *\n{errors}"));
|
||||||
|
world.file_cache.lock().unwrap().extend(binaries);
|
||||||
|
|
||||||
|
let Warned {
|
||||||
|
output,
|
||||||
|
warnings: _,
|
||||||
|
} = typst::compile(&world);
|
||||||
|
TypstDocument {
|
||||||
|
doc: output.expect("Should be valid syntax"),
|
||||||
|
warnings: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn label(world: &World, span: Span) -> Option<Label<FileId>> {
|
||||||
|
Some(Label::primary(span.id()?, world.range(span)?))
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user