diff --git a/fonts/IBMPlexMono-Bold.ttf b/fonts/IBMPlexMono-Bold.ttf new file mode 100644 index 000000000..2ad2fa1d8 Binary files /dev/null and b/fonts/IBMPlexMono-Bold.ttf differ diff --git a/src/eval/func.rs b/src/eval/func.rs index 7e4040b6a..b72e9f184 100644 --- a/src/eval/func.rs +++ b/src/eval/func.rs @@ -1,4 +1,4 @@ -use std::fmt::{self, Debug, Formatter, Write}; +use std::fmt::{self, Debug, Formatter}; use std::hash::{Hash, Hasher}; use std::sync::Arc; @@ -119,12 +119,10 @@ impl Func { impl Debug for Func { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - f.write_str(" f.write_str(name), + None => f.write_str("(..) => {..}"), } - f.write_char('>') } } diff --git a/src/eval/value.rs b/src/eval/value.rs index fc54cbceb..b5607cfdd 100644 --- a/src/eval/value.rs +++ b/src/eval/value.rs @@ -122,8 +122,9 @@ impl Value { Value::Content(v) => v, // For values which can't be shown "naturally", we return the raw - // representation. - v => Content::show(RawNode { text: v.repr(), block: false }), + // representation with typst code syntax highlighting. + v => Content::show(RawNode { text: v.repr(), block: false }) + .styled(RawNode::LANG, Some("typc".into())), } } } @@ -149,7 +150,7 @@ impl Debug for Value { Self::Fraction(v) => Debug::fmt(v, f), Self::Color(v) => Debug::fmt(v, f), Self::Str(v) => Debug::fmt(v, f), - Self::Content(_) => f.pad(""), + Self::Content(_) => f.pad("[...]"), Self::Array(v) => Debug::fmt(v, f), Self::Dict(v) => Debug::fmt(v, f), Self::Func(v) => Debug::fmt(v, f), @@ -720,7 +721,10 @@ mod tests { "30% + 56.69pt", ); test(Fraction::one() * 7.55, "7.55fr"); - test(Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), "#010101"); + test( + Color::Rgba(RgbaColor::new(1, 1, 1, 0xff)), + "rgb(\"#010101\")", + ); // Collections. test("hello", r#""hello""#); @@ -734,13 +738,9 @@ mod tests { test(dict!["one" => 1], "(one: 1)"); test(dict!["two" => false, "one" => 1], "(one: 1, two: false)"); - // Functions. - test( - Func::from_fn("nil", |_, _| Ok(Value::None)), - "", - ); - - // Dynamics. + // Functions, content and dynamics. + test(Content::Text("a".into()), "[...]"); + test(Func::from_fn("nil", |_, _| Ok(Value::None)), "nil"); test(Dynamic::new(1), "1"); } } diff --git a/src/geom/paint.rs b/src/geom/paint.rs index 351ef443d..9b310249d 100644 --- a/src/geom/paint.rs +++ b/src/geom/paint.rs @@ -154,7 +154,7 @@ impl FromStr for RgbaColor { impl From for RgbaColor { fn from(color: SynColor) -> Self { - Self::new(color.r, color.b, color.g, color.a) + Self::new(color.r, color.g, color.b, color.a) } } @@ -167,10 +167,11 @@ impl Debug for RgbaColor { self.r, self.g, self.b, self.a, )?; } else { - write!(f, "#{:02x}{:02x}{:02x}", self.r, self.g, self.b)?; + write!(f, "rgb(\"#{:02x}{:02x}{:02x}", self.r, self.g, self.b)?; if self.a != 255 { write!(f, "{:02x}", self.a)?; } + write!(f, "\")")?; } Ok(()) } diff --git a/src/library/text/raw.rs b/src/library/text/raw.rs index c711aa16d..d5a3aa950 100644 --- a/src/library/text/raw.rs +++ b/src/library/text/raw.rs @@ -1,20 +1,17 @@ +use std::sync::Arc; + use once_cell::sync::Lazy; use syntect::easy::HighlightLines; -use syntect::highlighting::{FontStyle, Highlighter, Style, Theme, ThemeSet}; +use syntect::highlighting::{ + Color, FontStyle, Highlighter, Style, StyleModifier, Theme, ThemeItem, ThemeSettings, +}; use syntect::parsing::SyntaxSet; use super::{FontFamily, Hyphenate, TextNode, Toggle}; use crate::library::layout::BlockSpacing; use crate::library::prelude::*; use crate::source::SourceId; -use crate::syntax::{self, RedNode}; - -/// The lazily-loaded theme used for syntax highlighting. -static THEME: Lazy = - Lazy::new(|| ThemeSet::load_defaults().themes.remove("InspiredGitHub").unwrap()); - -/// The lazily-loaded syntect syntax definitions. -static SYNTAXES: Lazy = Lazy::new(|| SyntaxSet::load_defaults_newlines()); +use crate::syntax::{self, GreenNode, NodeKind, RedNode}; /// Monospaced text with optional syntax highlighting. #[derive(Debug, Hash)] @@ -63,7 +60,7 @@ impl Show for RawNode { } fn realize(&self, _: &mut Context, styles: StyleChain) -> TypResult { - let lang = styles.get(Self::LANG).as_ref(); + let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase()); let foreground = THEME .settings .foreground @@ -71,15 +68,19 @@ impl Show for RawNode { .unwrap_or(Color::BLACK) .into(); - let mut realized = if matches!( - lang.map(|s| s.to_lowercase()).as_deref(), - Some("typ" | "typst") - ) { - let mut seq = vec![]; - let green = crate::parse::parse(&self.text); - let red = RedNode::from_root(green, SourceId::from_raw(0)); + let mut realized = if matches!(lang.as_deref(), Some("typ" | "typst" | "typc")) { + let root = match lang.as_deref() { + Some("typc") => { + let children = crate::parse::parse_code(&self.text); + Arc::new(GreenNode::with_children(NodeKind::CodeBlock, children)) + } + _ => crate::parse::parse(&self.text), + }; + + let red = RedNode::from_root(root, SourceId::from_raw(0)); let highlighter = Highlighter::new(&THEME); + let mut seq = vec![]; syntax::highlight_syntect(red.as_ref(), &highlighter, &mut |range, style| { seq.push(styled(&self.text[range], foreground, style)); }); @@ -159,3 +160,44 @@ fn styled(piece: &str, foreground: Paint, style: Style) -> Content { body.styled_with_map(styles) } + +/// The lazily-loaded syntect syntax definitions. +static SYNTAXES: Lazy = Lazy::new(|| SyntaxSet::load_defaults_newlines()); + +/// The lazily-loaded theme used for syntax highlighting. +#[rustfmt::skip] +static THEME: Lazy = Lazy::new(|| Theme { + name: Some("Typst Light".into()), + author: Some("The Typst Project Developers".into()), + settings: ThemeSettings::default(), + scopes: vec![ + item("markup.bold", None, Some(FontStyle::BOLD)), + item("markup.italic", None, Some(FontStyle::ITALIC)), + item("markup.heading, entity.name.section", None, Some(FontStyle::BOLD)), + item("markup.raw", Some("#818181"), None), + item("markup.list", Some("#8b41b1"), None), + item("comment", Some("#8a8a8a"), None), + item("keyword, constant.language, variable.language", Some("#d73a49"), None), + item("storage.type, storage.modifier", Some("#d73a49"), None), + item("entity.other", Some("#8b41b1"), None), + item("entity.name, variable.function, support", Some("#4b69c6"), None), + item("support.macro", Some("#16718d"), None), + item("meta.annotation", Some("#301414"), None), + item("constant", Some("#b60157"), None), + item("string", Some("#298e0d"), None), + item("punctuation.shortcut", Some("#1d6c76"), None), + item("constant.character.escape", Some("#1d6c76"), None), + ], +}); + +/// Create a syntect theme item. +fn item(scope: &str, color: Option<&str>, font_style: Option) -> ThemeItem { + ThemeItem { + scope: scope.parse().unwrap(), + style: StyleModifier { + foreground: color.map(|s| s.parse().unwrap()), + background: None, + font_style, + }, + } +} diff --git a/src/parse/mod.rs b/src/parse/mod.rs index 9f4860e71..7811482be 100644 --- a/src/parse/mod.rs +++ b/src/parse/mod.rs @@ -26,6 +26,13 @@ pub fn parse(src: &str) -> Arc { } } +/// Parse code directly, only used for syntax highlighting. +pub fn parse_code(src: &str) -> Vec { + let mut p = Parser::new(src, TokenMode::Code); + code(&mut p); + p.finish() +} + /// Reparse a code block. /// /// Returns `Some` if all of the input was consumed. @@ -696,20 +703,25 @@ fn params(p: &mut Parser, marker: Marker) { fn code_block(p: &mut Parser) { p.perform(NodeKind::CodeBlock, |p| { p.start_group(Group::Brace); - while !p.eof() { - p.start_group(Group::Expr); - if expr(p).is_ok() && !p.eof() { - p.expected("semicolon or line break"); - } - p.end_group(); - - // Forcefully skip over newlines since the group's contents can't. - p.eat_while(|t| matches!(t, NodeKind::Space(_))); - } + code(p); p.end_group(); }); } +/// Parse expressions. +fn code(p: &mut Parser) { + while !p.eof() { + p.start_group(Group::Expr); + if expr(p).is_ok() && !p.eof() { + p.expected("semicolon or line break"); + } + p.end_group(); + + // Forcefully skip over newlines since the group's contents can't. + p.eat_while(|t| matches!(t, NodeKind::Space(_))); + } +} + // Parse a content block: `[...]`. fn content_block(p: &mut Parser) { p.perform(NodeKind::ContentBlock, |p| { diff --git a/src/syntax/highlight.rs b/src/syntax/highlight.rs index 8e62424f0..dae379acb 100644 --- a/src/syntax/highlight.rs +++ b/src/syntax/highlight.rs @@ -95,7 +95,7 @@ pub enum Category { /// A function. Function, /// An interpolated variable in markup. - Variable, + Interpolated, /// An invalid node. Invalid, } @@ -178,7 +178,7 @@ impl Category { NodeKind::None => Some(Category::None), NodeKind::Auto => Some(Category::Auto), NodeKind::Ident(_) => match parent.kind() { - NodeKind::Markup(_) => Some(Category::Variable), + NodeKind::Markup(_) => Some(Category::Interpolated), NodeKind::FuncCall => Some(Category::Function), NodeKind::MethodCall if i > 0 => Some(Category::Function), NodeKind::ClosureExpr if i == 0 => Some(Category::Function), @@ -263,7 +263,7 @@ impl Category { Self::Number => "constant.numeric.typst", Self::String => "string.quoted.double.typst", Self::Function => "entity.name.function.typst", - Self::Variable => "variable.parameter.typst", + Self::Interpolated => "entity.other.interpolated.typst", Self::Invalid => "invalid.typst", } } diff --git a/tests/ref/code/array.png b/tests/ref/code/array.png index 613aab97c..cbda8aee7 100644 Binary files a/tests/ref/code/array.png and b/tests/ref/code/array.png differ diff --git a/tests/ref/code/call.png b/tests/ref/code/call.png index eefc8caef..eaf77ad6c 100644 Binary files a/tests/ref/code/call.png and b/tests/ref/code/call.png differ diff --git a/tests/ref/code/dict.png b/tests/ref/code/dict.png index 43cf13703..28e44999d 100644 Binary files a/tests/ref/code/dict.png and b/tests/ref/code/dict.png differ diff --git a/tests/ref/code/repr.png b/tests/ref/code/repr.png index b6427518e..b8beb09ac 100644 Binary files a/tests/ref/code/repr.png and b/tests/ref/code/repr.png differ diff --git a/tests/ref/graphics/shape-rect.png b/tests/ref/graphics/shape-rect.png index 5ba78f97e..2c2c7d7c1 100644 Binary files a/tests/ref/graphics/shape-rect.png and b/tests/ref/graphics/shape-rect.png differ diff --git a/tests/ref/text/code.png b/tests/ref/text/code.png new file mode 100644 index 000000000..b012c2cf8 Binary files /dev/null and b/tests/ref/text/code.png differ diff --git a/tests/ref/text/edge.png b/tests/ref/text/edge.png index 0514d628e..817e33005 100644 Binary files a/tests/ref/text/edge.png and b/tests/ref/text/edge.png differ diff --git a/tests/ref/text/par.png b/tests/ref/text/par.png index ae00ab45f..6ee4aa9cf 100644 Binary files a/tests/ref/text/par.png and b/tests/ref/text/par.png differ diff --git a/tests/ref/text/raw.png b/tests/ref/text/raw.png index e13293a6f..2c101dcb9 100644 Binary files a/tests/ref/text/raw.png and b/tests/ref/text/raw.png differ diff --git a/tests/typ/code/array.typ b/tests/typ/code/array.typ index cd1631759..58b43ebf0 100644 --- a/tests/typ/code/array.typ +++ b/tests/typ/code/array.typ @@ -4,6 +4,8 @@ --- // Ref: true +#set page(width: 150pt) + // Empty. {()} diff --git a/tests/typ/code/call.typ b/tests/typ/code/call.typ index 55471774d..5b8b5b05c 100644 --- a/tests/typ/code/call.typ +++ b/tests/typ/code/call.typ @@ -13,7 +13,6 @@ #f(1)[2](3) // Don't parse this as a function. -// Should output ` (it)`. #test (it) #let f(body) = body diff --git a/tests/typ/code/repr.typ b/tests/typ/code/repr.typ index 8742f4139..4d7a6cd5a 100644 --- a/tests/typ/code/repr.typ +++ b/tests/typ/code/repr.typ @@ -25,23 +25,24 @@ {2.3fr} --- -// Colors. -#rgb("f7a20500") \ -{2pt + rgb("f7a20500")} +// Colors and strokes. +#set text(0.8em) +#rgb("f7a205") \ +{2pt + rgb("f7a205")} --- // Strings and escaping. -#repr("hi") \ +#raw(repr("hi"), lang: "typc") \ #repr("a\n[]\"\u{1F680}string") --- // Content. -#repr[*{"H" + "i"} there*] +#raw(repr[*{"H" + "i"} there*]) --- // Functions #let f(x) = x -{() => none} \ {f} \ -{rect} +{rect} \ +{() => none} \ diff --git a/tests/typ/text/code.typ b/tests/typ/text/code.typ new file mode 100644 index 000000000..4230dd873 --- /dev/null +++ b/tests/typ/text/code.typ @@ -0,0 +1,57 @@ +// Test code highlighting. + +--- +#set text(6pt) +```typ += Chapter 1 +#lorem(100) + +#let hi = "Hello World" +``` + +--- +#set page(width: 180pt) +#set text(6pt) + +```rust +/// A carefully designed state machine. +#[derive(Debug)] +enum State<'a> { A(u8), B(&'a str) } + +fn advance(state: State<'_>) -> State<'_> { + unimplemented!("state machine") +} +``` + +--- +#set text(6pt) +```py +import this + +def hi(): + print("Hi!") +``` + +--- +#set page(width: 180pt) +#set text(6pt) + +#rect(inset: (x: 4pt, y: 5pt), radius: 4pt, fill: rgb(239, 241, 243))[ + ```html + + + + + + +

Topic

+

The Hypertext Markup Language.

+ + + + ``` +] diff --git a/tests/typ/text/microtype.typ b/tests/typ/text/microtype.typ index 991e0d0dc..28d29445b 100644 --- a/tests/typ/text/microtype.typ +++ b/tests/typ/text/microtype.typ @@ -5,7 +5,7 @@ #set page(width: 130pt, margins: 15pt) #set par(justify: true, linebreaks: "simple") #set text(size: 9pt) -#rect(fill: rgb(repr(teal) + "00"), width: 100%)[ +#rect(fill: rgb(0, 0, 0, 0), width: 100%)[ This is a little bit of text that builds up to hang-ing hyphens and dash---es and then, you know, some punctuation in the margin. diff --git a/tests/typ/text/raw.typ b/tests/typ/text/raw.typ index 33b08568d..cf497d753 100644 --- a/tests/typ/text/raw.typ +++ b/tests/typ/text/raw.typ @@ -37,16 +37,6 @@ The keyword ```rust let```. <``` trimmed ```> \ <``` trimmed```> -// Multiline trimming and dedenting. -#block[ - ```py - import this - - def hi(): - print("Hi!") - ``` -] - --- // First line is not dedented and leading space is still possible. ``` A diff --git a/tools/support/typst.tmLanguage.json b/tools/support/typst.tmLanguage.json index e1dd0ba14..0475af438 100644 --- a/tools/support/typst.tmLanguage.json +++ b/tools/support/typst.tmLanguage.json @@ -179,7 +179,7 @@ "patterns": [{ "include": "#arguments" }] }, { - "name": "variable.interpolated.typst", + "name": "entity.other.interpolated.typst", "match": "(#)[[:alpha:]_][[:alnum:]_-]*", "captures": { "1": { "name": "punctuation.definition.variable.typst" } } }