fix(typst): fix paragraph break spans (#2080)

* refactor(core): rename `EMPTY` to `ZERO` in `Span`

Using `EMPTY` to represent a `Span` of `0..0` is confusing. A span that
starts and ends at 0 is always empty, but not all empty spans start and
end at 0.

* feat(core): add constructor `Span::empty()`

* fix(typst): fix paragraph break spans

Fix bug which caused paragraph break tokens to always be positioned at
the beginning of the document.

Fixes #2070
This commit is contained in:
Steve Kurch
2025-10-14 15:54:57 -05:00
committed by GitHub
parent 8131d60e0a
commit 62629d856f
2 changed files with 18 additions and 11 deletions

View File

@@ -29,12 +29,8 @@ pub struct Span<T> {
}
impl<T> Span<T> {
/// An empty [`Span`].
pub const EMPTY: Self = Self {
start: 0,
end: 0,
span_type: PhantomData,
};
/// A [`Span`] with a start and end index of 0.
pub const ZERO: Self = Self::empty(0);
/// Creates a new [`Span`] with the provided start and end indices.
///
@@ -61,6 +57,15 @@ impl<T> Span<T> {
}
}
/// Creates a new empty [`Span`] with the provided position.
pub const fn empty(pos: usize) -> Self {
Self {
start: pos,
end: pos,
span_type: PhantomData,
}
}
/// The length of the [`Span`].
pub fn len(&self) -> usize {
self.end - self.start
@@ -179,7 +184,7 @@ impl Span<Token> {
/// this span is required.
pub fn to_char_span(&self, source_document_tokens: &[Token]) -> Span<char> {
if self.is_empty() {
Span::EMPTY
Span::ZERO
} else {
let target_tokens = &source_document_tokens[self.start..self.end];
Span::new(
@@ -263,7 +268,7 @@ mod tests {
let doc = Document::new_plain_english_curated("Hello world!");
// Empty span.
let token_span = Span::EMPTY;
let token_span = Span::ZERO;
let converted = token_span.to_char_span(doc.get_tokens());
assert!(converted.is_empty());

View File

@@ -152,15 +152,17 @@ impl<'a> TypstTranslator<'a> {
};
}
fn parbreak() -> Option<Vec<Token>> {
fn parbreak(pos: usize) -> Option<Vec<Token>> {
Some(vec![Token {
span: harper_core::Span::EMPTY,
span: harper_core::Span::empty(pos),
kind: TokenKind::ParagraphBreak,
}])
}
fn isolate(inner: Option<Vec<Token>>) -> Option<Vec<Token>> {
merge![parbreak(), inner, parbreak()]
let start = inner.as_ref()?.first().map_or(0, |token| token.span.start);
let end = inner.as_ref()?.last().map_or(0, |token| token.span.end);
merge![parbreak(start), inner, parbreak(end)]
}
// Recurse on each element of an iterator