New raw theme & nicer debug representation
BIN
fonts/IBMPlexMono-Bold.ttf
Normal 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::sync::Arc;
|
||||
|
||||
@ -119,12 +119,10 @@ impl Func {
|
||||
|
||||
impl Debug for Func {
|
||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||
f.write_str("<function")?;
|
||||
if let Some(name) = self.name() {
|
||||
f.write_char(' ')?;
|
||||
f.write_str(name)?;
|
||||
match self.name() {
|
||||
Some(name) => f.write_str(name),
|
||||
None => f.write_str("(..) => {..}"),
|
||||
}
|
||||
f.write_char('>')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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("<content>"),
|
||||
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)),
|
||||
"<function nil>",
|
||||
);
|
||||
|
||||
// Dynamics.
|
||||
// Functions, content and dynamics.
|
||||
test(Content::Text("a".into()), "[...]");
|
||||
test(Func::from_fn("nil", |_, _| Ok(Value::None)), "nil");
|
||||
test(Dynamic::new(1), "1");
|
||||
}
|
||||
}
|
||||
|
@ -154,7 +154,7 @@ impl FromStr for RgbaColor {
|
||||
|
||||
impl From<SynColor> 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(())
|
||||
}
|
||||
|
@ -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<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());
|
||||
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<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
|
||||
.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<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,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
///
|
||||
/// 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| {
|
||||
|
@ -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",
|
||||
}
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 6.0 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
BIN
tests/ref/text/code.png
Normal file
After Width: | Height: | Size: 38 KiB |
Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 18 KiB |
@ -4,6 +4,8 @@
|
||||
---
|
||||
// Ref: true
|
||||
|
||||
#set page(width: 150pt)
|
||||
|
||||
// Empty.
|
||||
{()}
|
||||
|
||||
|
@ -13,7 +13,6 @@
|
||||
#f(1)[2](3)
|
||||
|
||||
// Don't parse this as a function.
|
||||
// Should output `<function test> (it)`.
|
||||
#test (it)
|
||||
|
||||
#let f(body) = body
|
||||
|
@ -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} \
|
||||
|
57
tests/typ/text/code.typ
Normal 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>
|
||||
```
|
||||
]
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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" } }
|
||||
}
|
||||
|