From a3f3a1a83330e3fe9a686fbe4c0eda9f1e9e99b2 Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com> Date: Sun, 14 Jul 2024 21:14:21 +0800 Subject: [PATCH] Change the signature of `World::main` (#4531) Co-authored-by: Laurenz --- crates/typst-cli/src/compile.rs | 67 ++------------------------------- crates/typst-cli/src/world.rs | 4 +- crates/typst-ide/src/lib.rs | 4 +- crates/typst-syntax/src/file.rs | 5 +++ crates/typst-syntax/src/path.rs | 5 +++ crates/typst/src/lib.rs | 65 +++++++++++++++++++++++++++++--- docs/src/html.rs | 14 ++++--- tests/fuzz/src/compile.rs | 16 +++++--- tests/src/world.rs | 4 +- 9 files changed, 97 insertions(+), 87 deletions(-) diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs index 4b87c3bd3..d15264255 100644 --- a/crates/typst-cli/src/compile.rs +++ b/crates/typst-cli/src/compile.rs @@ -5,16 +5,16 @@ use std::path::{Path, PathBuf}; use chrono::{Datelike, Timelike}; use codespan_reporting::diagnostic::{Diagnostic, Label}; use codespan_reporting::term; -use ecow::{eco_format, eco_vec, EcoString, EcoVec}; +use ecow::{eco_format, EcoString}; use parking_lot::RwLock; use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; -use typst::diag::{bail, FileError, Severity, SourceDiagnostic, StrResult, Warned}; +use typst::diag::{bail, Severity, SourceDiagnostic, StrResult, Warned}; use typst::foundations::{Datetime, Smart}; use typst::layout::{Frame, PageRanges}; use typst::model::Document; use typst::syntax::{FileId, Source, Span}; use typst::visualize::Color; -use typst::{World, WorldExt}; +use typst::WorldExt; use crate::args::{ CompileCommand, DiagnosticFormat, Input, Output, OutputFormat, PageRangeArgument, @@ -96,21 +96,6 @@ pub fn compile_once( Status::Compiling.print(command).unwrap(); } - if let Err(errors) = world - .source(world.main()) - .map_err(|err| hint_invalid_main_file(err, &command.common.input)) - { - set_failed(); - if watching { - Status::Error.print(command).unwrap(); - } - - print_diagnostics(world, &errors, &[], command.common.diagnostic_format) - .map_err(|err| eco_format!("failed to print diagnostics ({err})"))?; - - return Ok(()); - } - let Warned { output, warnings } = typst::compile(world); match output { @@ -498,52 +483,6 @@ fn open_file(open: Option<&str>, path: &Path) -> StrResult<()> { } } -/// Adds useful hints when the main source file couldn't be read -/// and returns the final diagnostic. -fn hint_invalid_main_file( - file_error: FileError, - input: &Input, -) -> EcoVec { - let is_utf8_error = matches!(file_error, FileError::InvalidUtf8); - let mut diagnostic = - SourceDiagnostic::error(Span::detached(), EcoString::from(file_error)); - - // Attempt to provide helpful hints for UTF-8 errors. - // Perhaps the user mistyped the filename. - // For example, they could have written "file.pdf" instead of - // "file.typ". - if is_utf8_error { - if let Input::Path(path) = input { - let extension = path.extension(); - if extension.is_some_and(|extension| extension == "typ") { - // No hints if the file is already a .typ file. - // The file is indeed just invalid. - return eco_vec![diagnostic]; - } - - match extension { - Some(extension) => { - diagnostic.hint(eco_format!( - "a file with the `.{}` extension is not usually a Typst file", - extension.to_string_lossy() - )); - } - - None => { - diagnostic - .hint("a file without an extension is not usually a Typst file"); - } - }; - - if path.with_extension("typ").exists() { - diagnostic.hint("check if you meant to use the `.typ` extension instead"); - } - } - } - - eco_vec![diagnostic] -} - /// Print diagnostic messages to the terminal. pub fn print_diagnostics( world: &SystemWorld, diff --git a/crates/typst-cli/src/world.rs b/crates/typst-cli/src/world.rs index 8e8b305f5..5a0814a86 100644 --- a/crates/typst-cli/src/world.rs +++ b/crates/typst-cli/src/world.rs @@ -192,8 +192,8 @@ impl World for SystemWorld { &self.book } - fn main(&self) -> Source { - self.source(self.main).unwrap() + fn main(&self) -> FileId { + self.main } fn source(&self, id: FileId) -> FileResult { diff --git a/crates/typst-ide/src/lib.rs b/crates/typst-ide/src/lib.rs index c4a88085d..403a36ba0 100644 --- a/crates/typst-ide/src/lib.rs +++ b/crates/typst-ide/src/lib.rs @@ -139,8 +139,8 @@ mod tests { &self.base.book } - fn main(&self) -> Source { - self.main.clone() + fn main(&self) -> FileId { + self.main.id() } fn source(&self, id: FileId) -> FileResult { diff --git a/crates/typst-syntax/src/file.rs b/crates/typst-syntax/src/file.rs index 356337f3f..89aaa55e1 100644 --- a/crates/typst-syntax/src/file.rs +++ b/crates/typst-syntax/src/file.rs @@ -91,6 +91,11 @@ impl FileId { Self::new(self.package().cloned(), self.vpath().join(path)) } + /// The same file location, but with a different extension. + pub fn with_extension(&self, extension: &str) -> Self { + Self::new(self.package().cloned(), self.vpath().with_extension(extension)) + } + /// Construct from a raw number. pub(crate) const fn from_raw(v: u16) -> Self { Self(v) diff --git a/crates/typst-syntax/src/path.rs b/crates/typst-syntax/src/path.rs index b561128c1..6c625642a 100644 --- a/crates/typst-syntax/src/path.rs +++ b/crates/typst-syntax/src/path.rs @@ -85,6 +85,11 @@ impl VirtualPath { Self::new(path) } } + + /// The same path, but with a different extension. + pub fn with_extension(&self, extension: &str) -> Self { + Self(self.0.with_extension(extension)) + } } impl Debug for VirtualPath { diff --git a/crates/typst/src/lib.rs b/crates/typst/src/lib.rs index 50575d129..cfcfd757a 100644 --- a/crates/typst/src/lib.rs +++ b/crates/typst/src/lib.rs @@ -60,10 +60,12 @@ use std::collections::HashSet; use std::ops::{Deref, Range}; use comemo::{Track, Tracked, Validate}; -use ecow::{EcoString, EcoVec}; +use ecow::{eco_format, eco_vec, EcoString, EcoVec}; use typst_timing::{timed, TimingScope}; -use crate::diag::{warning, FileResult, SourceDiagnostic, SourceResult, Warned}; +use crate::diag::{ + warning, FileError, FileResult, SourceDiagnostic, SourceResult, Warned, +}; use crate::engine::{Engine, Route, Sink, Traced}; use crate::foundations::{ Array, Bytes, Datetime, Dict, Module, Scope, StyleChain, Styles, Value, @@ -108,13 +110,19 @@ fn compile_inner( let library = world.library(); let styles = StyleChain::new(&library.styles); + // Fetch the main source file once. + let main = world.main(); + let main = world + .source(main) + .map_err(|err| hint_invalid_main_file(world, err, main))?; + // First evaluate the main source file into a module. let content = crate::eval::eval( world, traced, sink.track_mut(), Route::default().track(), - &world.main(), + &main, )? .content(); @@ -203,8 +211,8 @@ pub trait World: Send + Sync { /// Metadata about all known fonts. fn book(&self) -> &LazyHash; - /// Access the main source file. - fn main(&self) -> Source; + /// Get the file id of the main source file. + fn main(&self) -> FileId; /// Try to access the specified source file. fn source(&self, id: FileId) -> FileResult; @@ -246,7 +254,7 @@ macro_rules! delegate_for_ptr { self.deref().book() } - fn main(&self) -> Source { + fn main(&self) -> FileId { self.deref().main() } @@ -402,3 +410,48 @@ fn prelude(global: &mut Scope) { global.define("horizon", Alignment::HORIZON); global.define("bottom", Alignment::BOTTOM); } + +/// Adds useful hints when the main source file couldn't be read +/// and returns the final diagnostic. +fn hint_invalid_main_file( + world: Tracked, + file_error: FileError, + input: FileId, +) -> EcoVec { + let is_utf8_error = matches!(file_error, FileError::InvalidUtf8); + let mut diagnostic = + SourceDiagnostic::error(Span::detached(), EcoString::from(file_error)); + + // Attempt to provide helpful hints for UTF-8 errors. Perhaps the user + // mistyped the filename. For example, they could have written "file.pdf" + // instead of "file.typ". + if is_utf8_error { + let path = input.vpath(); + let extension = path.as_rootless_path().extension(); + if extension.is_some_and(|extension| extension == "typ") { + // No hints if the file is already a .typ file. + // The file is indeed just invalid. + return eco_vec![diagnostic]; + } + + match extension { + Some(extension) => { + diagnostic.hint(eco_format!( + "a file with the `.{}` extension is not usually a Typst file", + extension.to_string_lossy() + )); + } + + None => { + diagnostic + .hint("a file without an extension is not usually a Typst file"); + } + }; + + if world.source(input.with_extension("typ")).is_ok() { + diagnostic.hint("check if you meant to use the `.typ` extension instead"); + } + } + + eco_vec![diagnostic] +} diff --git a/docs/src/html.rs b/docs/src/html.rs index ab140a902..58c8e54c2 100644 --- a/docs/src/html.rs +++ b/docs/src/html.rs @@ -6,7 +6,7 @@ use heck::{ToKebabCase, ToTitleCase}; use pulldown_cmark as md; use serde::{Deserialize, Serialize}; use typed_arena::Arena; -use typst::diag::{FileResult, StrResult}; +use typst::diag::{FileError, FileResult, StrResult}; use typst::foundations::{Bytes, Datetime}; use typst::layout::{Abs, Point, Size}; use typst::syntax::{FileId, Source, VirtualPath}; @@ -463,12 +463,16 @@ impl World for DocWorld { &FONTS.0 } - fn main(&self) -> Source { - self.0.clone() + fn main(&self) -> FileId { + self.0.id() } - fn source(&self, _: FileId) -> FileResult { - Ok(self.0.clone()) + fn source(&self, id: FileId) -> FileResult { + if id == self.0.id() { + Ok(self.0.clone()) + } else { + Err(FileError::NotFound(id.vpath().as_rootless_path().into())) + } } fn file(&self, id: FileId) -> FileResult { diff --git a/tests/fuzz/src/compile.rs b/tests/fuzz/src/compile.rs index 98c300ce5..c9536150f 100644 --- a/tests/fuzz/src/compile.rs +++ b/tests/fuzz/src/compile.rs @@ -39,16 +39,20 @@ impl World for FuzzWorld { &self.book } - fn main(&self) -> Source { - self.source.clone() + fn main(&self) -> FileId { + self.source.id() } - fn source(&self, src: FileId) -> FileResult { - Err(FileError::NotFound(src.vpath().as_rootless_path().into())) + fn source(&self, id: FileId) -> FileResult { + if id == self.source.id() { + Ok(self.source.clone()) + } else { + Err(FileError::NotFound(id.vpath().as_rootless_path().into())) + } } - fn file(&self, src: FileId) -> FileResult { - Err(FileError::NotFound(src.vpath().as_rootless_path().into())) + fn file(&self, id: FileId) -> FileResult { + Err(FileError::NotFound(id.vpath().as_rootless_path().into())) } fn font(&self, _: usize) -> Option { diff --git a/tests/src/world.rs b/tests/src/world.rs index ad925a538..47b77d7e5 100644 --- a/tests/src/world.rs +++ b/tests/src/world.rs @@ -43,8 +43,8 @@ impl World for TestWorld { &self.base.book } - fn main(&self) -> Source { - self.main.clone() + fn main(&self) -> FileId { + self.main.id() } fn source(&self, id: FileId) -> FileResult {