diff --git a/crates/typst-cli/src/terminal.rs b/crates/typst-cli/src/terminal.rs index cf2906eb7..45b8c82be 100644 --- a/crates/typst-cli/src/terminal.rs +++ b/crates/typst-cli/src/terminal.rs @@ -1,15 +1,16 @@ use std::io::{self, IsTerminal, Write}; use codespan_reporting::term::termcolor; -use once_cell::sync::Lazy; use termcolor::{ColorChoice, WriteColor}; +use typst::utils::singleton; use crate::ARGS; /// Returns a handle to the optionally colored terminal output. pub fn out() -> TermOut { - static OUTPUT: Lazy = Lazy::new(TermOutInner::new); - TermOut { inner: &OUTPUT } + TermOut { + inner: singleton!(TermOutInner, TermOutInner::new()), + } } /// The stuff that has to be shared between instances of [`TermOut`]. diff --git a/crates/typst-ide/src/lib.rs b/crates/typst-ide/src/lib.rs index 403a36ba0..63ba6f75f 100644 --- a/crates/typst-ide/src/lib.rs +++ b/crates/typst-ide/src/lib.rs @@ -97,13 +97,12 @@ fn summarize_font_family<'a>(variants: impl Iterator) -> Ec #[cfg(test)] mod tests { - use once_cell::sync::Lazy; use typst::diag::{FileError, FileResult}; use typst::foundations::{Bytes, Datetime, Smart}; use typst::layout::{Abs, Margin, PageElem}; use typst::syntax::{FileId, Source}; use typst::text::{Font, FontBook, TextElem, TextSize}; - use typst::utils::LazyHash; + use typst::utils::{singleton, LazyHash}; use typst::{Library, World}; /// A world for IDE testing. @@ -118,15 +117,16 @@ mod tests { /// This is cheap because the shared base for all test runs is lazily /// initialized just once. pub fn new(text: &str) -> Self { - static BASE: Lazy = Lazy::new(TestBase::default); let main = Source::detached(text); - Self { main, base: &*BASE } + Self { + main, + base: singleton!(TestBase, TestBase::default()), + } } /// The ID of the main file in a `TestWorld`. pub fn main_id() -> FileId { - static ID: Lazy = Lazy::new(|| Source::detached("").id()); - *ID + *singleton!(FileId, Source::detached("").id()) } } diff --git a/crates/typst-utils/src/lib.rs b/crates/typst-utils/src/lib.rs index 754fc70d7..4d5f8e0c3 100644 --- a/crates/typst-utils/src/lib.rs +++ b/crates/typst-utils/src/lib.rs @@ -25,6 +25,9 @@ use std::sync::Arc; use siphasher::sip128::{Hasher128, SipHasher13}; +#[doc(hidden)] +pub use once_cell; + /// Turn a closure into a struct implementing [`Debug`]. pub fn debug(f: F) -> impl Debug where diff --git a/crates/typst-utils/src/macros.rs b/crates/typst-utils/src/macros.rs index dfe0c3190..dd60a2e0f 100644 --- a/crates/typst-utils/src/macros.rs +++ b/crates/typst-utils/src/macros.rs @@ -1,8 +1,18 @@ +/// Create a lazy initialized, globally unique `'static` reference to a value. +#[macro_export] +macro_rules! singleton { + ($ty:ty, $value:expr) => {{ + static VALUE: $crate::once_cell::sync::Lazy<$ty> = + $crate::once_cell::sync::Lazy::new(|| $value); + &*VALUE + }}; +} + /// Implement the `Sub` trait based on existing `Neg` and `Add` impls. #[macro_export] macro_rules! sub_impl { ($a:ident - $b:ident -> $c:ident) => { - impl std::ops::Sub<$b> for $a { + impl ::core::ops::Sub<$b> for $a { type Output = $c; fn sub(self, other: $b) -> $c { @@ -16,7 +26,7 @@ macro_rules! sub_impl { #[macro_export] macro_rules! assign_impl { ($a:ident += $b:ident) => { - impl std::ops::AddAssign<$b> for $a { + impl ::core::ops::AddAssign<$b> for $a { fn add_assign(&mut self, other: $b) { *self = *self + other; } @@ -24,7 +34,7 @@ macro_rules! assign_impl { }; ($a:ident -= $b:ident) => { - impl std::ops::SubAssign<$b> for $a { + impl ::core::ops::SubAssign<$b> for $a { fn sub_assign(&mut self, other: $b) { *self = *self - other; } @@ -32,7 +42,7 @@ macro_rules! assign_impl { }; ($a:ident *= $b:ident) => { - impl std::ops::MulAssign<$b> for $a { + impl ::core::ops::MulAssign<$b> for $a { fn mul_assign(&mut self, other: $b) { *self = *self * other; } @@ -40,7 +50,7 @@ macro_rules! assign_impl { }; ($a:ident /= $b:ident) => { - impl std::ops::DivAssign<$b> for $a { + impl ::core::ops::DivAssign<$b> for $a { fn div_assign(&mut self, other: $b) { *self = *self / other; } diff --git a/crates/typst/src/eval/markup.rs b/crates/typst/src/eval/markup.rs index b44c97897..46abf8ce4 100644 --- a/crates/typst/src/eval/markup.rs +++ b/crates/typst/src/eval/markup.rs @@ -83,7 +83,7 @@ impl Eval for ast::Space<'_> { type Output = Content; fn eval(self, _: &mut Vm) -> SourceResult { - Ok(SpaceElem::new().pack()) + Ok(SpaceElem::shared().clone()) } } @@ -91,7 +91,7 @@ impl Eval for ast::Linebreak<'_> { type Output = Content; fn eval(self, _: &mut Vm) -> SourceResult { - Ok(LinebreakElem::new().pack()) + Ok(LinebreakElem::shared().clone()) } } @@ -99,7 +99,7 @@ impl Eval for ast::Parbreak<'_> { type Output = Content; fn eval(self, _: &mut Vm) -> SourceResult { - Ok(ParbreakElem::new().pack()) + Ok(ParbreakElem::shared().clone()) } } diff --git a/crates/typst/src/eval/math.rs b/crates/typst/src/eval/math.rs index 42528d618..d1a667a58 100644 --- a/crates/typst/src/eval/math.rs +++ b/crates/typst/src/eval/math.rs @@ -39,7 +39,7 @@ impl Eval for ast::MathAlignPoint<'_> { type Output = Content; fn eval(self, _: &mut Vm) -> SourceResult { - Ok(AlignPointElem::new().pack()) + Ok(AlignPointElem::shared().clone()) } } diff --git a/crates/typst/src/foundations/func.rs b/crates/typst/src/foundations/func.rs index 7cf6f5674..87f3dc4c7 100644 --- a/crates/typst/src/foundations/func.rs +++ b/crates/typst/src/foundations/func.rs @@ -12,7 +12,7 @@ use crate::foundations::{ Selector, Type, Value, }; use crate::syntax::{ast, Span, SyntaxNode}; -use crate::utils::{LazyHash, Static}; +use crate::utils::{singleton, LazyHash, Static}; #[doc(inline)] pub use typst_macros::func; @@ -216,11 +216,11 @@ impl Func { /// Get details about the function's return type. pub fn returns(&self) -> Option<&'static CastInfo> { - static CONTENT: Lazy = - Lazy::new(|| CastInfo::Type(Type::of::())); match &self.repr { Repr::Native(native) => Some(&native.0.returns), - Repr::Element(_) => Some(&CONTENT), + Repr::Element(_) => { + Some(singleton!(CastInfo, CastInfo::Type(Type::of::()))) + } Repr::Closure(_) => None, Repr::With(with) => with.0.returns(), } diff --git a/crates/typst/src/math/align.rs b/crates/typst/src/math/align.rs index 467f7ac97..41580bf9c 100644 --- a/crates/typst/src/math/align.rs +++ b/crates/typst/src/math/align.rs @@ -1,12 +1,20 @@ use crate::diag::SourceResult; -use crate::foundations::{elem, Packed, StyleChain}; +use crate::foundations::{elem, Content, NativeElement, Packed, StyleChain}; use crate::layout::Abs; use crate::math::{LayoutMath, MathContext, MathFragment, MathRun}; +use crate::utils::singleton; /// A math alignment point: `&`, `&&`. #[elem(title = "Alignment Point", LayoutMath)] pub struct AlignPointElem {} +impl AlignPointElem { + /// Get the globally shared alignment point element. + pub fn shared() -> &'static Content { + singleton!(Content, AlignPointElem::new().pack()) + } +} + impl LayoutMath for Packed { fn layout_math(&self, ctx: &mut MathContext, _: StyleChain) -> SourceResult<()> { ctx.push(MathFragment::Align); diff --git a/crates/typst/src/model/heading.rs b/crates/typst/src/model/heading.rs index 6f126ff53..74a0a036c 100644 --- a/crates/typst/src/model/heading.rs +++ b/crates/typst/src/model/heading.rs @@ -331,7 +331,7 @@ impl Outlinable for Packed { styles, numbering, )?; - content = numbers + SpaceElem::new().pack() + content; + content = numbers + SpaceElem::shared().clone() + content; }; Ok(Some(content)) diff --git a/crates/typst/src/model/outline.rs b/crates/typst/src/model/outline.rs index ec1e5f1b8..b90c4e3b6 100644 --- a/crates/typst/src/model/outline.rs +++ b/crates/typst/src/model/outline.rs @@ -190,7 +190,7 @@ impl OutlineElem { impl Show for Packed { #[typst_macros::time(name = "outline", span = self.span())] fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult { - let mut seq = vec![ParbreakElem::new().pack()]; + let mut seq = vec![ParbreakElem::shared().clone()]; // Build the outline title. if let Some(title) = self.title(styles).unwrap_or_else(|| { Some(TextElem::packed(Self::local_name_in(styles)).spanned(self.span())) @@ -247,12 +247,12 @@ impl Show for Packed { // Add the overridable outline entry, followed by a line break. seq.push(entry.pack()); - seq.push(LinebreakElem::new().pack()); + seq.push(LinebreakElem::shared().clone()); ancestors.push(elem); } - seq.push(ParbreakElem::new().pack()); + seq.push(ParbreakElem::shared().clone()); Ok(Content::sequence(seq)) } @@ -325,13 +325,13 @@ impl OutlineIndent { numbering, )?; - hidden += numbers + SpaceElem::new().pack(); + hidden += numbers + SpaceElem::shared().clone(); }; } if !ancestors.is_empty() { seq.push(HideElem::new(hidden).pack()); - seq.push(SpaceElem::new().pack()); + seq.push(SpaceElem::shared().clone()); } } @@ -508,7 +508,7 @@ impl Show for Packed { // Add filler symbols between the section name and page number. if let Some(filler) = self.fill() { - seq.push(SpaceElem::new().pack()); + seq.push(SpaceElem::shared().clone()); seq.push( BoxElem::new() .with_body(Some(filler.clone())) @@ -516,7 +516,7 @@ impl Show for Packed { .pack() .spanned(self.span()), ); - seq.push(SpaceElem::new().pack()); + seq.push(SpaceElem::shared().clone()); } else { seq.push(HElem::new(Fr::one().into()).pack()); } diff --git a/crates/typst/src/model/par.rs b/crates/typst/src/model/par.rs index 4d9f815e0..dc41297ab 100644 --- a/crates/typst/src/model/par.rs +++ b/crates/typst/src/model/par.rs @@ -7,6 +7,7 @@ use crate::foundations::{ }; use crate::layout::{Em, Length}; use crate::realize::StyleVec; +use crate::utils::singleton; /// Arranges text, spacing and inline-level elements into a paragraph. /// @@ -150,9 +151,9 @@ impl Construct for ParElem { let styles = Self::set(engine, args)?; let body = args.expect::("body")?; Ok(Content::sequence([ - ParbreakElem::new().pack(), + ParbreakElem::shared().clone(), body.styled_with_map(styles), - ParbreakElem::new().pack(), + ParbreakElem::shared().clone(), ])) } } @@ -197,4 +198,11 @@ pub enum Linebreaks { #[elem(title = "Paragraph Break", Unlabellable)] pub struct ParbreakElem {} +impl ParbreakElem { + /// Get the globally shared paragraph element. + pub fn shared() -> &'static Content { + singleton!(Content, ParbreakElem::new().pack()) + } +} + impl Unlabellable for Packed {} diff --git a/crates/typst/src/model/quote.rs b/crates/typst/src/model/quote.rs index c35d895fe..65a809dca 100644 --- a/crates/typst/src/model/quote.rs +++ b/crates/typst/src/model/quote.rs @@ -189,7 +189,7 @@ impl Show for Packed { .spanned(self.span()); if let Some(attribution) = self.attribution(styles).as_ref() { - let mut seq = vec![TextElem::packed('—'), SpaceElem::new().pack()]; + let mut seq = vec![TextElem::packed('—'), SpaceElem::shared().clone()]; match attribution { Attribution::Content(content) => { @@ -213,7 +213,7 @@ impl Show for Packed { realized = PadElem::new(realized).pack(); } else if let Some(Attribution::Label(label)) = self.attribution(styles) { - realized += SpaceElem::new().pack() + realized += SpaceElem::shared().clone() + CiteElem::new(*label).pack().spanned(self.span()); } diff --git a/crates/typst/src/text/linebreak.rs b/crates/typst/src/text/linebreak.rs index 23349e253..ea54da98b 100644 --- a/crates/typst/src/text/linebreak.rs +++ b/crates/typst/src/text/linebreak.rs @@ -1,5 +1,6 @@ -use crate::foundations::{elem, Packed}; +use crate::foundations::{elem, Content, NativeElement, Packed}; use crate::realize::{Behave, Behaviour}; +use crate::utils::singleton; /// Inserts a line break. /// @@ -37,6 +38,13 @@ pub struct LinebreakElem { pub justify: bool, } +impl LinebreakElem { + /// Get the globally shared linebreak element. + pub fn shared() -> &'static Content { + singleton!(Content, LinebreakElem::new().pack()) + } +} + impl Behave for Packed { fn behaviour(&self) -> Behaviour { Behaviour::Destructive diff --git a/crates/typst/src/text/raw.rs b/crates/typst/src/text/raw.rs index fcf59c255..8132688ac 100644 --- a/crates/typst/src/text/raw.rs +++ b/crates/typst/src/text/raw.rs @@ -440,7 +440,7 @@ impl Show for Packed { let mut seq = EcoVec::with_capacity((2 * lines.len()).saturating_sub(1)); for (i, line) in lines.iter().enumerate() { if i != 0 { - seq.push(LinebreakElem::new().pack()); + seq.push(LinebreakElem::shared().clone()); } seq.push(line.clone().pack()); diff --git a/crates/typst/src/text/space.rs b/crates/typst/src/text/space.rs index 114a2e80d..b2afdc355 100644 --- a/crates/typst/src/text/space.rs +++ b/crates/typst/src/text/space.rs @@ -1,12 +1,22 @@ use ecow::EcoString; -use crate::foundations::{elem, Packed, PlainText, Repr, Unlabellable}; +use crate::foundations::{ + elem, Content, NativeElement, Packed, PlainText, Repr, Unlabellable, +}; use crate::realize::{Behave, Behaviour}; +use crate::utils::singleton; /// A text space. #[elem(Behave, Unlabellable, PlainText, Repr)] pub struct SpaceElem {} +impl SpaceElem { + /// Get the globally shared space element. + pub fn shared() -> &'static Content { + singleton!(Content, SpaceElem::new().pack()) + } +} + impl Repr for SpaceElem { fn repr(&self) -> EcoString { "[ ]".into() diff --git a/crates/typst/src/visualize/color.rs b/crates/typst/src/visualize/color.rs index 52706a96e..e8ae4f900 100644 --- a/crates/typst/src/visualize/color.rs +++ b/crates/typst/src/visualize/color.rs @@ -211,11 +211,7 @@ pub enum Color { #[scope] impl Color { /// The module of preset color maps. - pub const MAP: fn() -> Module = || { - // Lazy to avoid re-allocating. - static MODULE: Lazy = Lazy::new(map); - MODULE.clone() - }; + pub const MAP: fn() -> Module = || crate::utils::singleton!(Module, map()).clone(); pub const BLACK: Self = Self::Luma(Luma::new(0.0, 1.0)); pub const GRAY: Self = Self::Luma(Luma::new(0.6666666, 1.0)); diff --git a/tests/src/world.rs b/tests/src/world.rs index 47b77d7e5..799527fad 100644 --- a/tests/src/world.rs +++ b/tests/src/world.rs @@ -5,14 +5,13 @@ use std::io::Write; use std::path::{Path, PathBuf}; use std::sync::OnceLock; -use once_cell::sync::Lazy; use parking_lot::Mutex; use typst::diag::{bail, FileError, FileResult, StrResult}; use typst::foundations::{func, Bytes, Datetime, NoneValue, Repr, Smart, Value}; use typst::layout::{Abs, Margin, PageElem}; use typst::syntax::{FileId, Source}; use typst::text::{Font, FontBook, TextElem, TextSize}; -use typst::utils::LazyHash; +use typst::utils::{singleton, LazyHash}; use typst::visualize::Color; use typst::{Library, World}; @@ -29,8 +28,10 @@ impl TestWorld { /// This is cheap because the shared base for all test runs is lazily /// initialized just once. pub fn new(source: Source) -> Self { - static BASE: Lazy = Lazy::new(TestBase::default); - Self { main: source, base: &*BASE } + Self { + main: source, + base: singleton!(TestBase, TestBase::default()), + } } }