Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "giallo"
version = "0.5.2"
version = "0.6.0"
edition = "2024"
license = "EUPL-1.2"
description = "A code highlighter giving the same output as VSCode"
Expand All @@ -19,17 +19,19 @@ include = [


[dependencies]
onig = { package = "onig-regset", version = "6", default-features = false }
papaya = "0.2"
serde = { version = "1", features = ["derive"] }
serde_json = "1"

# Optional dependencies for serialization
bitcode = { version = "0.6.9", optional = true, features = ["serde"] }
zstd = { version = "0.13.3", optional = true }

zstd = { version = "0.14", optional = true }
fancy-regex = "0.19"
log = { version = "0.4", optional = true }

[patch.crates-io]
fancy-regex = { git = "https://github.com/fancy-regex/fancy-regex" }

[features]
tools = ["dump"]
debug = ["dep:log"]
Expand Down
157 changes: 86 additions & 71 deletions benches/highlight_benchmark.rs
Original file line number Diff line number Diff line change
@@ -1,112 +1,127 @@
use criterion::{Criterion, criterion_group, criterion_main};
use giallo::{HighlightOptions, Registry, ThemeVariant};
use std::cell::RefCell;
use std::fs;

fn highlight_jquery_benchmark(c: &mut Criterion) {
// Load registry once for all benchmarks
let mut registry =
Registry::load_from_file("builtin.zst").expect("Failed to load registry from builtin.zst");
registry.link_grammars();

// Read jQuery file content once
let jquery_content =
fs::read_to_string("src/fixtures/samples/jquery.js").expect("Failed to read jQuery file");

let options = HighlightOptions::new("javascript", ThemeVariant::Single("vitesse-black"));
use criterion::{BatchSize, Criterion, criterion_group, criterion_main};
use giallo::{HighlightOptions, Registry, ThemeVariant};

c.bench_function("highlight jquery.js", |b| {
b.iter(|| {
registry.clear_pattern_cache();
let result = registry.highlight(&jquery_content, &options).unwrap();
std::hint::black_box(result);
})
});
const SAMPLES: &[(&str, &str)] = &[
("javascript", "src/fixtures/samples/jquery.js"),
("javascript", "grammars-themes/samples/javascript.sample"),
("typescript", "grammars-themes/samples/typescript.sample"),
("tsx", "grammars-themes/samples/tsx.sample"),
("rust", "grammars-themes/samples/rust.sample"),
("c", "grammars-themes/samples/c.sample"),
("markdown", "grammars-themes/samples/markdown.sample"),
("python", "grammars-themes/samples/python.sample"),
("html", "grammars-themes/samples/html.sample"),
("css", "grammars-themes/samples/css.sample"),
("ruby", "grammars-themes/samples/ruby.sample"),
("go", "grammars-themes/samples/go.sample"),
("astro", "grammars-themes/samples/astro.sample"),
("c#", "grammars-themes/samples/csharp.sample"),
("java", "grammars-themes/samples/java.sample"),
("php", "grammars-themes/samples/php.sample"),
("json", "grammars-themes/samples/json.sample"),
("shellscript", "grammars-themes/samples/shellscript.sample"),
];

fn bench_name(grammar: &'static str, path: &str) -> &'static str {
if path.contains("jquery") {
"jquery"
} else {
grammar
}
}

fn highlight_simple_benchmark(c: &mut Criterion) {
let mut registry =
Registry::load_from_file("builtin.zst").expect("Failed to load registry from builtin.zst");
registry.link_grammars();
let registry = RefCell::new(registry);

let ts_content = fs::read_to_string("src/fixtures/samples/simple.ts").unwrap();

let options = HighlightOptions::new("typescript", ThemeVariant::Single("vitesse-black"));

c.bench_function("highlight simple.ts", |b| {
b.iter(|| {
registry.clear_pattern_cache();
let result = registry.highlight(&ts_content, &options).unwrap();
std::hint::black_box(result);
})
b.iter_batched(
|| registry.borrow_mut().clear_caches(),
|()| {
let registry = registry.borrow();
std::hint::black_box(registry.highlight(&ts_content, &options).unwrap());
},
BatchSize::PerIteration,
)
});
}

fn highlight_multiple_simple_benchmark(c: &mut Criterion) {
let mut registry =
Registry::load_from_file("builtin.zst").expect("Failed to load registry from builtin.zst");
registry.link_grammars();
let registry = RefCell::new(registry);

let ts_content = fs::read_to_string("src/fixtures/samples/simple.ts").unwrap();

let options = HighlightOptions::new("typescript", ThemeVariant::Single("vitesse-black"));

c.bench_function("highlight multiple simple.ts", |b| {
b.iter(|| {
// should not be 5x slower than "highlight simple.ts"
registry.clear_pattern_cache();
let result = registry.highlight(&ts_content, &options).unwrap();
std::hint::black_box(result);
let result = registry.highlight(&ts_content, &options).unwrap();
std::hint::black_box(result);
let result = registry.highlight(&ts_content, &options).unwrap();
std::hint::black_box(result);
let result = registry.highlight(&ts_content, &options).unwrap();
std::hint::black_box(result);
let result = registry.highlight(&ts_content, &options).unwrap();
std::hint::black_box(result);
})
b.iter_batched(
|| registry.borrow_mut().clear_caches(),
|()| {
// should not be 5x slower than "highlight simple.ts"
for _ in 0..5 {
std::hint::black_box(
registry.borrow().highlight(&ts_content, &options).unwrap(),
);
}
},
BatchSize::PerIteration,
)
});
}

fn highlight_sample_benchmark(c: &mut Criterion, grammar: &str) {
let mut registry =
Registry::load_from_file("builtin.zst").expect("Failed to load registry from builtin.zst");
registry.link_grammars();

let sample_path = format!("grammars-themes/samples/{grammar}.sample");
let content = fs::read_to_string(&sample_path)
.unwrap_or_else(|_| panic!("Failed to read sample {sample_path}"));

let options = HighlightOptions::new(grammar, ThemeVariant::Single("vitesse-black"));

c.bench_function(&format!("highlight {grammar}.sample"), |b| {
b.iter(|| {
registry.clear_pattern_cache();
let result = registry.highlight(&content, &options).unwrap();
std::hint::black_box(result);
})
});
}

fn highlight_rust_sample_benchmark(c: &mut Criterion) {
highlight_sample_benchmark(c, "rust");
}

fn highlight_markdown_sample_benchmark(c: &mut Criterion) {
highlight_sample_benchmark(c, "markdown");
fn highlight_cold_benchmark(c: &mut Criterion) {
let registry = RefCell::new(Registry::load_from_file("builtin.zst").unwrap());
let mut group = c.benchmark_group("highlight cold");
group.sample_size(50);
for &(grammar, path) in SAMPLES {
let content = fs::read_to_string(path).unwrap();
let options = HighlightOptions::new(grammar, ThemeVariant::Single("vitesse-black"));
registry.borrow().highlight(&content, &options).unwrap();
group.bench_function(bench_name(grammar, path), |b| {
b.iter_batched(
|| registry.borrow_mut().clear_caches(),
|()| {
let registry = registry.borrow();
std::hint::black_box(registry.highlight(&content, &options).unwrap());
},
BatchSize::PerIteration,
)
});
}
group.finish();
}

fn highlight_javascript_sample_benchmark(c: &mut Criterion) {
highlight_sample_benchmark(c, "javascript");
fn highlight_warm_benchmark(c: &mut Criterion) {
let registry = Registry::load_from_file("builtin.zst").unwrap();
let mut group = c.benchmark_group("highlight warm");
for &(grammar, path) in SAMPLES {
let content = fs::read_to_string(path).unwrap();
let options = HighlightOptions::new(grammar, ThemeVariant::Single("vitesse-black"));
registry.highlight(&content, &options).unwrap();
group.bench_function(bench_name(grammar, path), |b| {
b.iter(|| std::hint::black_box(registry.highlight(&content, &options).unwrap()))
});
}
group.finish();
}

criterion_group!(
benches,
highlight_jquery_benchmark,
highlight_simple_benchmark,
highlight_multiple_simple_benchmark,
highlight_rust_sample_benchmark,
highlight_markdown_sample_benchmark,
highlight_javascript_sample_benchmark
highlight_cold_benchmark,
highlight_warm_benchmark,
);
criterion_main!(benches);
1 change: 1 addition & 0 deletions examples/html_classes.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use giallo::{HighlightOptions, HtmlRenderer, Registry, RenderOptions, ThemeVariant};
use std::fs;

#[allow(clippy::single_range_in_vec_init)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut registry = Registry::load_from_file("builtin.zst")?;
registry.link_grammars();
Expand Down
2 changes: 1 addition & 1 deletion grammars-themes
Submodule grammars-themes updated 55 files
+522 −4,547 packages/tm-grammars/NOTICE
+23 −23 packages/tm-grammars/README.md
+3 −3 packages/tm-grammars/grammars/ada.json
+107 −5 packages/tm-grammars/grammars/csharp.json
+272 −21 packages/tm-grammars/grammars/css.json
+2 −2 packages/tm-grammars/grammars/dart.json
+1 −1 packages/tm-grammars/grammars/gn.json
+34 −20 packages/tm-grammars/grammars/handlebars.json
+14 −2 packages/tm-grammars/grammars/julia.json
+23 −2 packages/tm-grammars/grammars/latex.json
+19 −6 packages/tm-grammars/grammars/luau.json
+7 −3 packages/tm-grammars/grammars/marko.json
+3 −3 packages/tm-grammars/grammars/mojo.json
+4 −0 packages/tm-grammars/grammars/nextflow-groovy.json
+1 −1 packages/tm-grammars/grammars/nextflow.json
+80 −15 packages/tm-grammars/grammars/nushell.json
+1 −1 packages/tm-grammars/grammars/php.json
+2 −2 packages/tm-grammars/grammars/powershell.json
+3 −3 packages/tm-grammars/grammars/ruby.json
+10 −2 packages/tm-grammars/grammars/rust.json
+20 −2 packages/tm-grammars/grammars/sql.json
+10,575 −310 packages/tm-grammars/grammars/typst.json
+186 −127 packages/tm-grammars/index.js
+1 −1 packages/tm-grammars/package.json
+3 −3 packages/tm-grammars/raw/ada.json
+108 −6 packages/tm-grammars/raw/csharp.json
+273 −22 packages/tm-grammars/raw/css.json
+3 −3 packages/tm-grammars/raw/dart.json
+1 −1 packages/tm-grammars/raw/gn.json
+37 −21 packages/tm-grammars/raw/handlebars.json
+16 −3 packages/tm-grammars/raw/julia.json
+24 −3 packages/tm-grammars/raw/latex.json
+19 −6 packages/tm-grammars/raw/luau.json
+8 −3 packages/tm-grammars/raw/marko.json
+3 −3 packages/tm-grammars/raw/mojo.json
+5 −0 packages/tm-grammars/raw/nextflow-groovy.json
+1 −1 packages/tm-grammars/raw/nextflow.json
+81 −15 packages/tm-grammars/raw/nushell.json
+2 −2 packages/tm-grammars/raw/php.json
+3 −3 packages/tm-grammars/raw/powershell.json
+4 −4 packages/tm-grammars/raw/ruby.json
+13 −4 packages/tm-grammars/raw/rust.json
+21 −3 packages/tm-grammars/raw/sql.json
+10,576 −315 packages/tm-grammars/raw/typst.json
+5 −5 packages/tm-themes/README.md
+30 −30 packages/tm-themes/index.js
+1 −1 packages/tm-themes/package.json
+2 −1 packages/tm-themes/themes/monokai.json
+84 −5 packages/tm-themes/themes/one-dark-pro.json
+5 −1 packages/tm-themes/themes/red.json
+6 −1 packages/tm-themes/themes/solarized-dark.json
+3 −0 packages/tm-themes/themes/solarized-light.json
+57 −1 scripts/shared/license.ts
+25 −9 sources-grammars.ts
+72 −39 test/__snapshots__/typst.txt
15 changes: 13 additions & 2 deletions scripts/lib/textmate-common.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,20 +83,31 @@ async function createRegistry() {
}
}

const injectionsByScope = new Map();
for (const rawGrammar of loadedGrammars.values()) {
for (const target of rawGrammar.injectTo || []) {
if (!injectionsByScope.has(target)) {
injectionsByScope.set(target, []);
}
injectionsByScope.get(target).push(rawGrammar.scopeName);
}
}

// Set up oniguruma and create registry
const onigLib = await getOniguruma();
const registry = new Registry({
onigLib: onigLib,
loadGrammar: async (scopeName) => {
return loadedGrammars.get(scopeName) || null;
}
},
getInjections: (scopeName) => injectionsByScope.get(scopeName),
});

// Load all grammars into the registry upfront to support injections
console.log(`Loading ${loadedGrammars.size} grammars into registry...`);
for (const [scopeName, rawGrammar] of loadedGrammars.entries()) {
try {
await registry.addGrammar(rawGrammar);
await registry.addGrammar(rawGrammar, injectionsByScope.get(scopeName) || []);
} catch (error) {
console.warn(`⚠️ Failed to load grammar ${scopeName}:`, error.message);
}
Expand Down
23 changes: 14 additions & 9 deletions src/tokenizer/anchors.rs → src/grammars/anchors.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use onig::SearchOptions;
use std::fmt;

/// We use that as a way to convey both the rule and which anchors should be active
Expand Down Expand Up @@ -36,15 +35,21 @@ impl AnchorActive {
}
}

pub fn to_search_options(self) -> SearchOptions {
pub fn allow_a(&self) -> bool {
matches!(self, AnchorActive::A | AnchorActive::AG)
}

pub fn allow_g(&self) -> bool {
matches!(self, AnchorActive::G | AnchorActive::AG)
}

/// The same anchor context with \G deactivated.
/// RegexSet handles it on its own (inside fancy-regex) but not the normal Regex
/// we walk through
pub fn without_g(&self) -> Self {
match self {
AnchorActive::AG => SearchOptions::SEARCH_OPTION_NONE,
AnchorActive::A => SearchOptions::SEARCH_OPTION_NOT_BEGIN_POSITION,
AnchorActive::G => SearchOptions::SEARCH_OPTION_NOT_BEGIN_STRING,
AnchorActive::None => {
SearchOptions::SEARCH_OPTION_NOT_BEGIN_STRING
| SearchOptions::SEARCH_OPTION_NOT_BEGIN_POSITION
}
AnchorActive::A | AnchorActive::AG => AnchorActive::A,
AnchorActive::G | AnchorActive::None => AnchorActive::None,
}
}
}
Expand Down
40 changes: 40 additions & 0 deletions src/grammars/caches.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
use std::sync::{Arc, OnceLock};

use crate::grammars::engine;

/// We want to compile only one of each regex/regexset as they are expensive.
/// We key by the patterns so they can be reused in multiple grammars/context.
#[derive(Default, Debug)]
pub(crate) struct RegexCache {
/// We cache the fancy regexes across all grammars
regexes: papaya::HashMap<String, OnceLock<Arc<engine::Regex>>>,
/// And the sets are cached by the patterns
sets: papaya::HashMap<Vec<String>, OnceLock<Arc<engine::RegexSet>>>,
}

impl RegexCache {
#[doc(hidden)]
pub(crate) fn clear(&self) {
self.sets.pin().clear();
self.regexes.pin().clear();
}

pub fn get_regex(&self, pattern: &str) -> Arc<engine::Regex> {
self.regexes
.pin()
.get_or_insert_with(pattern.to_string(), OnceLock::new)
.get_or_init(|| Arc::new(engine::Regex::new(pattern)))
.clone()
}

pub fn get_set(&self, patterns: &[String]) -> Arc<engine::RegexSet> {
self.sets
.pin()
.get_or_insert_with(patterns.to_vec(), OnceLock::new)
.get_or_init(|| {
let regexes: Vec<_> = patterns.iter().map(|p| self.get_regex(p)).collect();
Arc::new(engine::RegexSet::from_regexes(&regexes))
})
.clone()
}
}
Loading
Loading