From 0dd79bbad2f7eb8d5673317d982833b7a34a412a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20d=27Herbais=20de=20Thun?= Date: Tue, 10 Oct 2023 11:56:16 +0200 Subject: [PATCH] Add `raw.line` (#2341) --- crates/typst-library/src/text/raw.rs | 249 ++++++++++++++++++++++----- tests/ref/text/raw-line.png | Bin 0 -> 32488 bytes tests/typ/text/raw-line.typ | 109 ++++++++++++ 3 files changed, 312 insertions(+), 46 deletions(-) create mode 100644 tests/ref/text/raw-line.png create mode 100644 tests/typ/text/raw-line.typ diff --git a/crates/typst-library/src/text/raw.rs b/crates/typst-library/src/text/raw.rs index 8c121fa83..d16659beb 100644 --- a/crates/typst-library/src/text/raw.rs +++ b/crates/typst-library/src/text/raw.rs @@ -1,13 +1,15 @@ use std::hash::Hash; +use std::ops::Range; use std::sync::Arc; +use ecow::EcoVec; use once_cell::sync::Lazy; use once_cell::unsync::Lazy as UnsyncLazy; use syntect::highlighting as synt; use syntect::parsing::{SyntaxDefinition, SyntaxSet, SyntaxSetBuilder}; use typst::diag::FileError; use typst::eval::Bytes; -use typst::syntax::{self, LinkedNode}; +use typst::syntax::{self, is_newline, LinkedNode}; use typst::util::option_eq; use unicode_segmentation::UnicodeSegmentation; @@ -18,6 +20,10 @@ use crate::layout::BlockElem; use crate::meta::{Figurable, LocalName}; use crate::prelude::*; +// Shorthand for highlighter closures. +type StyleFn<'a> = &'a mut dyn FnMut(&LinkedNode, Range, synt::Style) -> Content; +type LineFn<'a> = &'a mut dyn FnMut(i64, Range, &mut Vec); + /// Raw text with optional syntax highlighting. /// /// Displays the text verbatim and in a monospace font. This is typically used @@ -58,6 +64,7 @@ use crate::prelude::*; /// the single backtick syntax. If your text should start or end with a /// backtick, put a space before or after it (it will be trimmed). #[elem( + scope, title = "Raw Text / Code", Synthesize, Show, @@ -239,6 +246,19 @@ pub struct RawElem { /// ```` #[default(2)] pub tab_size: usize, + + /// The stylized lines of raw text. + /// + /// Made accessible for the [`raw.line` element]($raw.line). + /// Allows more styling control in `show` rules. + #[synthesized] + pub lines: Vec, +} + +#[scope] +impl RawElem { + #[elem] + type RawLine; } impl RawElem { @@ -261,13 +281,7 @@ impl RawElem { impl Synthesize for RawElem { fn synthesize(&mut self, _vt: &mut Vt, styles: StyleChain) -> SourceResult<()> { self.push_lang(self.lang(styles)); - Ok(()) - } -} -impl Show for RawElem { - #[tracing::instrument(name = "RawElem::show", skip_all)] - fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { let mut text = self.text(); if text.contains('\t') { let tab_size = RawElem::tab_size_in(styles); @@ -292,24 +306,31 @@ impl Show for RawElem { let foreground = theme.settings.foreground.unwrap_or(synt::Color::BLACK); - let mut realized = if matches!(lang.as_deref(), Some("typ" | "typst" | "typc")) { + let mut seq = vec![]; + if matches!(lang.as_deref(), Some("typ" | "typst" | "typc")) { let root = match lang.as_deref() { Some("typc") => syntax::parse_code(&text), _ => syntax::parse(&text), }; - let mut seq = vec![]; - let highlighter = synt::Highlighter::new(theme); - highlight_themed( - &LinkedNode::new(&root), - vec![], - &highlighter, - &mut |node, style| { - seq.push(styled(&text[node.range()], foreground, style)); + ThemedHighlighter::new( + &text, + LinkedNode::new(&root), + synt::Highlighter::new(theme), + &mut |_, range, style| styled(&text[range], foreground, style), + &mut |i, range, line| { + seq.push( + RawLine::new( + i + 1, + text.split(is_newline).count() as i64, + EcoString::from(&text[range]), + Content::sequence(line.drain(..)), + ) + .pack(), + ); }, - ); - - Content::sequence(seq) + ) + .highlight(); } else if let Some((syntax_set, syntax)) = lang.and_then(|token| { SYNTAXES .find_syntax_by_token(&token) @@ -320,25 +341,49 @@ impl Show for RawElem { .map(|syntax| (&**extra_syntaxes, syntax)) }) }) { - let mut seq = vec![]; let mut highlighter = syntect::easy::HighlightLines::new(syntax, theme); + let len = text.lines().count(); for (i, line) in text.lines().enumerate() { - if i != 0 { - seq.push(LinebreakElem::new().pack()); - } - + let mut line_content = vec![]; for (style, piece) in highlighter.highlight_line(line, syntax_set).into_iter().flatten() { - seq.push(styled(piece, foreground, style)); + line_content.push(styled(piece, foreground, style)); } - } - Content::sequence(seq) + seq.push( + RawLine::new( + i as i64 + 1, + len as i64, + EcoString::from(line), + Content::sequence(line_content), + ) + .pack(), + ); + } } else { - TextElem::packed(text) + seq.extend(text.lines().map(TextElem::packed)); }; + self.push_lines(seq); + + Ok(()) + } +} + +impl Show for RawElem { + #[tracing::instrument(name = "RawElem::show", skip_all)] + fn show(&self, _: &mut Vt, styles: StyleChain) -> SourceResult { + let mut lines = EcoVec::with_capacity((2 * self.lines().len()).saturating_sub(1)); + for (i, line) in self.lines().into_iter().enumerate() { + if i != 0 { + lines.push(LinebreakElem::new().pack()); + } + + lines.push(line); + } + + let mut realized = Content::sequence(lines); if self.block(styles) { // Align the text before inserting it into the block. realized = realized.aligned(self.align(styles).into()); @@ -402,28 +447,140 @@ impl PlainText for RawElem { } } -/// Highlight a syntax node in a theme by calling `f` with ranges and their -/// styles. -fn highlight_themed( - node: &LinkedNode, +/// A highlighted line of raw text. +/// +/// This is a helper element that is synthesized by [`raw`]($raw) elements. +/// +/// It allows you to access various properties of the line, such as the line +/// number, the raw non-highlighted text, the highlighted text, and whether it +/// is the first or last line of the raw block. +#[elem(name = "line", title = "Raw Text / Code Line", Show, PlainText)] +pub struct RawLine { + /// The line number of the raw line inside of the raw block, starts at 1. + #[required] + pub number: i64, + + /// The total number of lines in the raw block. + #[required] + pub count: i64, + + /// The line of raw text. + #[required] + pub text: EcoString, + + /// The highlighted raw text. + #[required] + pub body: Content, +} + +impl Show for RawLine { + fn show(&self, _vt: &mut Vt, _styles: StyleChain) -> SourceResult { + Ok(self.body()) + } +} + +impl PlainText for RawLine { + fn plain_text(&self, text: &mut EcoString) { + text.push_str(&self.text()); + } +} + +/// Wrapper struct for the state required to highlight typst code. +struct ThemedHighlighter<'a> { + /// The code being highlighted. + code: &'a str, + /// The current node being highlighted. + node: LinkedNode<'a>, + /// The highlighter. + highlighter: synt::Highlighter<'a>, + /// The current scopes. scopes: Vec, - highlighter: &synt::Highlighter, - f: &mut F, -) where - F: FnMut(&LinkedNode, synt::Style), -{ - if node.children().len() == 0 { - let style = highlighter.style_for_stack(&scopes); - f(node, style); - return; + /// The current highlighted line. + current_line: Vec, + /// The range of the current line. + range: Range, + /// The current line number. + line: i64, + /// The function to style a piece of text. + style_fn: StyleFn<'a>, + /// The function to append a line. + line_fn: LineFn<'a>, +} + +impl<'a> ThemedHighlighter<'a> { + pub fn new( + code: &'a str, + top: LinkedNode<'a>, + highlighter: synt::Highlighter<'a>, + style_fn: StyleFn<'a>, + line_fn: LineFn<'a>, + ) -> Self { + Self { + code, + node: top, + highlighter, + range: 0..0, + scopes: Vec::new(), + current_line: Vec::new(), + line: 0, + style_fn, + line_fn, + } } - for child in node.children() { - let mut scopes = scopes.clone(); - if let Some(tag) = typst::syntax::highlight(&child) { - scopes.push(syntect::parsing::Scope::new(tag.tm_scope()).unwrap()) + pub fn highlight(&mut self) { + self.highlight_inner(); + + if !self.current_line.is_empty() { + (self.line_fn)( + self.line, + self.range.start..self.code.len(), + &mut self.current_line, + ); + + self.current_line.clear(); + } + } + + fn highlight_inner(&mut self) { + if self.node.children().len() == 0 { + let style = self.highlighter.style_for_stack(&self.scopes); + let segment = &self.code[self.node.range()]; + + let mut len = 0; + for (i, line) in segment.split(is_newline).enumerate() { + if i != 0 { + (self.line_fn)( + self.line, + self.range.start..self.range.end + len - 1, + &mut self.current_line, + ); + self.range.start = self.range.end + len; + self.line += 1; + } + + let offset = self.node.range().start + len; + let token_range = offset..(offset + line.len()); + self.current_line + .push((self.style_fn)(&self.node, token_range, style)); + + len += line.len() + 1; + } + + self.range.end += segment.len(); + } + + for child in self.node.children() { + let mut scopes = self.scopes.clone(); + if let Some(tag) = typst::syntax::highlight(&child) { + scopes.push(syntect::parsing::Scope::new(tag.tm_scope()).unwrap()) + } + + std::mem::swap(&mut scopes, &mut self.scopes); + self.node = child; + self.highlight_inner(); + std::mem::swap(&mut scopes, &mut self.scopes); } - highlight_themed(&child, scopes, highlighter, f); } } diff --git a/tests/ref/text/raw-line.png b/tests/ref/text/raw-line.png new file mode 100644 index 0000000000000000000000000000000000000000..b76eb8087ce8ab1be1f7e17f91785c5bca2bc292 GIT binary patch literal 32488 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 { + box(stack( + dir: ltr, + box(width: 15pt)[#it.number], + it.body, + )) + linebreak() +} + +```rs +fn main() { + println!("Hello, world!"); +} +``` + +--- +#set page(width: 200pt) +#show raw: it => stack(dir: ttb, ..it.lines) +#show raw.line: it => { + box( + width: 100%, + height: 1.75em, + inset: 0.25em, + fill: if calc.rem(it.number, 2) == 0 { + luma(90%) + } else { + white + }, + align(horizon, stack( + dir: ltr, + box(width: 15pt)[#it.number], + it.body, + )) + ) +} + +```typ +#show raw.line: block.with( + fill: luma(60%) +); + +Hello, world! + += A heading for good measure +``` + +--- +#set page(width: 200pt) +#show raw.line: set text(fill: red) + +```py +import numpy as np + +def f(x): + return x**2 + +x = np.linspace(0, 10, 100) +y = f(x) + +print(x) +print(y) +``` + +--- +// Ref: false + +// Test line extraction works. + +#show raw: code => { + for i in code.lines { + test(i.count, 10) + } + + test(code.lines.at(0).text, "import numpy as np") + test(code.lines.at(1).text, "") + test(code.lines.at(2).text, "def f(x):") + test(code.lines.at(3).text, " return x**2") + test(code.lines.at(4).text, "") + test(code.lines.at(5).text, "x = np.linspace(0, 10, 100)") + test(code.lines.at(6).text, "y = f(x)") + test(code.lines.at(7).text, "") + test(code.lines.at(8).text, "print(x)") + test(code.lines.at(9).text, "print(y)") + test(code.lines.at(10, default: none), none) +} + +```py +import numpy as np + +def f(x): + return x**2 + +x = np.linspace(0, 10, 100) +y = f(x) + +print(x) +print(y) +```