mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
134 lines
4.1 KiB
Rust
134 lines
4.1 KiB
Rust
use once_cell::sync::Lazy;
|
|
use syntect::easy::HighlightLines;
|
|
use syntect::highlighting::{FontStyle, Highlighter, Style, Theme, ThemeSet};
|
|
use syntect::parsing::SyntaxSet;
|
|
|
|
use super::{FontFamily, TextNode};
|
|
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());
|
|
|
|
/// 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(showable)]
|
|
impl RawNode {
|
|
/// The raw text's font family. Just the normal text family if `none`.
|
|
pub const FAMILY: Smart<FontFamily> = Smart::Custom(FontFamily::new("IBM Plex Mono"));
|
|
/// The language to syntax-highlight in.
|
|
pub const LANG: Option<EcoString> = None;
|
|
|
|
fn construct(_: &mut Context, args: &mut Args) -> TypResult<Content> {
|
|
Ok(Content::show(Self {
|
|
text: args.expect("text")?,
|
|
block: args.named("block")?.unwrap_or(false),
|
|
}))
|
|
}
|
|
}
|
|
|
|
impl Show for RawNode {
|
|
fn show(&self, ctx: &mut Context, styles: StyleChain) -> TypResult<Content> {
|
|
let lang = styles.get_ref(Self::LANG).as_ref();
|
|
let foreground = THEME
|
|
.settings
|
|
.foreground
|
|
.map(Color::from)
|
|
.unwrap_or(Color::BLACK)
|
|
.into();
|
|
|
|
let mut content = if let Some(content) = styles.show(self, ctx, [
|
|
Value::Str(self.text.clone()),
|
|
match lang {
|
|
Some(lang) => Value::Str(lang.clone()),
|
|
None => Value::None,
|
|
},
|
|
Value::Bool(self.block),
|
|
])? {
|
|
content
|
|
} else 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 highlighter = Highlighter::new(&THEME);
|
|
|
|
syntax::highlight_syntect(red.as_ref(), &highlighter, &mut |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(Content::Linebreak);
|
|
}
|
|
|
|
for (style, piece) in highlighter.highlight(line, &SYNTAXES) {
|
|
seq.push(styled(piece, foreground, style));
|
|
}
|
|
}
|
|
|
|
Content::sequence(seq)
|
|
} else {
|
|
Content::Text(self.text.clone())
|
|
};
|
|
|
|
let mut map = StyleMap::new();
|
|
if let Smart::Custom(family) = styles.get_cloned(Self::FAMILY) {
|
|
map.set_family(family, styles);
|
|
}
|
|
|
|
content = content.styled_with_map(map);
|
|
|
|
if self.block {
|
|
content = Content::Block(content.pack());
|
|
}
|
|
|
|
Ok(content)
|
|
}
|
|
}
|
|
|
|
/// Style a piece of text with a syntect style.
|
|
fn styled(piece: &str, foreground: Paint, style: Style) -> Content {
|
|
let mut styles = StyleMap::new();
|
|
let mut body = Content::Text(piece.into());
|
|
|
|
let paint = style.foreground.into();
|
|
if paint != foreground {
|
|
styles.set(TextNode::FILL, paint);
|
|
}
|
|
|
|
if style.font_style.contains(FontStyle::BOLD) {
|
|
styles.set(TextNode::STRONG, true);
|
|
}
|
|
|
|
if style.font_style.contains(FontStyle::ITALIC) {
|
|
styles.set(TextNode::EMPH, true);
|
|
}
|
|
|
|
if style.font_style.contains(FontStyle::UNDERLINE) {
|
|
body = body.underlined();
|
|
}
|
|
|
|
body.styled_with_map(styles)
|
|
}
|