New raw theme & nicer debug representation

This commit is contained in:
Laurenz 2022-05-04 22:14:57 +02:00
parent 75472fee1a
commit e674fd7e90
23 changed files with 171 additions and 69 deletions

BIN
fonts/IBMPlexMono-Bold.ttf Normal file

Binary file not shown.

View File

@ -1,4 +1,4 @@
use std::fmt::{self, Debug, Formatter, Write}; use std::fmt::{self, Debug, Formatter};
use std::hash::{Hash, Hasher}; use std::hash::{Hash, Hasher};
use std::sync::Arc; use std::sync::Arc;
@ -119,12 +119,10 @@ impl Func {
impl Debug for Func { impl Debug for Func {
fn fmt(&self, f: &mut Formatter) -> fmt::Result { fn fmt(&self, f: &mut Formatter) -> fmt::Result {
f.write_str("<function")?; match self.name() {
if let Some(name) = self.name() { Some(name) => f.write_str(name),
f.write_char(' ')?; None => f.write_str("(..) => {..}"),
f.write_str(name)?;
} }
f.write_char('>')
} }
} }

View File

@ -122,8 +122,9 @@ impl Value {
Value::Content(v) => v, Value::Content(v) => v,
// For values which can't be shown "naturally", we return the raw // For values which can't be shown "naturally", we return the raw
// representation. // representation with typst code syntax highlighting.
v => Content::show(RawNode { text: v.repr(), block: false }), 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::Fraction(v) => Debug::fmt(v, f),
Self::Color(v) => Debug::fmt(v, f), Self::Color(v) => Debug::fmt(v, f),
Self::Str(v) => Debug::fmt(v, f), Self::Str(v) => Debug::fmt(v, f),
Self::Content(_) => f.pad("<content>"), Self::Content(_) => f.pad("[...]"),
Self::Array(v) => Debug::fmt(v, f), Self::Array(v) => Debug::fmt(v, f),
Self::Dict(v) => Debug::fmt(v, f), Self::Dict(v) => Debug::fmt(v, f),
Self::Func(v) => Debug::fmt(v, f), Self::Func(v) => Debug::fmt(v, f),
@ -720,7 +721,10 @@ mod tests {
"30% + 56.69pt", "30% + 56.69pt",
); );
test(Fraction::one() * 7.55, "7.55fr"); 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. // Collections.
test("hello", r#""hello""#); test("hello", r#""hello""#);
@ -734,13 +738,9 @@ mod tests {
test(dict!["one" => 1], "(one: 1)"); test(dict!["one" => 1], "(one: 1)");
test(dict!["two" => false, "one" => 1], "(one: 1, two: false)"); test(dict!["two" => false, "one" => 1], "(one: 1, two: false)");
// Functions. // Functions, content and dynamics.
test( test(Content::Text("a".into()), "[...]");
Func::from_fn("nil", |_, _| Ok(Value::None)), test(Func::from_fn("nil", |_, _| Ok(Value::None)), "nil");
"<function nil>",
);
// Dynamics.
test(Dynamic::new(1), "1"); test(Dynamic::new(1), "1");
} }
} }

View File

@ -154,7 +154,7 @@ impl FromStr for RgbaColor {
impl From<SynColor> for RgbaColor { impl From<SynColor> for RgbaColor {
fn from(color: SynColor) -> Self { 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, self.r, self.g, self.b, self.a,
)?; )?;
} else { } 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 { if self.a != 255 {
write!(f, "{:02x}", self.a)?; write!(f, "{:02x}", self.a)?;
} }
write!(f, "\")")?;
} }
Ok(()) Ok(())
} }

View File

@ -1,20 +1,17 @@
use std::sync::Arc;
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use syntect::easy::HighlightLines; 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 syntect::parsing::SyntaxSet;
use super::{FontFamily, Hyphenate, TextNode, Toggle}; use super::{FontFamily, Hyphenate, TextNode, Toggle};
use crate::library::layout::BlockSpacing; use crate::library::layout::BlockSpacing;
use crate::library::prelude::*; use crate::library::prelude::*;
use crate::source::SourceId; use crate::source::SourceId;
use crate::syntax::{self, RedNode}; use crate::syntax::{self, GreenNode, NodeKind, RedNode};
/// The lazily-loaded theme used for syntax highlighting.
static THEME: Lazy<Theme> =
Lazy::new(|| ThemeSet::load_defaults().themes.remove("InspiredGitHub").unwrap());
/// The lazily-loaded syntect syntax definitions.
static SYNTAXES: Lazy<SyntaxSet> = Lazy::new(|| SyntaxSet::load_defaults_newlines());
/// Monospaced text with optional syntax highlighting. /// Monospaced text with optional syntax highlighting.
#[derive(Debug, Hash)] #[derive(Debug, Hash)]
@ -63,7 +60,7 @@ impl Show for RawNode {
} }
fn realize(&self, _: &mut Context, styles: StyleChain) -> TypResult<Content> { fn realize(&self, _: &mut Context, styles: StyleChain) -> TypResult<Content> {
let lang = styles.get(Self::LANG).as_ref(); let lang = styles.get(Self::LANG).as_ref().map(|s| s.to_lowercase());
let foreground = THEME let foreground = THEME
.settings .settings
.foreground .foreground
@ -71,15 +68,19 @@ impl Show for RawNode {
.unwrap_or(Color::BLACK) .unwrap_or(Color::BLACK)
.into(); .into();
let mut realized = if matches!( let mut realized = if matches!(lang.as_deref(), Some("typ" | "typst" | "typc")) {
lang.map(|s| s.to_lowercase()).as_deref(), let root = match lang.as_deref() {
Some("typ" | "typst") Some("typc") => {
) { let children = crate::parse::parse_code(&self.text);
let mut seq = vec![]; Arc::new(GreenNode::with_children(NodeKind::CodeBlock, children))
let green = crate::parse::parse(&self.text); }
let red = RedNode::from_root(green, SourceId::from_raw(0)); _ => crate::parse::parse(&self.text),
};
let red = RedNode::from_root(root, SourceId::from_raw(0));
let highlighter = Highlighter::new(&THEME); let highlighter = Highlighter::new(&THEME);
let mut seq = vec![];
syntax::highlight_syntect(red.as_ref(), &highlighter, &mut |range, style| { syntax::highlight_syntect(red.as_ref(), &highlighter, &mut |range, style| {
seq.push(styled(&self.text[range], foreground, 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) body.styled_with_map(styles)
} }
/// The lazily-loaded syntect syntax definitions.
static SYNTAXES: Lazy<SyntaxSet> = Lazy::new(|| SyntaxSet::load_defaults_newlines());
/// The lazily-loaded theme used for syntax highlighting.
#[rustfmt::skip]
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("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<FontStyle>) -> ThemeItem {
ThemeItem {
scope: scope.parse().unwrap(),
style: StyleModifier {
foreground: color.map(|s| s.parse().unwrap()),
background: None,
font_style,
},
}
}

View File

@ -26,6 +26,13 @@ pub fn parse(src: &str) -> Arc<GreenNode> {
} }
} }
/// Parse code directly, only used for syntax highlighting.
pub fn parse_code(src: &str) -> Vec<Green> {
let mut p = Parser::new(src, TokenMode::Code);
code(&mut p);
p.finish()
}
/// Reparse a code block. /// Reparse a code block.
/// ///
/// Returns `Some` if all of the input was consumed. /// 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) { fn code_block(p: &mut Parser) {
p.perform(NodeKind::CodeBlock, |p| { p.perform(NodeKind::CodeBlock, |p| {
p.start_group(Group::Brace); p.start_group(Group::Brace);
while !p.eof() { code(p);
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(_)));
}
p.end_group(); 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: `[...]`. // Parse a content block: `[...]`.
fn content_block(p: &mut Parser) { fn content_block(p: &mut Parser) {
p.perform(NodeKind::ContentBlock, |p| { p.perform(NodeKind::ContentBlock, |p| {

View File

@ -95,7 +95,7 @@ pub enum Category {
/// A function. /// A function.
Function, Function,
/// An interpolated variable in markup. /// An interpolated variable in markup.
Variable, Interpolated,
/// An invalid node. /// An invalid node.
Invalid, Invalid,
} }
@ -178,7 +178,7 @@ impl Category {
NodeKind::None => Some(Category::None), NodeKind::None => Some(Category::None),
NodeKind::Auto => Some(Category::Auto), NodeKind::Auto => Some(Category::Auto),
NodeKind::Ident(_) => match parent.kind() { NodeKind::Ident(_) => match parent.kind() {
NodeKind::Markup(_) => Some(Category::Variable), NodeKind::Markup(_) => Some(Category::Interpolated),
NodeKind::FuncCall => Some(Category::Function), NodeKind::FuncCall => Some(Category::Function),
NodeKind::MethodCall if i > 0 => Some(Category::Function), NodeKind::MethodCall if i > 0 => Some(Category::Function),
NodeKind::ClosureExpr 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::Number => "constant.numeric.typst",
Self::String => "string.quoted.double.typst", Self::String => "string.quoted.double.typst",
Self::Function => "entity.name.function.typst", Self::Function => "entity.name.function.typst",
Self::Variable => "variable.parameter.typst", Self::Interpolated => "entity.other.interpolated.typst",
Self::Invalid => "invalid.typst", Self::Invalid => "invalid.typst",
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

BIN
tests/ref/text/code.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -4,6 +4,8 @@
--- ---
// Ref: true // Ref: true
#set page(width: 150pt)
// Empty. // Empty.
{()} {()}

View File

@ -13,7 +13,6 @@
#f(1)[2](3) #f(1)[2](3)
// Don't parse this as a function. // Don't parse this as a function.
// Should output `<function test> (it)`.
#test (it) #test (it)
#let f(body) = body #let f(body) = body

View File

@ -25,23 +25,24 @@
{2.3fr} {2.3fr}
--- ---
// Colors. // Colors and strokes.
#rgb("f7a20500") \ #set text(0.8em)
{2pt + rgb("f7a20500")} #rgb("f7a205") \
{2pt + rgb("f7a205")}
--- ---
// Strings and escaping. // Strings and escaping.
#repr("hi") \ #raw(repr("hi"), lang: "typc") \
#repr("a\n[]\"\u{1F680}string") #repr("a\n[]\"\u{1F680}string")
--- ---
// Content. // Content.
#repr[*{"H" + "i"} there*] #raw(repr[*{"H" + "i"} there*])
--- ---
// Functions // Functions
#let f(x) = x #let f(x) = x
{() => none} \
{f} \ {f} \
{rect} {rect} \
{() => none} \

57
tests/typ/text/code.typ Normal file
View File

@ -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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
<h1>Topic</h1>
<p>The Hypertext Markup Language.</p>
<script>
function foo(a, b) {
return a + b + "string";
}
</script>
</body>
</html>
```
]

View File

@ -5,7 +5,7 @@
#set page(width: 130pt, margins: 15pt) #set page(width: 130pt, margins: 15pt)
#set par(justify: true, linebreaks: "simple") #set par(justify: true, linebreaks: "simple")
#set text(size: 9pt) #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 This is a little bit of text that builds up to
hang-ing hyphens and dash---es and then, you know, hang-ing hyphens and dash---es and then, you know,
some punctuation in the margin. some punctuation in the margin.

View File

@ -37,16 +37,6 @@ The keyword ```rust let```.
<``` trimmed ```> \ <``` trimmed ```> \
<``` 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. // First line is not dedented and leading space is still possible.
``` A ``` A

View File

@ -179,7 +179,7 @@
"patterns": [{ "include": "#arguments" }] "patterns": [{ "include": "#arguments" }]
}, },
{ {
"name": "variable.interpolated.typst", "name": "entity.other.interpolated.typst",
"match": "(#)[[:alpha:]_][[:alnum:]_-]*", "match": "(#)[[:alpha:]_][[:alnum:]_-]*",
"captures": { "1": { "name": "punctuation.definition.variable.typst" } } "captures": { "1": { "name": "punctuation.definition.variable.typst" } }
} }