From a7961be4ac93e9fdd6f96830aff176c448351177 Mon Sep 17 00:00:00 2001 From: Myriad-Dreamin <35292584+Myriad-Dreamin@users.noreply.github.com> Date: Mon, 20 Nov 2023 17:59:51 +0800 Subject: [PATCH] dev(compiler): replace diag features by CompileReporter (#413) * dev(compiler): add CompileReporter * dev(compiler): retrieve status feature from stack * dev(compiler): retrieve diag fmt from stack * dev(compiler): remove DiagObserver * dev(core): export ecow for downstream * dev(compiler): unify Time type * dev(compiler): remove DiagStatus, export CompileReport * fix: recover from unused error --- Cargo.lock | 1 - cli/src/compile.rs | 11 +- cli/src/lib.rs | 40 ++++++ cli/src/query_repl.rs | 49 +++++-- compiler/Cargo.toml | 1 - compiler/src/lib.rs | 8 +- compiler/src/service/compile.rs | 47 ++++--- compiler/src/service/diag.rs | 94 -------------- compiler/src/service/diag/console.rs | 146 +++++++++++++++++++++ compiler/src/service/diag/mod.rs | 18 +++ compiler/src/service/driver.rs | 2 +- compiler/src/service/export.rs | 153 +++++++++++++++++++++- compiler/src/service/features.rs | 106 +++++++++++++++ compiler/src/service/mod.rs | 185 +++++++++++++-------------- compiler/src/service/watch.rs | 5 +- compiler/src/time.rs | 2 +- compiler/src/vfs/browser.rs | 6 +- compiler/src/vfs/cached.rs | 8 +- compiler/src/vfs/dummy.rs | 6 +- compiler/src/vfs/mod.rs | 13 +- compiler/src/vfs/notify.rs | 10 +- compiler/src/vfs/overlay.rs | 10 +- compiler/src/vfs/system.rs | 3 +- compiler/src/vfs/trace.rs | 4 +- compiler/src/world.rs | 10 +- core/src/concepts/marker.rs | 1 + core/src/concepts/mod.rs | 4 +- core/src/concepts/typst.rs | 20 ++- server/dev/src/main.rs | 29 +++-- 29 files changed, 696 insertions(+), 296 deletions(-) delete mode 100644 compiler/src/service/diag.rs create mode 100644 compiler/src/service/diag/console.rs create mode 100644 compiler/src/service/diag/mod.rs create mode 100644 compiler/src/service/features.rs diff --git a/Cargo.lock b/Cargo.lock index f6df6311..aca9bf7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4259,7 +4259,6 @@ dependencies = [ "comemo", "dirs", "dissimilar", - "ecow", "flate2", "fontdb", "fst", diff --git a/cli/src/compile.rs b/cli/src/compile.rs index ec3c3a53..b88b3c29 100644 --- a/cli/src/compile.rs +++ b/cli/src/compile.rs @@ -2,7 +2,10 @@ use std::path::Path; use typst::doc::Document; use typst_ts_compiler::{ - service::{CompileActor, CompileDriver, CompileExporter, Compiler, DynamicLayoutCompiler}, + service::{ + features::{FeatureSet, DIAG_FMT_FEATURE}, + CompileActor, CompileDriver, CompileExporter, Compiler, DynamicLayoutCompiler, + }, TypstSystemWorld, }; use typst_ts_core::{config::CompileOpts, exporter_builtins::GroupExporter, path::PathClean}; @@ -101,10 +104,14 @@ pub fn compile_export(args: CompileArgs, exporter: GroupExporter) -> ! let watch_root = driver.world().root.as_ref().to_owned(); + let feature_set = + FeatureSet::default().configure(&DIAG_FMT_FEATURE, args.diagnostic_format.into()); + // CompileExporter + DynamicLayoutCompiler + WatchDriver let driver = CompileExporter::new(driver).with_exporter(exporter); let driver = DynamicLayoutCompiler::new(driver, output_dir).with_enable(args.dynamic_layout); - let actor = CompileActor::new(driver, watch_root).with_watch(args.watch); + let actor = + CompileActor::new_with_features(driver, watch_root, feature_set).with_watch(args.watch); utils::async_continue(async move { utils::logical_exit(actor.run()); diff --git a/cli/src/lib.rs b/cli/src/lib.rs index ae1624f8..68f605d4 100644 --- a/cli/src/lib.rs +++ b/cli/src/lib.rs @@ -1,3 +1,4 @@ +use core::fmt; use std::path::PathBuf; pub mod compile; @@ -148,6 +149,14 @@ pub struct CompileArgs { #[clap(long)] pub format: Vec, + /// In which format to emit diagnostics + #[clap( + long, + default_value_t = DiagnosticFormat::Human, + value_parser = clap::value_parser!(DiagnosticFormat) + )] + pub diagnostic_format: DiagnosticFormat, + /// Enable tracing. /// Possible usage: --trace=verbosity={0..3} /// where verbosity: {0..3} -> {warning, info, debug, trace} @@ -280,6 +289,37 @@ pub struct GenPackagesDocArgs { pub dynamic_layout: bool, } +/// Which format to use for diagnostics. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, ValueEnum)] +pub enum DiagnosticFormat { + Human, + Short, +} + +impl From for typst_ts_compiler::service::DiagnosticFormat { + fn from(fmt: DiagnosticFormat) -> Self { + match fmt { + DiagnosticFormat::Human => Self::Human, + DiagnosticFormat::Short => Self::Short, + } + } +} + +impl Default for DiagnosticFormat { + fn default() -> Self { + Self::Human + } +} + +impl fmt::Display for DiagnosticFormat { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.to_possible_value() + .expect("no values are skipped") + .get_name() + .fmt(f) + } +} + pub fn get_cli(sub_command_required: bool) -> Command { let cli = Command::new("$").disable_version_flag(true); Opts::augment_args(cli).subcommand_required(sub_command_required) diff --git a/cli/src/query_repl.rs b/cli/src/query_repl.rs index 3d3de496..aa273279 100644 --- a/cli/src/query_repl.rs +++ b/cli/src/query_repl.rs @@ -1,7 +1,10 @@ use std::borrow::Cow::{self, Owned}; -use std::cell::RefCell; -use typst_ts_compiler::service::{CompileDriver, Compiler, DiagObserver}; -use typst_ts_compiler::ShadowApi; +use std::cell::{RefCell, RefMut}; +use std::sync::Arc; + +use typst::diag::SourceDiagnostic; +use typst::World; +use typst_ide::autocomplete; use rustyline::completion::{Completer, Pair}; use rustyline::error::ReadlineError; @@ -10,14 +13,14 @@ use rustyline::hint::{Hinter, HistoryHinter}; use rustyline::validate::MatchingBracketValidator; use rustyline::{Cmd, CompletionType, Config, EditMode, Editor, KeyEvent}; use rustyline::{Helper, Validator}; -use typst_ts_core::TakeAs; + +use typst_ts_compiler::service::{CompileDriver, CompileReport, Compiler, ConsoleDiagReporter}; +use typst_ts_compiler::{ShadowApi, TypstSystemWorld}; +use typst_ts_core::{typst::prelude::*, GenericExporter, TakeAs}; use crate::query::serialize; use crate::CompileOnceArgs; -use typst::World; -use typst_ide::autocomplete; - #[derive(Helper, Validator)] struct ReplContext { #[rustyline(Validator)] @@ -28,6 +31,7 @@ struct ReplContext { // typst world state driver: RefCell, + reporter: ConsoleDiagReporter, } impl ReplContext { @@ -37,6 +41,7 @@ impl ReplContext { hinter: HistoryHinter {}, validator: MatchingBracketValidator::new(), driver: RefCell::new(driver), + reporter: ConsoleDiagReporter::default(), } } } @@ -228,14 +233,30 @@ pub fn start_repl_test(args: CompileOnceArgs) -> rustyline::Result<()> { } impl ReplContext { + fn process_err( + &self, + driver: &RefMut, + err: EcoVec, + ) -> Result<(), ()> { + let rep = CompileReport::CompileError(driver.main_id(), err, Default::default()); + let _ = self.reporter.export(driver.world(), Arc::new(rep)); + Ok(()) + } + fn repl_process_line(&mut self, line: String) { - let compiled = self.driver.borrow_mut().with_stage_diag::( - "compiling", - |driver: &mut CompileDriver| { - let doc = driver.compile(&mut Default::default())?; - driver.query(line, &doc) - }, - ); + let compiled = { + let mut driver = self.driver.borrow_mut(); + let doc = driver + .compile(&mut Default::default()) + .map_err(|err| self.process_err(&driver, err)) + .ok(); + doc.and_then(|doc| { + driver + .query(line, &doc) + .map_err(|err| self.process_err(&driver, err)) + .ok() + }) + }; if let Some(compiled) = compiled { let serialized = serialize(&compiled, "json").unwrap(); diff --git a/compiler/Cargo.toml b/compiler/Cargo.toml index 83a153c1..69a4c384 100644 --- a/compiler/Cargo.toml +++ b/compiler/Cargo.toml @@ -21,7 +21,6 @@ parking_lot.workspace = true hex.workspace = true sha2.workspace = true flate2.workspace = true -ecow.workspace = true instant.workspace = true strum.workspace = true rayon = { workspace = true, optional = true } diff --git a/compiler/src/lib.rs b/compiler/src/lib.rs index 4b25cd14..659de0f9 100644 --- a/compiler/src/lib.rs +++ b/compiler/src/lib.rs @@ -67,9 +67,11 @@ use typst::{ diag::{At, FileResult, SourceResult}, syntax::Span, }; -use typst_ts_core::{Bytes, ImmutPath, TypstFileId}; +use typst_ts_core::{typst::prelude::*, Bytes, ImmutPath, TypstFileId}; use vfs::notify::FilesystemEvent; +pub use time::Time; + /// Latest version of the shadow api, which is in beta. pub trait ShadowApi { fn _shadow_map_id(&self, _file_id: TypstFileId) -> FileResult { @@ -100,7 +102,7 @@ pub trait ShadowApi { f: impl FnOnce(&mut Self) -> SourceResult, ) -> SourceResult { self.map_shadow(file_path, content).at(Span::detached())?; - let res: Result> = f(self); + let res: Result> = f(self); self.unmap_shadow(file_path).at(Span::detached())?; res } @@ -138,7 +140,7 @@ pub trait ShadowApi { /// Latest version of the notify api, which is in beta. pub trait NotifyApi { - fn iter_dependencies<'a>(&'a self, f: &mut dyn FnMut(&'a ImmutPath, instant::SystemTime)); + fn iter_dependencies<'a>(&'a self, f: &mut dyn FnMut(&'a ImmutPath, crate::Time)); fn notify_fs_event(&mut self, event: FilesystemEvent); } diff --git a/compiler/src/service/compile.rs b/compiler/src/service/compile.rs index 9b5e793d..005417fd 100644 --- a/compiler/src/service/compile.rs +++ b/compiler/src/service/compile.rs @@ -17,6 +17,7 @@ use typst::{ }; use crate::{ + service::features::WITH_COMPILING_STATUS_FEATURE, vfs::notify::{FilesystemEvent, MemoryEvent, NotifyMessage}, world::{CompilerFeat, CompilerWorld}, ShadowApi, @@ -27,7 +28,10 @@ use typst_ts_core::{ TypstDocument, TypstFileId, }; -use super::{Compiler, DiagObserver, WorkspaceProvider, WorldExporter}; +use super::{ + features::FeatureSet, CompileEnv, CompileReporter, Compiler, ConsoleDiagReporter, + WorkspaceProvider, WorldExporter, +}; /// A task that can be sent to the context (compiler thread) /// @@ -66,7 +70,7 @@ struct TaggedMemoryEvent { /// The compiler thread. pub struct CompileActor { /// The underlying compiler. - pub compiler: C, + pub compiler: CompileReporter, /// The root path of the workspace. pub root: PathBuf, /// Whether to enable file system watching. @@ -81,6 +85,10 @@ pub struct CompileActor { estimated_shadow_files: HashSet>, /// The latest compiled document. latest_doc: Option>, + /// feature set for compile_once mode. + once_feature_set: FeatureSet, + /// Shared feature set for watch mode. + watch_feature_set: Arc, /// Internal channel for stealing the compiler thread. steal_send: mpsc::UnboundedSender>, @@ -95,13 +103,19 @@ impl CompileActor where C::World: for<'files> codespan_reporting::files::Files<'files, FileId = TypstFileId>, { - /// Create a new compiler thread. - pub fn new(compiler: C, root: PathBuf) -> Self { + pub fn new_with_features(compiler: C, root: PathBuf, feature_set: FeatureSet) -> Self { let (steal_send, steal_recv) = mpsc::unbounded_channel(); let (memory_send, memory_recv) = mpsc::unbounded_channel(); + let watch_feature_set = Arc::new( + feature_set + .clone() + .configure(&WITH_COMPILING_STATUS_FEATURE, true), + ); + Self { - compiler, + compiler: CompileReporter::new(compiler) + .with_generic_reporter(ConsoleDiagReporter::default()), root, logical_tick: 1, @@ -110,6 +124,8 @@ where estimated_shadow_files: Default::default(), latest_doc: None, + once_feature_set: feature_set, + watch_feature_set, steal_send, steal_recv, @@ -119,6 +135,11 @@ where } } + /// Create a new compiler thread. + pub fn new(compiler: C, root: PathBuf) -> Self { + Self::new_with_features(compiler, root, FeatureSet::default()) + } + /// Run the compiler thread synchronously. pub fn run(self) -> bool { use tokio::runtime::Handle; @@ -137,10 +158,8 @@ where if !self.enable_watch { let compiled = self .compiler - .with_stage_diag::("compiling", |driver| { - driver.compile(&mut Default::default()) - }); - return compiled.is_some(); + .compile(&mut CompileEnv::default().configure(self.once_feature_set)); + return compiled.is_ok(); } if let Some(h) = self.spawn().await { @@ -156,9 +175,8 @@ where pub async fn spawn(mut self) -> Option> { if !self.enable_watch { self.compiler - .with_stage_diag::("compiling", |driver| { - driver.compile(&mut Default::default()) - }); + .compile(&mut CompileEnv::default().configure(self.once_feature_set)) + .ok(); return None; } @@ -230,9 +248,8 @@ where // Compile the document. self.latest_doc = self .compiler - .with_stage_diag::("compiling", |driver| { - driver.compile(&mut Default::default()) - }); + .compile(&mut CompileEnv::default().configure_shared(self.watch_feature_set.clone())) + .ok(); // Evict compilation cache. comemo::evict(30); diff --git a/compiler/src/service/diag.rs b/compiler/src/service/diag.rs deleted file mode 100644 index 09ff02cd..00000000 --- a/compiler/src/service/diag.rs +++ /dev/null @@ -1,94 +0,0 @@ -use std::io::{self, IsTerminal}; - -use codespan_reporting::files::Files; -use codespan_reporting::{ - diagnostic::{Diagnostic, Label}, - term::{ - self, - termcolor::{ColorChoice, StandardStream}, - }, -}; - -use typst::diag::Severity; -use typst::syntax::Span; -use typst::WorldExt; -use typst::{diag::SourceDiagnostic, World}; - -use typst::eval::eco_format; -use typst_ts_core::TypstFileId; - -use super::DiagStatus; - -/// Get stderr with color support if desirable. -fn color_stream() -> StandardStream { - StandardStream::stderr(if std::io::stderr().is_terminal() { - ColorChoice::Auto - } else { - ColorChoice::Never - }) -} - -/// Print diagnostic messages to the terminal. -pub fn print_diagnostics<'files, W: World + Files<'files, FileId = TypstFileId>>( - world: &'files W, - errors: ecow::EcoVec, -) -> Result<(), codespan_reporting::files::Error> { - let mut w = color_stream(); - let config = term::Config { - tab_width: 2, - ..Default::default() - }; - - for diagnostic in errors { - let diag = match diagnostic.severity { - Severity::Error => Diagnostic::error(), - Severity::Warning => Diagnostic::warning(), - } - .with_message(diagnostic.message.clone()) - .with_notes( - diagnostic - .hints - .iter() - .map(|e| (eco_format!("hint: {e}")).into()) - .collect(), - ) - .with_labels(label(world, diagnostic.span).into_iter().collect()); - - term::emit(&mut w, &config, world, &diag)?; - - // Stacktrace-like helper diagnostics. - for point in diagnostic.trace { - let message = point.v.to_string(); - let help = Diagnostic::help() - .with_message(message) - .with_labels(label(world, point.span).into_iter().collect()); - - term::emit(&mut w, &config, world, &help)?; - } - } - - Ok(()) -} - -/// Create a label for a span. -fn label<'files, W: World + Files<'files, FileId = TypstFileId>>( - world: &'files W, - span: Span, -) -> Option> { - Some(Label::primary(span.id()?, world.range(span)?)) -} - -/// Render the status message. -pub fn status(entry_file: TypstFileId, status: DiagStatus) -> io::Result<()> { - let input = entry_file; - match status { - DiagStatus::Stage(stage) => log::info!("{:?}: {} ...", input, stage), - DiagStatus::Success(duration) => { - log::info!("{:?}: Compilation succeeded in {:?}", input, duration) - } - DiagStatus::Error(duration) => { - log::info!("{:?}: Compilation failed after {:?}", input, duration) - } - }; - Ok(()) -} diff --git a/compiler/src/service/diag/console.rs b/compiler/src/service/diag/console.rs new file mode 100644 index 00000000..83722917 --- /dev/null +++ b/compiler/src/service/diag/console.rs @@ -0,0 +1,146 @@ +use std::io::IsTerminal; +use std::sync::Arc; + +use codespan_reporting::files::Files; +use codespan_reporting::{ + diagnostic::{Diagnostic, Label}, + term::{ + self, + termcolor::{ColorChoice, StandardStream}, + }, +}; + +use typst::diag::{Severity, SourceResult}; +use typst::syntax::Span; +use typst::WorldExt; +use typst::{diag::SourceDiagnostic, World}; + +use typst::eval::eco_format; +use typst_ts_core::{typst::prelude::*, GenericExporter, PhantomParamData, TakeAs, TypstFileId}; + +use crate::service::features::{ + CompileFeature, FeatureSet, DIAG_FMT_FEATURE, WITH_COMPILING_STATUS_FEATURE, +}; +use crate::service::CompileReport; + +use super::DiagnosticFormat; + +/// Get stderr with color support if desirable. +fn color_stream() -> StandardStream { + StandardStream::stderr(if std::io::stderr().is_terminal() { + ColorChoice::Auto + } else { + ColorChoice::Never + }) +} + +/// Print diagnostic messages to the terminal. +fn print_diagnostics<'files, W: World + Files<'files, FileId = TypstFileId>>( + world: &'files W, + errors: EcoVec, + diagnostic_format: DiagnosticFormat, +) -> Result<(), codespan_reporting::files::Error> { + let mut w = match diagnostic_format { + DiagnosticFormat::Human => color_stream(), + DiagnosticFormat::Short => StandardStream::stderr(ColorChoice::Never), + }; + + let mut config = term::Config { + tab_width: 2, + ..Default::default() + }; + if diagnostic_format == DiagnosticFormat::Short { + config.display_style = term::DisplayStyle::Short; + } + + for diagnostic in errors { + let diag = match diagnostic.severity { + Severity::Error => Diagnostic::error(), + Severity::Warning => Diagnostic::warning(), + } + .with_message(diagnostic.message.clone()) + .with_notes( + diagnostic + .hints + .iter() + .map(|e| (eco_format!("hint: {e}")).into()) + .collect(), + ) + .with_labels(label(world, diagnostic.span).into_iter().collect()); + + term::emit(&mut w, &config, world, &diag)?; + + // Stacktrace-like helper diagnostics. + for point in diagnostic.trace { + let message = point.v.to_string(); + let help = Diagnostic::help() + .with_message(message) + .with_labels(label(world, point.span).into_iter().collect()); + + term::emit(&mut w, &config, world, &help)?; + } + } + + Ok(()) +} + +/// Create a label for a span. +fn label<'files, W: World + Files<'files, FileId = TypstFileId>>( + world: &'files W, + span: Span, +) -> Option> { + Some(Label::primary(span.id()?, world.range(span)?)) +} + +#[derive(Debug, Clone, Copy)] +pub struct ConsoleDiagReporter(PhantomParamData); + +impl Default for ConsoleDiagReporter +where + W: for<'files> codespan_reporting::files::Files<'files, FileId = TypstFileId>, +{ + fn default() -> Self { + Self(PhantomParamData::default()) + } +} + +impl GenericExporter for ConsoleDiagReporter +where + X: World + for<'files> codespan_reporting::files::Files<'files, FileId = TypstFileId>, +{ + type W = X; + + fn export(&self, world: &Self::W, output: Arc) -> SourceResult<()> { + self.export(world, Arc::new((Default::default(), output.take()))) + } +} + +impl GenericExporter<(Arc, CompileReport)> for ConsoleDiagReporter +where + X: World + for<'files> codespan_reporting::files::Files<'files, FileId = TypstFileId>, +{ + type W = X; + + fn export( + &self, + world: &Self::W, + output: Arc<(Arc, CompileReport)>, + ) -> SourceResult<()> { + let (features, report) = output.take(); + + if WITH_COMPILING_STATUS_FEATURE.retrieve(&features) { + log::info!("{}", report.message()); + } + + if let Some(diag) = report.diagnostics() { + let _err = print_diagnostics(world, diag, DIAG_FMT_FEATURE.retrieve(&features)); + // todo: log in browser compiler + #[cfg(feature = "system-compile")] + if _err.is_err() { + log::error!("failed to print diagnostics: {:?}", _err); + } + } + + Ok(()) + } +} diff --git a/compiler/src/service/diag/mod.rs b/compiler/src/service/diag/mod.rs new file mode 100644 index 00000000..03ca449a --- /dev/null +++ b/compiler/src/service/diag/mod.rs @@ -0,0 +1,18 @@ +// todo: remove cfg feature here +#[cfg(feature = "system-compile")] +mod console; +#[cfg(feature = "system-compile")] +pub use console::*; + +/// Which format to use for diagnostics. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd)] +pub enum DiagnosticFormat { + Human, + Short, +} + +impl Default for DiagnosticFormat { + fn default() -> Self { + Self::Human + } +} diff --git a/compiler/src/service/driver.rs b/compiler/src/service/driver.rs index 8ca3e467..7a285774 100644 --- a/compiler/src/service/driver.rs +++ b/compiler/src/service/driver.rs @@ -97,7 +97,7 @@ impl Compiler for CompileDriverImpl self._relevant(event).unwrap_or(true) } - fn iter_dependencies<'a>(&'a self, f: &mut dyn FnMut(&'a ImmutPath, instant::SystemTime)) { + fn iter_dependencies<'a>(&'a self, f: &mut dyn FnMut(&'a ImmutPath, crate::Time)) { self.world.iter_dependencies(f) } diff --git a/compiler/src/service/export.rs b/compiler/src/service/export.rs index aaa23a44..8ec1fc28 100644 --- a/compiler/src/service/export.rs +++ b/compiler/src/service/export.rs @@ -1,10 +1,16 @@ use std::{path::PathBuf, sync::Arc}; use crate::ShadowApi; -use typst::diag::SourceResult; -use typst_ts_core::{exporter_builtins::GroupExporter, DynExporter, TypstDocument}; +use typst::{diag::SourceResult, World}; +use typst_ts_core::{ + exporter_builtins::GroupExporter, typst::prelude::*, DynExporter, DynGenericExporter, + DynPolymorphicExporter, GenericExporter, TakeAs, TypstDocument, +}; -use super::{CompileEnv, CompileMiddleware, Compiler}; +use super::{ + features::{CompileFeature, FeatureSet, WITH_COMPILING_STATUS_FEATURE}, + CompileEnv, CompileMiddleware, CompileReport, Compiler, +}; pub trait WorldExporter { fn export(&mut self, output: Arc) -> SourceResult<()>; @@ -61,6 +67,145 @@ impl CompileMiddleware for CompileExporter { } } +pub type ReportExporter = DynExporter; +pub type FeaturedReportExporter = DynExporter<(Arc, CompileReport)>; + +#[allow(dead_code)] +pub struct CompileReporter { + pub compiler: C, + pub reporter: DynGenericExporter, CompileReport)>, +} + +#[allow(dead_code)] +impl CompileReporter +where + C::World: 'static, +{ + pub fn new(compiler: C) -> Self { + let x: FeaturedReportExporter = GroupExporter::new(vec![]).into(); + Self { + compiler, + reporter: Box::new(DynPolymorphicExporter::::new(x)), + } + } + + /// Wrap driver with a given reporter. + pub fn with_reporter(mut self, reporter: impl Into) -> Self { + self.set_reporter(reporter); + self + } + + /// set an reporter. + pub fn set_reporter(&mut self, reporter: impl Into) { + let reporter = reporter.into(); + let reporter: FeaturedReportExporter = Box::new( + move |world: &dyn World, inp: Arc<(Arc, CompileReport)>| { + // it is believed that no clone will happen here + reporter.export(world, Arc::new(inp.take().1)) + }, + ); + self.reporter = Box::new(DynPolymorphicExporter::::new(reporter)); + } + + /// Wrap driver with a given featured reporter. + pub fn with_featured_reporter(mut self, reporter: impl Into) -> Self { + self.set_featured_reporter(reporter); + self + } + + /// set an featured reporter. + pub fn set_featured_reporter(&mut self, reporter: impl Into) { + self.reporter = Box::new(DynPolymorphicExporter::::new( + reporter.into(), + )); + } + + /// Wrap driver with a given generic reporter. + pub fn with_generic_reporter( + mut self, + reporter: impl GenericExporter<(Arc, CompileReport), W = C::World> + Send + 'static, + ) -> Self { + self.reporter = Box::new(reporter); + self + } + + /// set an generic reporter. + pub fn set_generic_reporter( + &mut self, + reporter: impl GenericExporter<(Arc, CompileReport), W = C::World> + Send + 'static, + ) { + self.reporter = Box::new(reporter); + } +} + +impl WorldExporter for CompileReporter { + /// Export a typst document using `typst_ts_core::DocumentExporter`. + fn export(&mut self, output: Arc) -> SourceResult<()> { + self.compiler.export(output) + } +} + +impl CompileMiddleware for CompileReporter { + type Compiler = C; + + fn inner(&self) -> &Self::Compiler { + &self.compiler + } + + fn inner_mut(&mut self) -> &mut Self::Compiler { + &mut self.compiler + } + + fn wrap_compile(&mut self, env: &mut CompileEnv) -> SourceResult> { + let start = crate::Time::now(); + let id = self.main_id(); + if WITH_COMPILING_STATUS_FEATURE.retrieve(&env.features) { + let rep = CompileReport::Stage(id, "compiling", start); + let rep = Arc::new((env.features.clone(), rep)); + // we currently ignore export error here + let _ = self.reporter.export(self.compiler.world(), rep); + } + + let tracer = env.tracer.take(); + let origin = tracer.is_some(); + + env.tracer = Some(tracer.unwrap_or_default()); + + let doc = self.inner_mut().compile(env); + + let elapsed = start.elapsed().unwrap_or_default(); + + let rep; + + let doc = match doc { + Ok(doc) => { + let warnings = env.tracer.as_ref().unwrap().clone().warnings(); + if warnings.is_empty() { + rep = CompileReport::CompileSuccess(id, warnings, elapsed); + } else { + rep = CompileReport::CompileWarning(id, warnings, elapsed); + } + + Ok(doc) + } + Err(err) => { + rep = CompileReport::CompileError(id, err, elapsed); + Err(eco_vec![]) + } + }; + + if !origin { + env.tracer = None; + } + + let rep = Arc::new((env.features.clone(), rep)); + // we currently ignore export error here + let _ = self.reporter.export(self.compiler.world(), rep); + + doc + } +} + pub type LayoutWidths = Vec; pub struct DynamicLayoutCompiler { @@ -130,8 +275,8 @@ impl WorldExporter for DynamicLayoutCompiler { diag::At, syntax::{PackageSpec, Span, VirtualPath}, }; - use typst_ts_core::TypstFileId; + use typst_ts_svg_exporter::{flat_ir::serialize_doc, DynamicLayoutSvgExporter}; let variable_file = TypstFileId::new( diff --git a/compiler/src/service/features.rs b/compiler/src/service/features.rs new file mode 100644 index 00000000..07129712 --- /dev/null +++ b/compiler/src/service/features.rs @@ -0,0 +1,106 @@ +use once_cell::sync::Lazy; +use typst_ts_core::typst::prelude::*; + +use super::diag::DiagnosticFormat; + +#[derive(Debug, Clone, Copy)] +pub struct FeatureSlot(u16); + +/// The global feature allocator. +/// already used: 1 +static ALLOCATOR: Lazy = + Lazy::new(|| std::sync::atomic::AtomicU16::new(1)); + +#[derive(Default)] +pub struct LazyFeatureSlot(once_cell::sync::OnceCell); + +impl From<&LazyFeatureSlot> for FeatureSlot { + fn from(slot: &LazyFeatureSlot) -> Self { + *slot.0.get_or_init(|| { + FeatureSlot(ALLOCATOR.fetch_add(1, std::sync::atomic::Ordering::SeqCst)) + }) + } +} + +#[derive(Default, Clone)] +pub struct FeatureSet { + features: Vec, +} + +impl FeatureSet { + pub fn configure, V>(self, feature: &T, value: V) -> Self { + feature.configure(self, value) + } + + pub fn configure_slot(mut self, slot: impl Into, value: EcoString) -> Self { + let slot = slot.into().0 as usize; + if slot >= self.features.len() { + self.features.resize(slot + 1, "".into()); + } + + self.features[slot] = value; + self + } + + fn slot(&self, slot: impl Into) -> Option<&EcoString> { + let slot = slot.into().0 as usize; + + self.features.get(slot) + } +} + +pub trait CompileFeature { + fn configure(&self, features: FeatureSet, value: T) -> FeatureSet; + fn retrieve(&self, features: &FeatureSet) -> T; +} + +pub struct DiagFmtFeature; +const DIAG_FEATURE: FeatureSlot = FeatureSlot(0); +pub static DIAG_FMT_FEATURE: DiagFmtFeature = DiagFmtFeature; + +impl CompileFeature for DiagFmtFeature { + fn configure(&self, features: FeatureSet, value: DiagnosticFormat) -> FeatureSet { + features.configure_slot( + DIAG_FEATURE, + match value { + DiagnosticFormat::Human => "", + DiagnosticFormat::Short => "s", + } + .into(), + ) + } + + fn retrieve(&self, features: &FeatureSet) -> DiagnosticFormat { + features + .slot(DIAG_FEATURE) + .and_then(|s| (s == "s").then_some(DiagnosticFormat::Short)) + .unwrap_or_default() + } +} + +#[derive(Default)] +pub struct BuiltinFeature(LazyFeatureSlot, std::marker::PhantomData); + +impl BuiltinFeature { + pub const fn new() -> Self { + Self( + LazyFeatureSlot(once_cell::sync::OnceCell::new()), + std::marker::PhantomData, + ) + } +} + +pub static WITH_COMPILING_STATUS_FEATURE: BuiltinFeature = BuiltinFeature::::new(); + +impl CompileFeature for BuiltinFeature { + fn configure(&self, features: FeatureSet, value: bool) -> FeatureSet { + features.configure_slot(&self.0, if value { "1" } else { "" }.into()) + } + + fn retrieve(&self, features: &FeatureSet) -> bool { + features + .slot(&self.0) + .and_then(|s| (s == "1").then_some(true)) + .unwrap_or_default() + } +} diff --git a/compiler/src/service/mod.rs b/compiler/src/service/mod.rs index 10c7fae4..95372716 100644 --- a/compiler/src/service/mod.rs +++ b/compiler/src/service/mod.rs @@ -1,3 +1,4 @@ +use core::fmt; use std::{ path::{Path, PathBuf}, sync::Arc, @@ -12,12 +13,11 @@ use typst::{ syntax::Span, World, }; -use typst_library::prelude::{eco_format, EcoString}; -use typst_ts_core::{Bytes, ImmutPath, TypstFileId}; +use typst_ts_core::{typst::prelude::*, Bytes, ImmutPath, TypstFileId}; -// todo: remove cfg feature here -#[cfg(feature = "system-compile")] pub(crate) mod diag; +#[cfg(feature = "system-compile")] +pub use diag::ConsoleDiagReporter; #[cfg(feature = "system-watch")] pub(crate) mod watch; @@ -34,6 +34,7 @@ pub use compile::*; pub(crate) mod export; pub use export::*; +pub mod features; pub mod query; #[cfg(feature = "system-compile")] @@ -41,6 +42,8 @@ pub(crate) mod session; #[cfg(feature = "system-compile")] pub use session::*; +pub use self::{diag::DiagnosticFormat, features::FeatureSet}; + #[cfg(feature = "system-compile")] pub type CompileDriver = CompileDriverImpl; @@ -57,6 +60,84 @@ pub trait WorkspaceProvider { #[derive(Clone, Default)] pub struct CompileEnv { pub tracer: Option, + pub features: Arc, +} + +impl CompileEnv { + pub fn configure(mut self, feature_set: FeatureSet) -> Self { + self.features = Arc::new(feature_set); + self + } + + pub fn configure_shared(mut self, feature_set: Arc) -> Self { + self.features = feature_set; + self + } +} + +#[derive(Clone, Debug)] +pub enum CompileReport { + Stage(TypstFileId, &'static str, crate::Time), + CompileError(TypstFileId, EcoVec, instant::Duration), + ExportError(TypstFileId, EcoVec, instant::Duration), + CompileWarning(TypstFileId, EcoVec, instant::Duration), + CompileSuccess(TypstFileId, EcoVec, instant::Duration), +} + +impl CompileReport { + pub fn compiling_id(&self) -> TypstFileId { + match self { + Self::Stage(id, ..) + | Self::CompileError(id, ..) + | Self::ExportError(id, ..) + | Self::CompileWarning(id, ..) + | Self::CompileSuccess(id, ..) => *id, + } + } + + pub fn duration(&self) -> Option { + match self { + Self::Stage(..) => None, + Self::CompileError(_, _, dur) + | Self::ExportError(_, _, dur) + | Self::CompileWarning(_, _, dur) + | Self::CompileSuccess(_, _, dur) => Some(*dur), + } + } + + pub fn diagnostics(self) -> Option> { + match self { + Self::Stage(..) => None, + Self::CompileError(_, diagnostics, ..) + | Self::ExportError(_, diagnostics, ..) + | Self::CompileWarning(_, diagnostics, ..) + | Self::CompileSuccess(_, diagnostics, ..) => Some(diagnostics), + } + } + + /// Get the status message. + pub fn message(&self) -> CompileReportMsg<'_> { + CompileReportMsg(self) + } +} + +pub struct CompileReportMsg<'a>(&'a CompileReport); + +impl<'a> fmt::Display for CompileReportMsg<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use CompileReport::*; + + let input = self.0.compiling_id(); + match self.0 { + Stage(_, stage, ..) => writeln!(f, "{:?}: {} ...", input, stage), + CompileSuccess(_, _, duration) | CompileWarning(_, _, duration) => { + writeln!(f, "{:?}: Compilation succeeded in {:?}", input, duration) + } + CompileError(_, _, duration) | ExportError(_, _, duration) => { + writeln!(f, "{:?}: Compilation failed after {:?}", input, duration) + } + } + } } pub trait Compiler { @@ -108,7 +189,7 @@ pub trait Compiler { /// Iterate over the dependencies of found by the compiler. /// Note: reset the compiler will clear the dependencies cache. - fn iter_dependencies<'a>(&'a self, _f: &mut dyn FnMut(&'a ImmutPath, instant::SystemTime)) {} + fn iter_dependencies<'a>(&'a self, _f: &mut dyn FnMut(&'a ImmutPath, crate::Time)) {} fn notify_fs_event(&mut self, _event: FilesystemEvent) {} @@ -256,7 +337,7 @@ impl Compiler for T { } #[inline] - fn iter_dependencies<'a>(&'a self, f: &mut dyn FnMut(&'a ImmutPath, instant::SystemTime)) { + fn iter_dependencies<'a>(&'a self, f: &mut dyn FnMut(&'a ImmutPath, crate::Time)) { self.inner().iter_dependencies(f) } @@ -296,98 +377,6 @@ where } } -/// The status in which the watcher can be. -pub enum DiagStatus { - Stage(&'static str), - Success(std::time::Duration), - Error(std::time::Duration), -} - -pub trait DiagObserver { - /// Print diagnostic messages to the terminal. - fn print_diagnostics( - &self, - errors: ecow::EcoVec, - ) -> Result<(), codespan_reporting::files::Error>; - - /// Print status message to the terminal. - fn print_status(&self, status: DiagStatus); - - /// Run inner function with print (optional) status and diagnostics to the - /// terminal (for compilation). - #[deprecated(note = "use `with_stage_diag` instead")] - fn with_compile_diag( - &mut self, - f: impl FnOnce(&mut Self) -> SourceResult, - ) -> Option; - - /// Run inner function with print (optional) status and diagnostics to the - /// terminal. - fn with_stage_diag( - &mut self, - stage: &'static str, - f: impl FnOnce(&mut Self) -> SourceResult, - ) -> Option; -} - -#[cfg(feature = "system-compile")] -impl DiagObserver for C -where - C::World: for<'files> codespan_reporting::files::Files<'files, FileId = TypstFileId>, -{ - /// Print diagnostic messages to the terminal. - fn print_diagnostics( - &self, - errors: ecow::EcoVec, - ) -> Result<(), codespan_reporting::files::Error> { - diag::print_diagnostics(self.world(), errors) - } - - /// Print status message to the terminal. - fn print_status(&self, status: DiagStatus) { - if !WITH_STATUS { - return; - } - diag::status(self.main_id(), status).unwrap(); - } - - /// Run inner function with print (optional) status and diagnostics to the - /// terminal (for compilation). - fn with_compile_diag( - &mut self, - f: impl FnOnce(&mut Self) -> SourceResult, - ) -> Option { - self.with_stage_diag::("compiling", f) - } - - /// Run inner function with print (optional) status and diagnostics to the - /// terminal (for stages). - fn with_stage_diag( - &mut self, - stage: &'static str, - f: impl FnOnce(&mut Self) -> SourceResult, - ) -> Option { - self.print_status::(DiagStatus::Stage(stage)); - let start = instant::Instant::now(); - match f(self) { - Ok(val) => { - self.print_status::(DiagStatus::Success(start.elapsed())); - Some(val) - } - Err(errs) => { - self.print_status::(DiagStatus::Error(start.elapsed())); - let _err = self.print_diagnostics(errs); - // todo: log in browser compiler - #[cfg(feature = "system-compile")] - if _err.is_err() { - log::error!("failed to print diagnostics: {:?}", _err); - } - None - } - } - } -} - struct AtFile(TypstFileId); impl From for EcoString { diff --git a/compiler/src/service/watch.rs b/compiler/src/service/watch.rs index 494ad1c2..72cc2370 100644 --- a/compiler/src/service/watch.rs +++ b/compiler/src/service/watch.rs @@ -26,10 +26,7 @@ use crate::vfs::{ type WatcherPair = (RecommendedWatcher, mpsc::UnboundedReceiver); type NotifyEvent = notify::Result; type FileEntry = (/* key */ ImmutPath, /* value */ FileSnapshot); -type NotifyFilePair = FileResult<( - /* mtime */ instant::SystemTime, - /* content */ Bytes, -)>; +type NotifyFilePair = FileResult<(/* mtime */ crate::Time, /* content */ Bytes)>; /// The state of a watched file. /// diff --git a/compiler/src/time.rs b/compiler/src/time.rs index 298729d9..a7e92eff 100644 --- a/compiler/src/time.rs +++ b/compiler/src/time.rs @@ -1 +1 @@ -pub type SystemTime = instant::SystemTime; +pub use instant::SystemTime as Time; diff --git a/compiler/src/vfs/browser.rs b/compiler/src/vfs/browser.rs index d089db08..ba7bc0c2 100644 --- a/compiler/src/vfs/browser.rs +++ b/compiler/src/vfs/browser.rs @@ -5,7 +5,7 @@ use wasm_bindgen::prelude::*; use typst_ts_core::Bytes; -use crate::time::SystemTime; +use crate::Time; use super::AccessModel; @@ -20,12 +20,12 @@ pub struct ProxyAccessModel { impl AccessModel for ProxyAccessModel { type RealPath = PathBuf; - fn mtime(&self, src: &Path) -> FileResult { + fn mtime(&self, src: &Path) -> FileResult