Improve error handling: render errors through typst
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
/target
|
||||
/.direnv
|
||||
*.mgc
|
||||
*.typ
|
||||
|
||||
27
Cargo.lock
generated
27
Cargo.lock
generated
@@ -309,6 +309,17 @@ dependencies = [
|
||||
"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]]
|
||||
name = "codex"
|
||||
version = "0.2.0"
|
||||
@@ -2424,6 +2435,15 @@ dependencies = [
|
||||
"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]]
|
||||
name = "thin-vec"
|
||||
version = "0.2.14"
|
||||
@@ -3082,6 +3102,12 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b1d386ff53b415b7fe27b50bb44679e2cc4660272694b7b6f3326d8480823a94"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b4ac048d71ede7ee76d585517add45da530660ef4390e49b098733c6e897f254"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-libyaml"
|
||||
version = "0.2.11"
|
||||
@@ -3533,6 +3559,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"bindgen",
|
||||
"cairo-rs",
|
||||
"codespan-reporting",
|
||||
"pkg-config",
|
||||
"system-deps",
|
||||
"typst",
|
||||
|
||||
@@ -8,6 +8,7 @@ typst = "0.14.0"
|
||||
typst-render = "0.14.0"
|
||||
cairo-rs = "0.21.2"
|
||||
typst-kit = { version = "0.14.0", features = ["embed-fonts", "vendor-openssl"] }
|
||||
codespan-reporting = "0.13.1"
|
||||
|
||||
[build-dependencies]
|
||||
bindgen = "0.72.1"
|
||||
|
||||
24
src/lib.rs
24
src/lib.rs
@@ -4,13 +4,11 @@ use std::{
|
||||
path::Path,
|
||||
};
|
||||
|
||||
use ::typst::layout::Page;
|
||||
use cairo::{Format, ImageSurface};
|
||||
|
||||
use crate::zathura::{
|
||||
ZathuraResult, 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_document, zathura_page_get_index, zathura_page_set_height,
|
||||
zathura_page_set_width, zathura_page_t,
|
||||
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
|
||||
};
|
||||
|
||||
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(),
|
||||
))
|
||||
};
|
||||
let Ok(document) = TypstDocument::load(path) else {
|
||||
return ZathuraResult::InvalidArguments;
|
||||
};
|
||||
let document = Box::new(document);
|
||||
let document = Box::new(TypstDocument::load(path));
|
||||
|
||||
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 _);
|
||||
}
|
||||
|
||||
@@ -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 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 typst_page: &Page = &typst_doc.doc.pages[pageno as usize];
|
||||
|
||||
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_height(page, typst_page.frame.height().to_pt());
|
||||
};
|
||||
@@ -105,10 +102,7 @@ unsafe extern "C" fn page_render_cairo(
|
||||
cairo: *mut cairo_t,
|
||||
_printing: bool,
|
||||
) -> ZathuraResult {
|
||||
let pageno = unsafe { zathura_page_get_index(page) };
|
||||
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 typst_page: &Page = unsafe { &*(zathura_page_get_data(page) as *mut _)};
|
||||
|
||||
let context = unsafe { cairo::Context::from_raw_none(cairo) };
|
||||
let surface = context.target();
|
||||
@@ -153,9 +147,9 @@ unsafe extern "C" fn page_render_cairo(
|
||||
|
||||
ZathuraResult::OK
|
||||
}
|
||||
// TODO: handle compile errors
|
||||
// TODO: render warnings
|
||||
// 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)
|
||||
/*
|
||||
0 string/cW /*\ doc:\ typst\ */ typst text document
|
||||
|
||||
182
src/typst.rs
182
src/typst.rs
@@ -2,15 +2,21 @@ use std::{
|
||||
collections::{HashMap, hash_map::Entry},
|
||||
hash::Hash,
|
||||
io::{self, ErrorKind},
|
||||
iter,
|
||||
path::{Path, PathBuf},
|
||||
sync::{LazyLock, Mutex},
|
||||
};
|
||||
|
||||
use codespan_reporting::{
|
||||
diagnostic::{Diagnostic, Label},
|
||||
files::Files,
|
||||
term::{Config, DisplayStyle, emit_to_write_style, termcolor::Ansi},
|
||||
};
|
||||
use typst::{
|
||||
Library, LibraryExt,
|
||||
Library, LibraryExt, WorldExt,
|
||||
diag::{FileError, FileResult, SourceDiagnostic, Warned},
|
||||
foundations::{Bytes, Datetime},
|
||||
syntax::{FileId, Source, Span, VirtualPath},
|
||||
syntax::{FileId, Lines, Source, Span, VirtualPath},
|
||||
text::{Font, FontBook},
|
||||
utils::LazyHash,
|
||||
};
|
||||
@@ -120,6 +126,16 @@ impl World {
|
||||
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 {
|
||||
@@ -127,21 +143,153 @@ pub struct TypstDocument {
|
||||
pub warnings: Vec<SourceDiagnostic>,
|
||||
}
|
||||
|
||||
impl TypstDocument {
|
||||
pub fn load(path: &Path) -> Result<Self, Vec<SourceDiagnostic>> {
|
||||
let Warned { output, warnings } = typst::compile(
|
||||
&World::new(path)
|
||||
.map_err(|e| vec![SourceDiagnostic::error(Span::detached(), e.to_string())])?,
|
||||
);
|
||||
match output {
|
||||
Ok(doc) => Ok(Self {
|
||||
doc,
|
||||
warnings: warnings.to_vec(),
|
||||
}),
|
||||
Err(mut errors) => {
|
||||
errors.extend(warnings);
|
||||
Err(errors.to_vec())
|
||||
}
|
||||
#[allow(clippy::result_large_err)]
|
||||
fn compile(path: &Path) -> Result<TypstDocument, (Option<World>, Vec<SourceDiagnostic>)> {
|
||||
let world = World::new(path).map_err(|e| {
|
||||
(
|
||||
None,
|
||||
vec![SourceDiagnostic::error(Span::detached(), e.to_string())],
|
||||
)
|
||||
})?;
|
||||
let Warned { output, warnings } = typst::compile(&world);
|
||||
match output {
|
||||
Ok(doc) => Ok(TypstDocument {
|
||||
doc,
|
||||
warnings: warnings.to_vec(),
|
||||
}),
|
||||
Err(mut errors) => {
|
||||
errors.extend(warnings);
|
||||
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