mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Raw syntax definition loading (#1655)
This commit is contained in:
parent
076ef3d5f2
commit
07553cbe71
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -2059,6 +2059,7 @@ dependencies = [
|
|||||||
"serde_json",
|
"serde_json",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
|
"yaml-rust",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
22
NOTICE
22
NOTICE
@ -1,5 +1,27 @@
|
|||||||
Licenses for third party components used by this project can be found below.
|
Licenses for third party components used by this project can be found below.
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
The 0BSD License applies to:
|
||||||
|
|
||||||
|
* The S-Expression sublime-syntax in `assets/files/SExpressions.sublime-syntax`
|
||||||
|
which is adapted from the S-Expression syntax definition in the Sublime Text
|
||||||
|
package `S-Expressions` (https://github.com/whitequark/Sublime-S-Expressions)
|
||||||
|
|
||||||
|
BSD Zero Clause License (0BSD)
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and/or distribute this software for
|
||||||
|
any purpose with or without fee is hereby granted.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
|
||||||
|
AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
|
||||||
|
OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
================================================================================
|
||||||
|
|
||||||
================================================================================
|
================================================================================
|
||||||
The MIT License applies to:
|
The MIT License applies to:
|
||||||
|
|
||||||
|
73
assets/files/SExpressions.sublime-syntax
Normal file
73
assets/files/SExpressions.sublime-syntax
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
%YAML 1.2
|
||||||
|
---
|
||||||
|
name: S Expressions
|
||||||
|
file_extensions: ["sexp"]
|
||||||
|
scope: source.sexpr
|
||||||
|
|
||||||
|
contexts:
|
||||||
|
main:
|
||||||
|
- match: '(;+).*$'
|
||||||
|
scope: comment.line.sexpr
|
||||||
|
captures:
|
||||||
|
1: punctuation.definition.comment.sexpr
|
||||||
|
- match: '#;'
|
||||||
|
scope: punctuation.definition.comment.sexpr
|
||||||
|
push: comment
|
||||||
|
- match: '#\|'
|
||||||
|
scope: punctuation.definition.comment.sexpr
|
||||||
|
push: block_comment
|
||||||
|
|
||||||
|
- match: '"'
|
||||||
|
scope: punctuation.definition.string.begin.sexpr
|
||||||
|
push: string_unquote
|
||||||
|
- match: '\d+\.\d+'
|
||||||
|
scope: constant.numeric.float.sexpr
|
||||||
|
- match: '\d+'
|
||||||
|
scope: constant.numeric.integer.sexpr
|
||||||
|
- match: '\w+'
|
||||||
|
scope: constant.other.sexpr
|
||||||
|
- match: '\('
|
||||||
|
scope: punctuation.section.parens.begin.sexpr
|
||||||
|
push: main_rparen
|
||||||
|
- match: '\)'
|
||||||
|
scope: invalid.illegal.stray-paren-end
|
||||||
|
|
||||||
|
string_unquote:
|
||||||
|
- meta_scope: string.quoted.double.sexpr
|
||||||
|
- match: '""'
|
||||||
|
scope: constant.character.escape.sexpr
|
||||||
|
- match: '"'
|
||||||
|
scope: punctuation.definition.string.end.sexpr
|
||||||
|
pop: true
|
||||||
|
|
||||||
|
main_rparen:
|
||||||
|
- match: '\)'
|
||||||
|
scope: punctuation.section.parens.end.sexpr
|
||||||
|
pop: true
|
||||||
|
- include: main
|
||||||
|
|
||||||
|
comment:
|
||||||
|
- meta_scope: comment.block.sexpr
|
||||||
|
- match: '\('
|
||||||
|
set: comment_rparen
|
||||||
|
|
||||||
|
comment_lparen:
|
||||||
|
- meta_scope: comment.block.sexpr
|
||||||
|
- match: '\('
|
||||||
|
push: comment_rparen
|
||||||
|
- match: '\)'
|
||||||
|
scope: invalid.illegal.stray-paren-end
|
||||||
|
|
||||||
|
comment_rparen:
|
||||||
|
- meta_scope: comment.block.sexpr
|
||||||
|
- match: '\)'
|
||||||
|
pop: true
|
||||||
|
- include: comment
|
||||||
|
|
||||||
|
block_comment:
|
||||||
|
- meta_scope: comment.block.sexpr
|
||||||
|
- match: '#\|'
|
||||||
|
push: block_comment
|
||||||
|
- match: '\|#'
|
||||||
|
scope: punctuation.definition.comment.sexpr
|
||||||
|
pop: true
|
@ -39,7 +39,7 @@ rustybuzz = "0.7"
|
|||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
serde_yaml = "0.8"
|
serde_yaml = "0.8"
|
||||||
smallvec = "1.10"
|
smallvec = "1.10"
|
||||||
syntect = { version = "5", default-features = false, features = ["parsing", "regex-fancy"] }
|
syntect = { version = "5", default-features = false, features = ["parsing", "regex-fancy", "yaml-load"] }
|
||||||
time = { version = "0.3.20", features = ["formatting"] }
|
time = { version = "0.3.20", features = ["formatting"] }
|
||||||
toml = { version = "0.7.3", default-features = false, features = ["parse"] }
|
toml = { version = "0.7.3", default-features = false, features = ["parse"] }
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.37"
|
||||||
|
@ -119,11 +119,11 @@ pub struct ParElem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Construct for ParElem {
|
impl Construct for ParElem {
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
// The paragraph constructor is special: It doesn't create a paragraph
|
// The paragraph constructor is special: It doesn't create a paragraph
|
||||||
// element. Instead, it just ensures that the passed content lives in a
|
// element. Instead, it just ensures that the passed content lives in a
|
||||||
// separate paragraph and styles it.
|
// separate paragraph and styles it.
|
||||||
let styles = Self::set(args)?;
|
let styles = Self::set(vm, args)?;
|
||||||
let body = args.expect::<Content>("body")?;
|
let body = args.expect::<Content>("body")?;
|
||||||
Ok(Content::sequence([
|
Ok(Content::sequence([
|
||||||
ParbreakElem::new().pack(),
|
ParbreakElem::new().pack(),
|
||||||
|
@ -520,11 +520,11 @@ impl TextElem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Construct for TextElem {
|
impl Construct for TextElem {
|
||||||
fn construct(_: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
fn construct(vm: &mut Vm, args: &mut Args) -> SourceResult<Content> {
|
||||||
// The text constructor is special: It doesn't create a text element.
|
// The text constructor is special: It doesn't create a text element.
|
||||||
// Instead, it leaves the passed argument structurally unchanged, but
|
// Instead, it leaves the passed argument structurally unchanged, but
|
||||||
// styles all text in it.
|
// styles all text in it.
|
||||||
let styles = Self::set(args)?;
|
let styles = Self::set(vm, args)?;
|
||||||
let body = args.expect::<Content>("body")?;
|
let body = args.expect::<Content>("body")?;
|
||||||
Ok(body.styled_with_map(styles))
|
Ok(body.styled_with_map(styles))
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,13 @@
|
|||||||
|
use std::hash::Hash;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use once_cell::unsync::Lazy as UnsyncLazy;
|
||||||
use syntect::highlighting as synt;
|
use syntect::highlighting as synt;
|
||||||
|
use syntect::parsing::{SyntaxDefinition, SyntaxSet, SyntaxSetBuilder};
|
||||||
|
use typst::diag::FileError;
|
||||||
use typst::syntax::{self, LinkedNode};
|
use typst::syntax::{self, LinkedNode};
|
||||||
|
use typst::util::Bytes;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
FontFamily, FontList, Hyphenate, LinebreakElem, SmartQuoteElem, TextElem, TextSize,
|
FontFamily, FontList, Hyphenate, LinebreakElem, SmartQuoteElem, TextElem, TextSize,
|
||||||
@ -126,6 +133,33 @@ pub struct RawElem {
|
|||||||
/// ````
|
/// ````
|
||||||
#[default(HorizontalAlign(GenAlign::Start))]
|
#[default(HorizontalAlign(GenAlign::Start))]
|
||||||
pub align: HorizontalAlign,
|
pub align: HorizontalAlign,
|
||||||
|
|
||||||
|
/// One or multiple additional syntax definitions to load. The syntax
|
||||||
|
/// definitions should be in the `sublime-syntax` file format.
|
||||||
|
///
|
||||||
|
/// ````example
|
||||||
|
/// #set raw(syntaxes: "SExpressions.sublime-syntax")
|
||||||
|
///
|
||||||
|
/// ```sexp
|
||||||
|
/// (defun factorial (x)
|
||||||
|
/// (if (zerop x)
|
||||||
|
/// ; with a comment
|
||||||
|
/// 1
|
||||||
|
/// (* x (factorial (- x 1)))))
|
||||||
|
/// ```
|
||||||
|
/// ````
|
||||||
|
#[parse(
|
||||||
|
let (syntaxes, data) = parse_syntaxes(vm, args)?;
|
||||||
|
syntaxes
|
||||||
|
)]
|
||||||
|
#[fold]
|
||||||
|
pub syntaxes: SyntaxPaths,
|
||||||
|
|
||||||
|
/// The raw file buffers.
|
||||||
|
#[internal]
|
||||||
|
#[parse(data)]
|
||||||
|
#[fold]
|
||||||
|
pub data: Vec<Bytes>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RawElem {
|
impl RawElem {
|
||||||
@ -163,6 +197,9 @@ impl Show for RawElem {
|
|||||||
.map(to_typst)
|
.map(to_typst)
|
||||||
.map_or(Color::BLACK, Color::from);
|
.map_or(Color::BLACK, Color::from);
|
||||||
|
|
||||||
|
let extra_syntaxes =
|
||||||
|
UnsyncLazy::new(|| load(&self.syntaxes(styles), &self.data(styles)).unwrap());
|
||||||
|
|
||||||
let mut realized = if matches!(lang.as_deref(), Some("typ" | "typst" | "typc")) {
|
let mut realized = if matches!(lang.as_deref(), Some("typ" | "typst" | "typc")) {
|
||||||
let root = match lang.as_deref() {
|
let root = match lang.as_deref() {
|
||||||
Some("typc") => syntax::parse_code(&text),
|
Some("typc") => syntax::parse_code(&text),
|
||||||
@ -181,9 +218,16 @@ impl Show for RawElem {
|
|||||||
);
|
);
|
||||||
|
|
||||||
Content::sequence(seq)
|
Content::sequence(seq)
|
||||||
} else if let Some(syntax) =
|
} else if let Some((syntax_set, syntax)) = lang.and_then(|token| {
|
||||||
lang.and_then(|token| SYNTAXES.find_syntax_by_token(&token))
|
SYNTAXES
|
||||||
{
|
.find_syntax_by_token(&token)
|
||||||
|
.map(|syntax| (&*SYNTAXES, syntax))
|
||||||
|
.or_else(|| {
|
||||||
|
extra_syntaxes
|
||||||
|
.find_syntax_by_token(&token)
|
||||||
|
.map(|syntax| (&**extra_syntaxes, syntax))
|
||||||
|
})
|
||||||
|
}) {
|
||||||
let mut seq = vec![];
|
let mut seq = vec![];
|
||||||
let mut highlighter = syntect::easy::HighlightLines::new(syntax, &THEME);
|
let mut highlighter = syntect::easy::HighlightLines::new(syntax, &THEME);
|
||||||
for (i, line) in text.lines().enumerate() {
|
for (i, line) in text.lines().enumerate() {
|
||||||
@ -192,7 +236,7 @@ impl Show for RawElem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (style, piece) in
|
for (style, piece) in
|
||||||
highlighter.highlight_line(line, &SYNTAXES).into_iter().flatten()
|
highlighter.highlight_line(line, syntax_set).into_iter().flatten()
|
||||||
{
|
{
|
||||||
seq.push(styled(piece, foreground.into(), style));
|
seq.push(styled(piece, foreground.into(), style));
|
||||||
}
|
}
|
||||||
@ -319,6 +363,71 @@ fn to_syn(RgbaColor { r, g, b, a }: RgbaColor) -> synt::Color {
|
|||||||
synt::Color { r, g, b, a }
|
synt::Color { r, g, b, a }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A list of bibliography file paths.
|
||||||
|
#[derive(Debug, Default, Clone, Hash)]
|
||||||
|
pub struct SyntaxPaths(Vec<EcoString>);
|
||||||
|
|
||||||
|
cast! {
|
||||||
|
SyntaxPaths,
|
||||||
|
self => self.0.into_value(),
|
||||||
|
v: EcoString => Self(vec![v]),
|
||||||
|
v: Array => Self(v.into_iter().map(Value::cast).collect::<StrResult<_>>()?),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fold for SyntaxPaths {
|
||||||
|
type Output = Self;
|
||||||
|
|
||||||
|
fn fold(mut self, outer: Self::Output) -> Self::Output {
|
||||||
|
self.0.extend(outer.0);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Load a syntax set from a list of syntax file paths.
|
||||||
|
#[comemo::memoize]
|
||||||
|
fn load(paths: &SyntaxPaths, bytes: &[Bytes]) -> StrResult<Arc<SyntaxSet>> {
|
||||||
|
let mut out = SyntaxSetBuilder::new();
|
||||||
|
|
||||||
|
// We might have multiple sublime-syntax/yaml files
|
||||||
|
for (path, bytes) in paths.0.iter().zip(bytes.iter()) {
|
||||||
|
let src = std::str::from_utf8(bytes).map_err(|_| FileError::InvalidUtf8)?;
|
||||||
|
out.add(
|
||||||
|
SyntaxDefinition::load_from_str(src, false, None)
|
||||||
|
.map_err(|e| eco_format!("failed to parse syntax file `{path}`: {e}"))?,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Arc::new(out.build()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Function to parse the syntaxes argument.
|
||||||
|
/// Much nicer than having it be part of the `element` macro.
|
||||||
|
fn parse_syntaxes(
|
||||||
|
vm: &mut Vm,
|
||||||
|
args: &mut Args,
|
||||||
|
) -> SourceResult<(Option<SyntaxPaths>, Option<Vec<Bytes>>)> {
|
||||||
|
let Some(Spanned { v: paths, span }) =
|
||||||
|
args.named::<Spanned<SyntaxPaths>>("syntaxes")?
|
||||||
|
else {
|
||||||
|
return Ok((None, None));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Load syntax files.
|
||||||
|
let data = paths
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.map(|path| {
|
||||||
|
let id = vm.location().join(path).at(span)?;
|
||||||
|
vm.world().file(id).at(span)
|
||||||
|
})
|
||||||
|
.collect::<SourceResult<Vec<Bytes>>>()?;
|
||||||
|
|
||||||
|
// Check that parsing works.
|
||||||
|
let _ = load(&paths, &data).at(span)?;
|
||||||
|
|
||||||
|
Ok((Some(paths), Some(data)))
|
||||||
|
}
|
||||||
|
|
||||||
/// The syntect syntax definitions.
|
/// The syntect syntax definitions.
|
||||||
///
|
///
|
||||||
/// Code for syntax set generation is below. The `syntaxes` directory is from
|
/// Code for syntax set generation is below. The `syntaxes` directory is from
|
||||||
|
@ -523,6 +523,7 @@ fn create_set_impl(element: &Elem) -> TokenStream {
|
|||||||
quote! {
|
quote! {
|
||||||
impl ::typst::model::Set for #ident {
|
impl ::typst::model::Set for #ident {
|
||||||
fn set(
|
fn set(
|
||||||
|
vm: &mut Vm,
|
||||||
args: &mut ::typst::eval::Args,
|
args: &mut ::typst::eval::Args,
|
||||||
) -> ::typst::diag::SourceResult<::typst::model::Styles> {
|
) -> ::typst::diag::SourceResult<::typst::model::Styles> {
|
||||||
let mut styles = ::typst::model::Styles::new();
|
let mut styles = ::typst::model::Styles::new();
|
||||||
|
@ -1471,7 +1471,7 @@ impl Eval for ast::SetRule {
|
|||||||
})
|
})
|
||||||
.at(target.span())?;
|
.at(target.span())?;
|
||||||
let args = self.args().eval(vm)?;
|
let args = self.args().eval(vm)?;
|
||||||
Ok(target.set(args)?.spanned(self.span()))
|
Ok(target.set(vm, args)?.spanned(self.span()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,8 +7,8 @@ use comemo::Prehashed;
|
|||||||
use ecow::{eco_format, EcoString, EcoVec};
|
use ecow::{eco_format, EcoString, EcoVec};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
element, Behave, Behaviour, ElemFunc, Element, Fold, Guard, Label, Locatable,
|
element, Behave, Behaviour, ElemFunc, Element, Guard, Label, Locatable, Location,
|
||||||
Location, Recipe, Selector, Style, Styles, Synthesize,
|
Recipe, Selector, Style, Styles, Synthesize,
|
||||||
};
|
};
|
||||||
use crate::diag::{SourceResult, StrResult};
|
use crate::diag::{SourceResult, StrResult};
|
||||||
use crate::doc::Meta;
|
use crate::doc::Meta;
|
||||||
@ -588,15 +588,6 @@ impl Behave for MetaElem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Fold for Vec<Meta> {
|
|
||||||
type Output = Self;
|
|
||||||
|
|
||||||
fn fold(mut self, outer: Self::Output) -> Self::Output {
|
|
||||||
self.extend(outer);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tries to extract the plain-text representation of the element.
|
/// Tries to extract the plain-text representation of the element.
|
||||||
pub trait PlainText {
|
pub trait PlainText {
|
||||||
/// Write this element's plain text into the given buffer.
|
/// Write this element's plain text into the given buffer.
|
||||||
|
@ -32,7 +32,7 @@ pub trait Construct {
|
|||||||
/// An element's set rule.
|
/// An element's set rule.
|
||||||
pub trait Set {
|
pub trait Set {
|
||||||
/// Parse relevant arguments into style properties for this element.
|
/// Parse relevant arguments into style properties for this element.
|
||||||
fn set(args: &mut Args) -> SourceResult<Styles>;
|
fn set(vm: &mut Vm, args: &mut Args) -> SourceResult<Styles>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// An element's function.
|
/// An element's function.
|
||||||
@ -80,8 +80,8 @@ impl ElemFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Execute the set rule for the element and return the resulting style map.
|
/// Execute the set rule for the element and return the resulting style map.
|
||||||
pub fn set(self, mut args: Args) -> SourceResult<Styles> {
|
pub fn set(self, vm: &mut Vm, mut args: Args) -> SourceResult<Styles> {
|
||||||
let styles = (self.0.set)(&mut args)?;
|
let styles = (self.0.set)(vm, &mut args)?;
|
||||||
args.finish()?;
|
args.finish()?;
|
||||||
Ok(styles)
|
Ok(styles)
|
||||||
}
|
}
|
||||||
@ -128,7 +128,7 @@ pub struct NativeElemFunc {
|
|||||||
/// The element's constructor.
|
/// The element's constructor.
|
||||||
pub construct: fn(&mut Vm, &mut Args) -> SourceResult<Content>,
|
pub construct: fn(&mut Vm, &mut Args) -> SourceResult<Content>,
|
||||||
/// The element's set rule.
|
/// The element's set rule.
|
||||||
pub set: fn(&mut Args) -> SourceResult<Styles>,
|
pub set: fn(&mut Vm, &mut Args) -> SourceResult<Styles>,
|
||||||
/// Details about the function.
|
/// Details about the function.
|
||||||
pub info: Lazy<FuncInfo>,
|
pub info: Lazy<FuncInfo>,
|
||||||
}
|
}
|
||||||
|
@ -748,3 +748,12 @@ where
|
|||||||
self.map(|inner| inner.fold(outer.unwrap_or_default()))
|
self.map(|inner| inner.fold(outer.unwrap_or_default()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<T> Fold for Vec<T> {
|
||||||
|
type Output = Vec<T>;
|
||||||
|
|
||||||
|
fn fold(mut self, outer: Self::Output) -> Self::Output {
|
||||||
|
self.extend(outer);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BIN
tests/ref/text/raw-syntaxes.png
Normal file
BIN
tests/ref/text/raw-syntaxes.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.0 KiB |
14
tests/typ/text/raw-syntaxes.typ
Normal file
14
tests/typ/text/raw-syntaxes.typ
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
// Test code highlighting with custom syntaxes.
|
||||||
|
|
||||||
|
---
|
||||||
|
#set page(width: 180pt)
|
||||||
|
#set text(6pt)
|
||||||
|
#set raw(syntaxes: "/files/SExpressions.sublime-syntax")
|
||||||
|
|
||||||
|
```sexp
|
||||||
|
(defun factorial (x)
|
||||||
|
(if (zerop x)
|
||||||
|
; with a comment
|
||||||
|
1
|
||||||
|
(* x (factorial (- x 1)))))
|
||||||
|
```
|
Loading…
x
Reference in New Issue
Block a user