summaryrefslogtreecommitdiff
path: root/library/src/text/raw.rs
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-11-03 11:44:53 +0100
committerLaurenz <laurmaedje@gmail.com>2022-11-03 13:35:39 +0100
commit37a7afddfaffd44cb9bc013c9506599267e08983 (patch)
tree20e7d62d3c5418baff01a21d0406b91bf3096214 /library/src/text/raw.rs
parent56342bd972a13ffe21beaf2b87ab7eb1597704b4 (diff)
Split crates
Diffstat (limited to 'library/src/text/raw.rs')
-rw-r--r--library/src/text/raw.rs206
1 files changed, 206 insertions, 0 deletions
diff --git a/library/src/text/raw.rs b/library/src/text/raw.rs
new file mode 100644
index 00000000..31f1517e
--- /dev/null
+++ b/library/src/text/raw.rs
@@ -0,0 +1,206 @@
+use once_cell::sync::Lazy;
+use syntect::easy::HighlightLines;
+use syntect::highlighting::{
+ Color, FontStyle, Style, StyleModifier, Theme, ThemeItem, ThemeSettings,
+};
+use syntect::parsing::SyntaxSet;
+use typst::syntax;
+
+use super::{FontFamily, Hyphenate, LinebreakNode, TextNode};
+use crate::layout::{BlockNode, BlockSpacing};
+use crate::prelude::*;
+
+/// Monospaced text with optional syntax highlighting.
+#[derive(Debug, Hash)]
+pub struct RawNode {
+ /// The raw text.
+ pub text: EcoString,
+ /// Whether the node is block-level.
+ pub block: bool,
+}
+
+#[node(Show)]
+impl RawNode {
+ /// The language to syntax-highlight in.
+ #[property(referenced)]
+ pub const LANG: Option<EcoString> = None;
+ /// The raw text's font family.
+ #[property(referenced)]
+ pub const FAMILY: FontFamily = FontFamily::new("IBM Plex Mono");
+ /// The spacing above block-level raw.
+ #[property(resolve, shorthand(around))]
+ pub const ABOVE: Option<BlockSpacing> = Some(Ratio::one().into());
+ /// The spacing below block-level raw.
+ #[property(resolve, shorthand(around))]
+ pub const BELOW: Option<BlockSpacing> = Some(Ratio::one().into());
+
+ fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
+ Ok(Self {
+ text: args.expect("text")?,
+ block: args.named("block")?.unwrap_or(false),
+ }
+ .pack())
+ }
+}
+
+impl Show for RawNode {
+ fn unguard_parts(&self, _: Selector) -> Content {
+ Self { text: self.text.clone(), ..*self }.pack()
+ }
+
+ fn field(&self, name: &str) -> Option<Value> {
+ match name {
+ "text" => Some(Value::Str(self.text.clone().into())),
+ "block" => Some(Value::Bool(self.block)),
+ _ => None,
+ }
+ }
+
+ fn realize(
+ &self,
+ _: Tracked<dyn World>,
+ styles: StyleChain,
+ ) -> SourceResult<Content> {
+ let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase());
+ let foreground = THEME
+ .settings
+ .foreground
+ .map(Color::from)
+ .unwrap_or(Color::BLACK)
+ .into();
+
+ let mut realized = if matches!(lang.as_deref(), Some("typ" | "typst" | "typc")) {
+ let root = match lang.as_deref() {
+ Some("typc") => syntax::parse_code(&self.text),
+ _ => syntax::parse(&self.text),
+ };
+
+ let mut seq = vec![];
+ syntax::highlight::highlight_themed(&root, &THEME, |range, style| {
+ seq.push(styled(&self.text[range], foreground, style));
+ });
+
+ Content::sequence(seq)
+ } else if let Some(syntax) =
+ lang.and_then(|token| SYNTAXES.find_syntax_by_token(&token))
+ {
+ let mut seq = vec![];
+ let mut highlighter = HighlightLines::new(syntax, &THEME);
+ for (i, line) in self.text.lines().enumerate() {
+ if i != 0 {
+ seq.push(LinebreakNode { justify: false }.pack());
+ }
+
+ for (style, piece) in
+ highlighter.highlight_line(line, &SYNTAXES).into_iter().flatten()
+ {
+ seq.push(styled(piece, foreground, style));
+ }
+ }
+
+ Content::sequence(seq)
+ } else {
+ TextNode(self.text.clone()).pack()
+ };
+
+ if self.block {
+ realized = BlockNode(realized).pack();
+ }
+
+ let mut map = StyleMap::new();
+ map.set(TextNode::OVERHANG, false);
+ map.set(TextNode::HYPHENATE, Hyphenate(Smart::Custom(false)));
+ map.set(TextNode::SMART_QUOTES, false);
+
+ Ok(realized.styled_with_map(map))
+ }
+
+ fn finalize(
+ &self,
+ _: Tracked<dyn World>,
+ styles: StyleChain,
+ mut realized: Content,
+ ) -> SourceResult<Content> {
+ let mut map = StyleMap::new();
+ map.set_family(styles.get(Self::FAMILY).clone(), styles);
+
+ if self.block {
+ realized = realized.spaced(styles.get(Self::ABOVE), styles.get(Self::BELOW));
+ }
+
+ Ok(realized.styled_with_map(map))
+ }
+}
+
+/// Style a piece of text with a syntect style.
+fn styled(piece: &str, foreground: Paint, style: Style) -> Content {
+ let mut body = TextNode(piece.into()).pack();
+
+ let paint = style.foreground.into();
+ if paint != foreground {
+ body = body.styled(TextNode::FILL, paint);
+ }
+
+ if style.font_style.contains(FontStyle::BOLD) {
+ body = body.strong();
+ }
+
+ if style.font_style.contains(FontStyle::ITALIC) {
+ body = body.emph();
+ }
+
+ if style.font_style.contains(FontStyle::UNDERLINE) {
+ body = body.underlined();
+ }
+
+ body
+}
+
+/// The syntect syntax definitions.
+static SYNTAXES: Lazy<SyntaxSet> = Lazy::new(|| SyntaxSet::load_defaults_newlines());
+
+/// The default theme used for syntax highlighting.
+#[rustfmt::skip]
+pub static THEME: Lazy<Theme> = Lazy::new(|| Theme {
+ name: Some("Typst Light".into()),
+ author: Some("The Typst Project Developers".into()),
+ settings: ThemeSettings::default(),
+ scopes: vec![
+ item("comment", Some("#8a8a8a"), None),
+ item("constant.character.escape", Some("#1d6c76"), None),
+ item("constant.character.shortcut", Some("#1d6c76"), None),
+ item("markup.bold", None, Some(FontStyle::BOLD)),
+ item("markup.italic", None, Some(FontStyle::ITALIC)),
+ item("markup.underline", None, Some(FontStyle::UNDERLINE)),
+ item("markup.raw", Some("#818181"), None),
+ item("string.other.math.typst", None, None),
+ item("punctuation.definition.math", Some("#298e0d"), None),
+ item("keyword.operator.math", Some("#1d6c76"), None),
+ item("markup.heading, entity.name.section", None, Some(FontStyle::BOLD)),
+ item("markup.heading.typst", None, Some(FontStyle::BOLD | FontStyle::UNDERLINE)),
+ item("punctuation.definition.list", Some("#8b41b1"), None),
+ item("markup.list.term", None, Some(FontStyle::BOLD)),
+ item("entity.name.label, markup.other.reference", Some("#1d6c76"), None),
+ item("keyword, constant.language, variable.language", Some("#d73a49"), None),
+ item("storage.type, storage.modifier", Some("#d73a49"), None),
+ item("constant", Some("#b60157"), None),
+ item("string", Some("#298e0d"), None),
+ item("entity.name, variable.function, support", Some("#4b69c6"), None),
+ item("support.macro", Some("#16718d"), None),
+ item("meta.annotation", Some("#301414"), None),
+ item("entity.other, meta.interpolation", Some("#8b41b1"), None),
+ item("invalid", Some("#ff0000"), None),
+ ],
+});
+
+/// Create a syntect theme item.
+fn item(scope: &str, color: Option<&str>, font_style: Option<FontStyle>) -> ThemeItem {
+ ThemeItem {
+ scope: scope.parse().unwrap(),
+ style: StyleModifier {
+ foreground: color.map(|s| s.parse::<RgbaColor>().unwrap().into()),
+ background: None,
+ font_style,
+ },
+ }
+}