Compare commits

...
Sign in to create a new pull request.

26 commits

Author SHA1 Message Date
Quentin Boyer
c2e50ccd11 Add the source to the closure 2026-01-22 01:23:08 +01:00
Quentin Boyer
1a54b1794d Refactor to be able to pass a query 2026-01-22 01:15:51 +01:00
Quentin Boyer
4c89725111 Expose a callback instead of doing the filtering in the iterator 2026-01-22 00:47:36 +01:00
Quentin Boyer
705130705a Ignore directives in predicates 2026-01-22 00:14:19 +01:00
Quentin Boyer
e5ee144b0a Allow to match arbitrary predicates 2026-01-21 23:50:06 +01:00
Quentin Boyer
90885404ce Depend on upstream tree-sitter 2026-01-21 23:01:14 +01:00
Will Lillis
152d2756fc fix(cli): warn user when nm can't be run to verify the symbols inside
the parser being built

(cherry picked from commit 0cdb6bef7b)
2026-01-18 23:26:47 -05:00
Christian Clason
f05efbb352 fix(wasm): regenerate stdlib with wasm-opt
Problem: Output of `cargo xtask build-wasm-stdlib` depends on whether
`wasm-opt` is installed (since `clang` will use it by default if it
finds it).

Solution: Install it and rerun the xtask.
(cherry picked from commit 5d290a2a75)
2026-01-15 16:52:47 +01:00
Will Lillis
1f221c8500 fix(build): define _BSD_SOURCE
System endian conversion macros are gated behind this feature flag for
older versions of GLIBC. `_BSD_SOURCE` and `_SVID_SOURCE` were
deprecated and replaced with `_DEFAULT_SOURCE` starting with GLIBC 2.19.

(cherry picked from commit aefae11c0d)
2026-01-12 23:43:46 -05:00
Kevin Wang
fdca0718bc fix(templates): fix python free-threading compatibility
(cherry picked from commit 630fa52717)
2026-01-10 04:01:08 -06:00
Christian Clason
fa7b1b2a66 fix(wasm): update wasm-stdlib.h
(cherry picked from commit cd6672701b)
2026-01-06 19:27:35 +01:00
tree-sitter-ci-bot[bot]
adcc4d1f7b
fix(wasm): add common definitions to stdlib (#5199) (#5208)
Also expose `strlen` through `string.h` instead of `stdio.h`.

(cherry picked from commit f4ca3d95ca)

Co-authored-by: Trim21 <trim21.me@gmail.com>
2026-01-06 12:27:26 +01:00
skewb1k
7d9c544c96 fix(cli): restore test summary output for tree-sitter test
Problem:
After commit f02d7e7e33
the `tree-sitter test` command no longer printed the final test summary,
leaving empty line. The `Stats` struct was embedded into `TestSummary`,
and the explicit call to print it was removed.

Solution:
Print `parse_stats` from `TestSummary.fmt()` implementation.

(cherry picked from commit 17e3c7a5c5)
2026-01-04 22:45:41 -08:00
WillLillis
c1e49d1571 feat(cli): fill in missing fields to tree-sitter.json when running
`tree-sitter init -u`

(cherry picked from commit dd60d5cff0)
2025-12-31 20:37:15 +01:00
WillLillis
eae6554735 fix(cli): increase verbosity of tree-sitter init -u updates
Also, use `info` logs rather than `warn`

(cherry picked from commit f1288ea5c9)
2025-12-31 20:37:15 +01:00
WillLillis
48ee942c4f fix(cli): canonicalize build --output path
This fixes a potential issue with the new lock file hashing mechanism,
in which two different path literals pointing to the same location would
hash to separate lock files, allowing a race condition.

(cherry picked from commit 93d793d249)
2025-12-30 17:49:45 +01:00
Firas al-Khalil
9ee2b87dd6 feat(cli): concurrent build of same grammar on different paths
(cherry picked from commit 5d9605a91e)
2025-12-29 12:37:04 +01:00
Firas al-Khalil
fb91deb8d9 fix(cli): report library load failure
Instead of panicking somehere else.

This happens on concurrent builds of the the same grammar.

(cherry picked from commit 5293dd683e)
2025-12-29 12:37:04 +01:00
Firas al-Khalil
789a966f96 fix(cli): report context on compile fail
(cherry picked from commit 62effdf128)
2025-12-29 12:37:04 +01:00
WillLillis
3c49fef0e3 fix(rust): address nightly clippy lint
(cherry picked from commit 8e4f21aba0)
2025-12-27 19:39:28 -05:00
WillLillis
8a297b86bc fix(cli): set language in cwd for all usages of highlight command
(cherry picked from commit 5208299bbb)
2025-12-27 19:39:28 -05:00
skewb1k
ac6644016c fix(cli): remove extra newline with --cst
Makes CST output consistent with other formats.

(cherry picked from commit f05e57e2fc)
2025-12-24 15:37:30 +01:00
skewb1k
a80765614b fix(cli): remove extra indentation with --cst --no-ranges
(cherry picked from commit 2f33a37dff)
2025-12-24 15:37:30 +01:00
kevin-hua-kraken
34602af22c fix(playground): update query API
(cherry picked from commit a7d8c0cbb2)
2025-12-23 14:18:14 +01:00
Will Lillis
c4f81931e6 fix(cli): correct discrepancy with cst for --no-ranges
(cherry picked from commit eacb95c85d)
2025-12-16 23:24:07 -05:00
skewb1k
25777e5a64 fix(cli): trailing whitespace after multiline text nodes in CST
Problem:
The CST printer emits trailing whitespace after multiline text nodes.
With 1704c604bf and `:cst` corpus tests
this causes trailing spaces to appear on `test --update`.
These spaces cannot be removed afterward, as the test runner
expects an exact character-for-character match for CST tests.

Solution:
Print whitespace only if node is not multiline.

(cherry picked from commit 4ac2d5d276)
2025-12-14 22:41:02 -05:00
24 changed files with 1328 additions and 3882 deletions

View file

@ -81,7 +81,7 @@ set_target_properties(tree-sitter
SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}" SOVERSION "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}"
DEFINE_SYMBOL "") DEFINE_SYMBOL "")
target_compile_definitions(tree-sitter PRIVATE _POSIX_C_SOURCE=200112L _DEFAULT_SOURCE _DARWIN_C_SOURCE) target_compile_definitions(tree-sitter PRIVATE _POSIX_C_SOURCE=200112L _DEFAULT_SOURCE _BSD_SOURCE _DARWIN_C_SOURCE)
include(GNUInstallDirs) include(GNUInstallDirs)

2713
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,15 +1,6 @@
[workspace] [workspace]
default-members = ["crates/cli"]
members = [ members = [
"crates/cli",
"crates/config",
"crates/generate",
"crates/highlight", "crates/highlight",
"crates/loader",
"crates/tags",
"crates/xtask",
"crates/language",
"lib",
] ]
resolver = "2" resolver = "2"

View file

@ -24,7 +24,7 @@ OBJ := $(SRC:.c=.o)
ARFLAGS := rcs ARFLAGS := rcs
CFLAGS ?= -O3 -Wall -Wextra -Wshadow -Wpedantic -Werror=incompatible-pointer-types CFLAGS ?= -O3 -Wall -Wextra -Wshadow -Wpedantic -Werror=incompatible-pointer-types
override CFLAGS += -std=c11 -fPIC -fvisibility=hidden override CFLAGS += -std=c11 -fPIC -fvisibility=hidden
override CFLAGS += -D_POSIX_C_SOURCE=200112L -D_DEFAULT_SOURCE -D_DARWIN_C_SOURCE override CFLAGS += -D_POSIX_C_SOURCE=200112L -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_DARWIN_C_SOURCE
override CFLAGS += -Ilib/src -Ilib/src/wasm -Ilib/include override CFLAGS += -Ilib/src -Ilib/src/wasm -Ilib/include
# ABI versioning # ABI versioning

View file

@ -27,6 +27,7 @@ let package = Package(
.headerSearchPath("src"), .headerSearchPath("src"),
.define("_POSIX_C_SOURCE", to: "200112L"), .define("_POSIX_C_SOURCE", to: "200112L"),
.define("_DEFAULT_SOURCE"), .define("_DEFAULT_SOURCE"),
.define("_BSD_SOURCE"),
.define("_DARWIN_C_SOURCE"), .define("_DARWIN_C_SOURCE"),
]), ]),
], ],

View file

@ -40,6 +40,7 @@ pub fn build(b: *std.Build) !void {
lib.root_module.addCMacro("_POSIX_C_SOURCE", "200112L"); lib.root_module.addCMacro("_POSIX_C_SOURCE", "200112L");
lib.root_module.addCMacro("_DEFAULT_SOURCE", ""); lib.root_module.addCMacro("_DEFAULT_SOURCE", "");
lib.root_module.addCMacro("_BSD_SOURCE", "");
lib.root_module.addCMacro("_DARWIN_C_SOURCE", ""); lib.root_module.addCMacro("_DARWIN_C_SOURCE", "");
if (wasm) { if (wasm) {

View file

@ -8,7 +8,7 @@ use anyhow::{anyhow, Context, Result};
use crc32fast::hash as crc32; use crc32fast::hash as crc32;
use heck::{ToKebabCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase}; use heck::{ToKebabCase, ToShoutySnakeCase, ToSnakeCase, ToUpperCamelCase};
use indoc::{formatdoc, indoc}; use indoc::{formatdoc, indoc};
use log::warn; use log::info;
use rand::{thread_rng, Rng}; use rand::{thread_rng, Rng};
use semver::Version; use semver::Version;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -123,7 +123,7 @@ const BUILD_ZIG_ZON_TEMPLATE: &str = include_str!("./templates/build.zig.zon");
const ROOT_ZIG_TEMPLATE: &str = include_str!("./templates/root.zig"); const ROOT_ZIG_TEMPLATE: &str = include_str!("./templates/root.zig");
const TEST_ZIG_TEMPLATE: &str = include_str!("./templates/test.zig"); const TEST_ZIG_TEMPLATE: &str = include_str!("./templates/test.zig");
const TREE_SITTER_JSON_SCHEMA: &str = pub const TREE_SITTER_JSON_SCHEMA: &str =
"https://tree-sitter.github.io/tree-sitter/assets/schemas/config.schema.json"; "https://tree-sitter.github.io/tree-sitter/assets/schemas/config.schema.json";
#[derive(Serialize, Deserialize, Clone)] #[derive(Serialize, Deserialize, Clone)]
@ -356,7 +356,7 @@ pub fn generate_grammar_files(
"tree-sitter-cli":"#}, "tree-sitter-cli":"#},
); );
if !contents.contains("module") { if !contents.contains("module") {
warn!("Updating package.json"); info!("Migrating package.json to ESM");
contents = contents.replace( contents = contents.replace(
r#""repository":"#, r#""repository":"#,
indoc! {r#" indoc! {r#"
@ -378,6 +378,7 @@ pub fn generate_grammar_files(
|path| { |path| {
let mut contents = fs::read_to_string(path)?; let mut contents = fs::read_to_string(path)?;
if contents.contains("module.exports") { if contents.contains("module.exports") {
info!("Migrating grammars.js to ESM");
contents = contents.replace("module.exports =", "export default"); contents = contents.replace("module.exports =", "export default");
write_file(path, contents)?; write_file(path, contents)?;
} }
@ -393,10 +394,16 @@ pub fn generate_grammar_files(
allow_update, allow_update,
|path| generate_file(path, GITIGNORE_TEMPLATE, language_name, &generate_opts), |path| generate_file(path, GITIGNORE_TEMPLATE, language_name, &generate_opts),
|path| { |path| {
let contents = fs::read_to_string(path)?; let mut contents = fs::read_to_string(path)?;
if !contents.contains("Zig artifacts") { if !contents.contains("Zig artifacts") {
warn!("Replacing .gitignore"); info!("Adding zig entries to .gitignore");
generate_file(path, GITIGNORE_TEMPLATE, language_name, &generate_opts)?; contents.push('\n');
contents.push_str(indoc! {"
# Zig artifacts
.zig-cache/
zig-cache/
zig-out/
"});
} }
Ok(()) Ok(())
}, },
@ -409,8 +416,13 @@ pub fn generate_grammar_files(
|path| generate_file(path, GITATTRIBUTES_TEMPLATE, language_name, &generate_opts), |path| generate_file(path, GITATTRIBUTES_TEMPLATE, language_name, &generate_opts),
|path| { |path| {
let mut contents = fs::read_to_string(path)?; let mut contents = fs::read_to_string(path)?;
contents = contents.replace("bindings/c/* ", "bindings/c/** "); let c_bindings_entry = "bindings/c/* ";
if contents.contains(c_bindings_entry) {
info!("Updating c bindings entry in .gitattributes");
contents = contents.replace(c_bindings_entry, "bindings/c/** ");
}
if !contents.contains("Zig bindings") { if !contents.contains("Zig bindings") {
info!("Adding zig entries to .gitattributes");
contents.push('\n'); contents.push('\n');
contents.push_str(indoc! {" contents.push_str(indoc! {"
# Zig bindings # Zig bindings
@ -438,39 +450,40 @@ pub fn generate_grammar_files(
}, |path| { }, |path| {
let mut contents = fs::read_to_string(path)?; let mut contents = fs::read_to_string(path)?;
if !contents.contains("#[cfg(with_highlights_query)]") { if !contents.contains("#[cfg(with_highlights_query)]") {
let replacement = indoc! {r#" info!("Updating query constants in bindings/rust/lib.rs");
#[cfg(with_highlights_query)] let replacement = indoc! {r#"
/// The syntax highlighting query for this grammar. #[cfg(with_highlights_query)]
pub const HIGHLIGHTS_QUERY: &str = include_str!("../../HIGHLIGHTS_QUERY_PATH"); /// The syntax highlighting query for this grammar.
pub const HIGHLIGHTS_QUERY: &str = include_str!("../../HIGHLIGHTS_QUERY_PATH");
#[cfg(with_injections_query)] #[cfg(with_injections_query)]
/// The language injection query for this grammar. /// The language injection query for this grammar.
pub const INJECTIONS_QUERY: &str = include_str!("../../INJECTIONS_QUERY_PATH"); pub const INJECTIONS_QUERY: &str = include_str!("../../INJECTIONS_QUERY_PATH");
#[cfg(with_locals_query)] #[cfg(with_locals_query)]
/// The local variable query for this grammar. /// The local variable query for this grammar.
pub const LOCALS_QUERY: &str = include_str!("../../LOCALS_QUERY_PATH"); pub const LOCALS_QUERY: &str = include_str!("../../LOCALS_QUERY_PATH");
#[cfg(with_tags_query)] #[cfg(with_tags_query)]
/// The symbol tagging query for this grammar. /// The symbol tagging query for this grammar.
pub const TAGS_QUERY: &str = include_str!("../../TAGS_QUERY_PATH"); pub const TAGS_QUERY: &str = include_str!("../../TAGS_QUERY_PATH");
"#} "#}
.replace("HIGHLIGHTS_QUERY_PATH", generate_opts.highlights_query_path) .replace("HIGHLIGHTS_QUERY_PATH", generate_opts.highlights_query_path)
.replace("INJECTIONS_QUERY_PATH", generate_opts.injections_query_path) .replace("INJECTIONS_QUERY_PATH", generate_opts.injections_query_path)
.replace("LOCALS_QUERY_PATH", generate_opts.locals_query_path) .replace("LOCALS_QUERY_PATH", generate_opts.locals_query_path)
.replace("TAGS_QUERY_PATH", generate_opts.tags_query_path); .replace("TAGS_QUERY_PATH", generate_opts.tags_query_path);
contents = contents contents = contents
.replace( .replace(
indoc! {r#" indoc! {r#"
// NOTE: uncomment these to include any queries that this grammar contains: // NOTE: uncomment these to include any queries that this grammar contains:
// pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm"); // pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm");
// pub const INJECTIONS_QUERY: &str = include_str!("../../queries/injections.scm"); // pub const INJECTIONS_QUERY: &str = include_str!("../../queries/injections.scm");
// pub const LOCALS_QUERY: &str = include_str!("../../queries/locals.scm"); // pub const LOCALS_QUERY: &str = include_str!("../../queries/locals.scm");
// pub const TAGS_QUERY: &str = include_str!("../../queries/tags.scm"); // pub const TAGS_QUERY: &str = include_str!("../../queries/tags.scm");
"#}, "#},
&replacement, &replacement,
); );
} }
write_file(path, contents)?; write_file(path, contents)?;
Ok(()) Ok(())
@ -483,6 +496,7 @@ pub fn generate_grammar_files(
|path| { |path| {
let mut contents = fs::read_to_string(path)?; let mut contents = fs::read_to_string(path)?;
if !contents.contains("wasm32-unknown-unknown") { if !contents.contains("wasm32-unknown-unknown") {
info!("Adding wasm32-unknown-unknown target to bindings/rust/build.rs");
let replacement = indoc!{r#" let replacement = indoc!{r#"
c_config.flag("-utf-8"); c_config.flag("-utf-8");
@ -503,19 +517,18 @@ pub fn generate_grammar_files(
wasm_src.join("string.c"), wasm_src.join("string.c"),
]); ]);
} }
"#}; "#}
let indented_replacement = replacement
.lines() .lines()
.map(|line| if line.is_empty() { line.to_string() } else { format!(" {line}") }) .map(|line| if line.is_empty() { line.to_string() } else { format!(" {line}") })
.collect::<Vec<_>>() .collect::<Vec<_>>()
.join("\n"); .join("\n");
contents = contents.replace(r#" c_config.flag("-utf-8");"#, &indented_replacement); contents = contents.replace(r#" c_config.flag("-utf-8");"#, &replacement);
} }
// Introduce configuration variables for dynamic query inclusion // Introduce configuration variables for dynamic query inclusion
if !contents.contains("with_highlights_query") { if !contents.contains("with_highlights_query") {
info!("Adding support for dynamic query inclusion to bindings/rust/build.rs");
let replaced = indoc! {r#" let replaced = indoc! {r#"
c_config.compile("tree-sitter-KEBAB_PARSER_NAME"); c_config.compile("tree-sitter-KEBAB_PARSER_NAME");
}"#} }"#}
@ -572,6 +585,7 @@ pub fn generate_grammar_files(
|path| { |path| {
let contents = fs::read_to_string(path)?; let contents = fs::read_to_string(path)?;
if contents.contains("\"LICENSE\"") { if contents.contains("\"LICENSE\"") {
info!("Adding LICENSE entry to bindings/rust/Cargo.toml");
write_file(path, contents.replace("\"LICENSE\"", "\"/LICENSE\""))?; write_file(path, contents.replace("\"LICENSE\"", "\"/LICENSE\""))?;
} }
Ok(()) Ok(())
@ -592,7 +606,7 @@ pub fn generate_grammar_files(
|path| { |path| {
let contents = fs::read_to_string(path)?; let contents = fs::read_to_string(path)?;
if !contents.contains("Object.defineProperty") { if !contents.contains("Object.defineProperty") {
warn!("Replacing index.js"); info!("Replacing index.js");
generate_file(path, INDEX_JS_TEMPLATE, language_name, &generate_opts)?; generate_file(path, INDEX_JS_TEMPLATE, language_name, &generate_opts)?;
} }
Ok(()) Ok(())
@ -606,7 +620,7 @@ pub fn generate_grammar_files(
|path| { |path| {
let contents = fs::read_to_string(path)?; let contents = fs::read_to_string(path)?;
if !contents.contains("export default binding") { if !contents.contains("export default binding") {
warn!("Replacing index.d.ts"); info!("Replacing index.d.ts");
generate_file(path, INDEX_D_TS_TEMPLATE, language_name, &generate_opts)?; generate_file(path, INDEX_D_TS_TEMPLATE, language_name, &generate_opts)?;
} }
Ok(()) Ok(())
@ -627,7 +641,7 @@ pub fn generate_grammar_files(
|path| { |path| {
let contents = fs::read_to_string(path)?; let contents = fs::read_to_string(path)?;
if !contents.contains("import") { if !contents.contains("import") {
warn!("Replacing binding_test.js"); info!("Replacing binding_test.js");
generate_file( generate_file(
path, path,
BINDING_TEST_JS_TEMPLATE, BINDING_TEST_JS_TEMPLATE,
@ -650,6 +664,7 @@ pub fn generate_grammar_files(
|path| { |path| {
let contents = fs::read_to_string(path)?; let contents = fs::read_to_string(path)?;
if contents.contains("fs.exists(") { if contents.contains("fs.exists(") {
info!("Replacing `fs.exists` calls in binding.gyp");
write_file(path, contents.replace("fs.exists(", "fs.existsSync("))?; write_file(path, contents.replace("fs.exists(", "fs.existsSync("))?;
} }
Ok(()) Ok(())
@ -662,14 +677,17 @@ pub fn generate_grammar_files(
// Generate C bindings // Generate C bindings
if tree_sitter_config.bindings.c { if tree_sitter_config.bindings.c {
let kebab_case_name = language_name.to_kebab_case();
missing_path(bindings_dir.join("c"), create_dir)?.apply(|path| { missing_path(bindings_dir.join("c"), create_dir)?.apply(|path| {
let old_file = &path.join(format!("tree-sitter-{}.h", language_name.to_kebab_case())); let header_name = format!("tree-sitter-{kebab_case_name}.h");
let old_file = &path.join(&header_name);
if allow_update && fs::exists(old_file).unwrap_or(false) { if allow_update && fs::exists(old_file).unwrap_or(false) {
info!("Removing bindings/c/{header_name}");
fs::remove_file(old_file)?; fs::remove_file(old_file)?;
} }
missing_path(path.join("tree_sitter"), create_dir)?.apply(|include_path| { missing_path(path.join("tree_sitter"), create_dir)?.apply(|include_path| {
missing_path( missing_path(
include_path.join(format!("tree-sitter-{}.h", language_name.to_kebab_case())), include_path.join(&header_name),
|path| { |path| {
generate_file(path, PARSER_NAME_H_TEMPLATE, language_name, &generate_opts) generate_file(path, PARSER_NAME_H_TEMPLATE, language_name, &generate_opts)
}, },
@ -678,7 +696,7 @@ pub fn generate_grammar_files(
})?; })?;
missing_path( missing_path(
path.join(format!("tree-sitter-{}.pc.in", language_name.to_kebab_case())), path.join(format!("tree-sitter-{kebab_case_name}.pc.in")),
|path| { |path| {
generate_file( generate_file(
path, path,
@ -698,23 +716,27 @@ pub fn generate_grammar_files(
|path| { |path| {
let mut contents = fs::read_to_string(path)?; let mut contents = fs::read_to_string(path)?;
if !contents.contains("cd '$(DESTDIR)$(LIBDIR)' && ln -sf") { if !contents.contains("cd '$(DESTDIR)$(LIBDIR)' && ln -sf") {
warn!("Replacing Makefile"); info!("Replacing Makefile");
generate_file(path, MAKEFILE_TEMPLATE, language_name, &generate_opts)?; generate_file(path, MAKEFILE_TEMPLATE, language_name, &generate_opts)?;
} else { } else {
contents = contents let replaced = indoc! {r"
.replace( $(PARSER): $(SRC_DIR)/grammar.json
indoc! {r" $(TS) generate $^
$(PARSER): $(SRC_DIR)/grammar.json "};
$(TS) generate $^ if contents.contains(replaced) {
"}, info!("Adding --no-parser target to Makefile");
indoc! {r" contents = contents
$(SRC_DIR)/grammar.json: grammar.js .replace(
$(TS) generate --no-parser $^ replaced,
indoc! {r"
$(SRC_DIR)/grammar.json: grammar.js
$(TS) generate --no-parser $^
$(PARSER): $(SRC_DIR)/grammar.json $(PARSER): $(SRC_DIR)/grammar.json
$(TS) generate $^ $(TS) generate $^
"} "}
); );
}
write_file(path, contents)?; write_file(path, contents)?;
} }
Ok(()) Ok(())
@ -726,8 +748,8 @@ pub fn generate_grammar_files(
allow_update, allow_update,
|path| generate_file(path, CMAKELISTS_TXT_TEMPLATE, language_name, &generate_opts), |path| generate_file(path, CMAKELISTS_TXT_TEMPLATE, language_name, &generate_opts),
|path| { |path| {
let mut contents = fs::read_to_string(path)?; let contents = fs::read_to_string(path)?;
contents = contents let replaced_contents = contents
.replace("add_custom_target(test", "add_custom_target(ts-test") .replace("add_custom_target(test", "add_custom_target(ts-test")
.replace( .replace(
&formatdoc! {r#" &formatdoc! {r#"
@ -775,7 +797,10 @@ pub fn generate_grammar_files(
COMMENT "Generating parser.c") COMMENT "Generating parser.c")
"#} "#}
); );
write_file(path, contents)?; if !replaced_contents.eq(&contents) {
info!("Updating CMakeLists.txt");
write_file(path, replaced_contents)?;
}
Ok(()) Ok(())
}, },
)?; )?;
@ -811,7 +836,8 @@ pub fn generate_grammar_files(
// Generate Python bindings // Generate Python bindings
if tree_sitter_config.bindings.python { if tree_sitter_config.bindings.python {
missing_path(bindings_dir.join("python"), create_dir)?.apply(|path| { missing_path(bindings_dir.join("python"), create_dir)?.apply(|path| {
let lang_path = path.join(format!("tree_sitter_{}", language_name.to_snake_case())); let snake_case_grammar_name = format!("tree_sitter_{}", language_name.to_snake_case());
let lang_path = path.join(&snake_case_grammar_name);
missing_path(&lang_path, create_dir)?; missing_path(&lang_path, create_dir)?;
missing_path_else( missing_path_else(
@ -821,6 +847,7 @@ pub fn generate_grammar_files(
|path| { |path| {
let mut contents = fs::read_to_string(path)?; let mut contents = fs::read_to_string(path)?;
if !contents.contains("PyModuleDef_Init") { if !contents.contains("PyModuleDef_Init") {
info!("Updating bindings/python/{snake_case_grammar_name}/binding.c");
contents = contents contents = contents
.replace("PyModule_Create", "PyModuleDef_Init") .replace("PyModule_Create", "PyModuleDef_Init")
.replace( .replace(
@ -862,7 +889,7 @@ pub fn generate_grammar_files(
|path| { |path| {
let contents = fs::read_to_string(path)?; let contents = fs::read_to_string(path)?;
if !contents.contains("uncomment these to include any queries") { if !contents.contains("uncomment these to include any queries") {
warn!("Replacing __init__.py"); info!("Replacing __init__.py");
generate_file(path, INIT_PY_TEMPLATE, language_name, &generate_opts)?; generate_file(path, INIT_PY_TEMPLATE, language_name, &generate_opts)?;
} }
Ok(()) Ok(())
@ -876,9 +903,10 @@ pub fn generate_grammar_files(
|path| { |path| {
let mut contents = fs::read_to_string(path)?; let mut contents = fs::read_to_string(path)?;
if contents.contains("uncomment these to include any queries") { if contents.contains("uncomment these to include any queries") {
warn!("Replacing __init__.pyi"); info!("Replacing __init__.pyi");
generate_file(path, INIT_PYI_TEMPLATE, language_name, &generate_opts)?; generate_file(path, INIT_PYI_TEMPLATE, language_name, &generate_opts)?;
} else if !contents.contains("CapsuleType") { } else if !contents.contains("CapsuleType") {
info!("Updating __init__.pyi");
contents = contents contents = contents
.replace( .replace(
"from typing import Final", "from typing import Final",
@ -910,6 +938,7 @@ pub fn generate_grammar_files(
|path| { |path| {
let mut contents = fs::read_to_string(path)?; let mut contents = fs::read_to_string(path)?;
if !contents.contains("Parser(Language(") { if !contents.contains("Parser(Language(") {
info!("Updating Language function in bindings/python/tests/test_binding.py");
contents = contents contents = contents
.replace("tree_sitter.Language(", "Parser(Language(") .replace("tree_sitter.Language(", "Parser(Language(")
.replace(".language())\n", ".language()))\n") .replace(".language())\n", ".language()))\n")
@ -930,11 +959,19 @@ pub fn generate_grammar_files(
allow_update, allow_update,
|path| generate_file(path, SETUP_PY_TEMPLATE, language_name, &generate_opts), |path| generate_file(path, SETUP_PY_TEMPLATE, language_name, &generate_opts),
|path| { |path| {
let contents = fs::read_to_string(path)?; let mut contents = fs::read_to_string(path)?;
if !contents.contains("build_ext") { if !contents.contains("build_ext") {
warn!("Replacing setup.py"); info!("Replacing setup.py");
generate_file(path, SETUP_PY_TEMPLATE, language_name, &generate_opts)?; generate_file(path, SETUP_PY_TEMPLATE, language_name, &generate_opts)?;
} }
if !contents.contains(" and not get_config_var") {
info!("Updating Python free-threading support in setup.py");
contents = contents.replace(
r#"startswith("cp"):"#,
r#"startswith("cp") and not get_config_var("Py_GIL_DISABLED"):"#
);
write_file(path, contents)?;
}
Ok(()) Ok(())
}, },
)?; )?;
@ -953,6 +990,7 @@ pub fn generate_grammar_files(
|path| { |path| {
let mut contents = fs::read_to_string(path)?; let mut contents = fs::read_to_string(path)?;
if !contents.contains("cp310-*") { if !contents.contains("cp310-*") {
info!("Updating dependencies in pyproject.toml");
contents = contents contents = contents
.replace(r#"build = "cp39-*""#, r#"build = "cp310-*""#) .replace(r#"build = "cp39-*""#, r#"build = "cp310-*""#)
.replace(r#"python = ">=3.9""#, r#"python = ">=3.10""#) .replace(r#"python = ">=3.9""#, r#"python = ">=3.10""#)
@ -990,15 +1028,18 @@ pub fn generate_grammar_files(
allow_update, allow_update,
|path| generate_file(path, PACKAGE_SWIFT_TEMPLATE, language_name, &generate_opts), |path| generate_file(path, PACKAGE_SWIFT_TEMPLATE, language_name, &generate_opts),
|path| { |path| {
let mut contents = fs::read_to_string(path)?; let contents = fs::read_to_string(path)?;
contents = contents let replaced_contents = contents
.replace( .replace(
"https://github.com/ChimeHQ/SwiftTreeSitter", "https://github.com/ChimeHQ/SwiftTreeSitter",
"https://github.com/tree-sitter/swift-tree-sitter", "https://github.com/tree-sitter/swift-tree-sitter",
) )
.replace("version: \"0.8.0\")", "version: \"0.9.0\")") .replace("version: \"0.8.0\")", "version: \"0.9.0\")")
.replace("(url:", "(name: \"SwiftTreeSitter\", url:"); .replace("(url:", "(name: \"SwiftTreeSitter\", url:");
write_file(path, contents)?; if !replaced_contents.eq(&contents) {
info!("Updating tree-sitter dependency in Package.swift");
write_file(path, contents)?;
}
Ok(()) Ok(())
}, },
)?; )?;
@ -1016,7 +1057,7 @@ pub fn generate_grammar_files(
|path| { |path| {
let contents = fs::read_to_string(path)?; let contents = fs::read_to_string(path)?;
if !contents.contains("b.pkg_hash.len") { if !contents.contains("b.pkg_hash.len") {
warn!("Replacing build.zig"); info!("Replacing build.zig");
generate_file(path, BUILD_ZIG_TEMPLATE, language_name, &generate_opts) generate_file(path, BUILD_ZIG_TEMPLATE, language_name, &generate_opts)
} else { } else {
Ok(()) Ok(())
@ -1031,7 +1072,7 @@ pub fn generate_grammar_files(
|path| { |path| {
let contents = fs::read_to_string(path)?; let contents = fs::read_to_string(path)?;
if !contents.contains(".name = .tree_sitter_") { if !contents.contains(".name = .tree_sitter_") {
warn!("Replacing build.zig.zon"); info!("Replacing build.zig.zon");
generate_file(path, BUILD_ZIG_ZON_TEMPLATE, language_name, &generate_opts) generate_file(path, BUILD_ZIG_ZON_TEMPLATE, language_name, &generate_opts)
} else { } else {
Ok(()) Ok(())
@ -1047,7 +1088,7 @@ pub fn generate_grammar_files(
|path| { |path| {
let contents = fs::read_to_string(path)?; let contents = fs::read_to_string(path)?;
if contents.contains("ts.Language") { if contents.contains("ts.Language") {
warn!("Replacing root.zig"); info!("Replacing root.zig");
generate_file(path, ROOT_ZIG_TEMPLATE, language_name, &generate_opts) generate_file(path, ROOT_ZIG_TEMPLATE, language_name, &generate_opts)
} else { } else {
Ok(()) Ok(())

View file

@ -20,7 +20,7 @@ use tree_sitter_cli::{
LOG_GRAPH_ENABLED, START_SEED, LOG_GRAPH_ENABLED, START_SEED,
}, },
highlight::{self, HighlightOptions}, highlight::{self, HighlightOptions},
init::{generate_grammar_files, JsonConfigOpts}, init::{generate_grammar_files, JsonConfigOpts, TREE_SITTER_JSON_SCHEMA},
input::{get_input, get_tmp_source_file, CliInput}, input::{get_input, get_tmp_source_file, CliInput},
logger, logger,
parse::{self, ParseDebugType, ParseFileOptions, ParseOutput, ParseTheme}, parse::{self, ParseDebugType, ParseFileOptions, ParseOutput, ParseTheme},
@ -867,10 +867,26 @@ impl Init {
(opts.name.clone(), Some(opts)) (opts.name.clone(), Some(opts))
} else { } else {
let mut json = serde_json::from_str::<TreeSitterJSON>( let old_config = fs::read_to_string(current_dir.join("tree-sitter.json"))
&fs::read_to_string(current_dir.join("tree-sitter.json")) .with_context(|| "Failed to read tree-sitter.json")?;
.with_context(|| "Failed to read tree-sitter.json")?,
)?; let mut json = serde_json::from_str::<TreeSitterJSON>(&old_config)?;
if json.schema.is_none() {
json.schema = Some(TREE_SITTER_JSON_SCHEMA.to_string());
}
let new_config = format!("{}\n", serde_json::to_string_pretty(&json)?);
// Write the re-serialized config back, as newly added optional boolean fields
// will be included with explicit `false`s rather than implict `null`s
if self.update && !old_config.trim().eq(new_config.trim()) {
info!("Updating tree-sitter.json");
fs::write(
current_dir.join("tree-sitter.json"),
serde_json::to_string_pretty(&json)?,
)
.with_context(|| "Failed to write tree-sitter.json")?;
}
(json.grammars.swap_remove(0).name, None) (json.grammars.swap_remove(0).name, None)
}; };
@ -955,11 +971,21 @@ impl Build {
} else { } else {
let output_path = if let Some(ref path) = self.output { let output_path = if let Some(ref path) = self.output {
let path = Path::new(path); let path = Path::new(path);
if path.is_absolute() { let full_path = if path.is_absolute() {
path.to_path_buf() path.to_path_buf()
} else { } else {
current_dir.join(path) current_dir.join(path)
} };
let parent_path = full_path
.parent()
.context("Output path must have a parent")?;
let name = full_path
.file_name()
.context("Ouput path must have a filename")?;
fs::create_dir_all(parent_path).context("Failed to create output path")?;
let mut canon_path = parent_path.canonicalize().context("Invalid output path")?;
canon_path.push(name);
canon_path
} else { } else {
let file_name = grammar_path let file_name = grammar_path
.file_stem() .file_stem()
@ -984,7 +1010,7 @@ impl Build {
loader loader
.compile_parser_at_path(&grammar_path, output_path, flags) .compile_parser_at_path(&grammar_path, output_path, flags)
.unwrap(); .context("Failed to compile parser")?;
} }
Ok(()) Ok(())
} }
@ -1622,6 +1648,7 @@ impl Highlight {
let loader_config = config.get()?; let loader_config = config.get()?;
loader.find_all_languages(&loader_config)?; loader.find_all_languages(&loader_config)?;
loader.force_rebuild(self.rebuild || self.grammar_path.is_some()); loader.force_rebuild(self.rebuild || self.grammar_path.is_some());
let languages = loader.languages_at_path(current_dir)?;
let cancellation_flag = util::cancel_on_signal(); let cancellation_flag = util::cancel_on_signal();
@ -1702,7 +1729,6 @@ impl Highlight {
} => { } => {
let path = get_tmp_source_file(&contents)?; let path = get_tmp_source_file(&contents)?;
let languages = loader.languages_at_path(current_dir)?;
let language = languages let language = languages
.iter() .iter()
.find(|(_, n)| language_names.contains(&Box::from(n.as_str()))) .find(|(_, n)| language_names.contains(&Box::from(n.as_str())))
@ -1733,7 +1759,6 @@ impl Highlight {
if let (Some(l), Some(lc)) = (language.clone(), language_configuration) { if let (Some(l), Some(lc)) = (language.clone(), language_configuration) {
(l, lc) (l, lc)
} else { } else {
let languages = loader.languages_at_path(current_dir)?;
let language = languages let language = languages
.first() .first()
.map(|(l, _)| l.clone()) .map(|(l, _)| l.clone())

View file

@ -515,7 +515,6 @@ pub fn parse_file_at_path(
if opts.output == ParseOutput::Cst { if opts.output == ParseOutput::Cst {
render_cst(&source_code, &tree, &mut cursor, opts, &mut stdout)?; render_cst(&source_code, &tree, &mut cursor, opts, &mut stdout)?;
println!();
} }
if opts.output == ParseOutput::Xml { if opts.output == ParseOutput::Xml {
@ -785,7 +784,7 @@ pub fn render_cst<'a, 'b: 'a>(
.map(|(row, col)| (row as f64).log10() as usize + (col.len() as f64).log10() as usize + 1) .map(|(row, col)| (row as f64).log10() as usize + (col.len() as f64).log10() as usize + 1)
.max() .max()
.unwrap_or(1); .unwrap_or(1);
let mut indent_level = 1; let mut indent_level = usize::from(!opts.no_ranges);
let mut did_visit_children = false; let mut did_visit_children = false;
let mut in_error = false; let mut in_error = false;
loop { loop {
@ -883,35 +882,24 @@ fn write_node_text(
0 0
}; };
let formatted_line = render_line_feed(line, opts); let formatted_line = render_line_feed(line, opts);
if !opts.no_ranges { write!(
write!( out,
out, "{}{}{}{}{}{}",
"{}{}{}{}{}{}", if multiline { "\n" } else { " " },
if multiline { "\n" } else { "" }, if multiline && !opts.no_ranges {
if multiline { render_node_range(opts, cursor, is_named, true, total_width, node_range)
render_node_range(opts, cursor, is_named, true, total_width, node_range) } else {
} else { String::new()
String::new() },
}, if multiline {
if multiline { " ".repeat(indent_level + 1)
" ".repeat(indent_level + 1) } else {
} else { String::new()
String::new() },
}, paint(quote_color, &String::from(quote)),
paint(quote_color, &String::from(quote)), paint(color, &render_node_text(&formatted_line)),
&paint(color, &render_node_text(&formatted_line)), paint(quote_color, &String::from(quote)),
paint(quote_color, &String::from(quote)), )?;
)?;
} else {
write!(
out,
"\n{}{}{}{}",
" ".repeat(indent_level + 1),
paint(quote_color, &String::from(quote)),
&paint(color, &render_node_text(&formatted_line)),
paint(quote_color, &String::from(quote)),
)?;
}
} }
} }
@ -1011,10 +999,9 @@ fn cst_render_node(
} else { } else {
opts.parse_theme.node_kind opts.parse_theme.node_kind
}; };
write!(out, "{}", paint(kind_color, node.kind()),)?; write!(out, "{}", paint(kind_color, node.kind()))?;
if node.child_count() == 0 { if node.child_count() == 0 {
write!(out, " ")?;
// Node text from a pattern or external scanner // Node text from a pattern or external scanner
write_node_text( write_node_text(
opts, opts,

View file

@ -32,7 +32,7 @@ class BuildExt(build_ext):
class BdistWheel(bdist_wheel): class BdistWheel(bdist_wheel):
def get_tag(self): def get_tag(self):
python, abi, platform = super().get_tag() python, abi, platform = super().get_tag()
if python.startswith("cp"): if python.startswith("cp") and not get_config_var("Py_GIL_DISABLED"):
python, abi = "cp310", "abi3" python, abi = "cp310", "abi3"
return python, abi, platform return python, abi, platform

View file

@ -595,6 +595,8 @@ impl std::fmt::Display for TestSummary {
render_assertion_results("queries", &self.query_results)?; render_assertion_results("queries", &self.query_results)?;
} }
write!(f, "{}", self.parse_stats)?;
Ok(()) Ok(())
} }
} }

View file

@ -70,12 +70,13 @@ impl InlinedProductionMapBuilder {
let production_map = production_indices_by_step_id let production_map = production_indices_by_step_id
.into_iter() .into_iter()
.map(|(step_id, production_indices)| { .map(|(step_id, production_indices)| {
let production = step_id.variable_index.map_or_else( let production =
|| &productions[step_id.production_index], core::ptr::from_ref::<Production>(step_id.variable_index.map_or_else(
|variable_index| { || &productions[step_id.production_index],
&grammar.variables[variable_index].productions[step_id.production_index] |variable_index| {
}, &grammar.variables[variable_index].productions[step_id.production_index]
) as *const Production; },
));
((production, step_id.step_index as u32), production_indices) ((production, step_id.step_index as u32), production_indices)
}) })
.collect(); .collect();

View file

@ -28,4 +28,4 @@ regex.workspace = true
thiserror.workspace = true thiserror.workspace = true
streaming-iterator.workspace = true streaming-iterator.workspace = true
tree-sitter.workspace = true tree-sitter = "0.26"

View file

@ -297,6 +297,7 @@ impl TSHighlighter {
}) })
}) })
}, },
&|_, _, _| true,
); );
if let Ok(highlights) = highlights { if let Ok(highlights) = highlights {

View file

@ -162,15 +162,17 @@ struct LocalScope<'a> {
local_defs: Vec<LocalDef<'a>>, local_defs: Vec<LocalDef<'a>>,
} }
struct HighlightIter<'a, F> struct HighlightIter<'a, F, G>
where where
F: FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a, F: FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a,
G: Fn(&QueryMatch, &Query, &[u8]) -> bool + 'a,
{ {
source: &'a [u8], source: &'a [u8],
language_name: &'a str, language_name: &'a str,
byte_offset: usize, byte_offset: usize,
highlighter: &'a mut Highlighter, highlighter: &'a mut Highlighter,
injection_callback: F, injection_callback: F,
capture_filter: &'a G,
cancellation_flag: Option<&'a AtomicUsize>, cancellation_flag: Option<&'a AtomicUsize>,
layers: Vec<HighlightIterLayer<'a>>, layers: Vec<HighlightIterLayer<'a>>,
iter_count: usize, iter_count: usize,
@ -181,7 +183,7 @@ where
struct HighlightIterLayer<'a> { struct HighlightIterLayer<'a> {
_tree: Tree, _tree: Tree,
cursor: QueryCursor, cursor: QueryCursor,
captures: iter::Peekable<_QueryCaptures<'a, 'a, &'a [u8], &'a [u8]>>, captures: iter::Peekable<Box<dyn Iterator<Item = (QueryMatch<'a, 'a>, usize)> + 'a>>,
config: &'a HighlightConfiguration, config: &'a HighlightConfiguration,
highlight_end_stack: Vec<usize>, highlight_end_stack: Vec<usize>,
scope_stack: Vec<LocalScope<'a>>, scope_stack: Vec<LocalScope<'a>>,
@ -244,6 +246,7 @@ impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> Iterator
&m.assume_init(), &m.assume_init(),
self.ptr, self.ptr,
)); ));
if result.satisfies_text_predicates( if result.satisfies_text_predicates(
self.query, self.query,
&mut self.buffer1, &mut self.buffer1,
@ -252,6 +255,7 @@ impl<'query, 'tree: 'query, T: TextProvider<I>, I: AsRef<[u8]>> Iterator
) { ) {
return Some((result, capture_index as usize)); return Some((result, capture_index as usize));
} }
result.remove(); result.remove();
} else { } else {
return None; return None;
@ -287,6 +291,7 @@ impl Highlighter {
source: &'a [u8], source: &'a [u8],
cancellation_flag: Option<&'a AtomicUsize>, cancellation_flag: Option<&'a AtomicUsize>,
mut injection_callback: impl FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a, mut injection_callback: impl FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a,
query_filter: &'a impl Fn(&QueryMatch, &Query, &[u8]) -> bool,
) -> Result<impl Iterator<Item = Result<HighlightEvent, Error>> + 'a, Error> { ) -> Result<impl Iterator<Item = Result<HighlightEvent, Error>> + 'a, Error> {
let layers = HighlightIterLayer::new( let layers = HighlightIterLayer::new(
source, source,
@ -294,6 +299,7 @@ impl Highlighter {
self, self,
cancellation_flag, cancellation_flag,
&mut injection_callback, &mut injection_callback,
query_filter,
config, config,
0, 0,
vec![Range { vec![Range {
@ -309,6 +315,7 @@ impl Highlighter {
language_name: &config.language_name, language_name: &config.language_name,
byte_offset: 0, byte_offset: 0,
injection_callback, injection_callback,
capture_filter: query_filter,
cancellation_flag, cancellation_flag,
highlighter: self, highlighter: self,
iter_count: 0, iter_count: 0,
@ -509,12 +516,16 @@ impl<'a> HighlightIterLayer<'a> {
/// disjoint ranges are parsed as one syntax tree), these will be eagerly processed and /// disjoint ranges are parsed as one syntax tree), these will be eagerly processed and
/// added to the returned vector. /// added to the returned vector.
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn new<F: FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a>( fn new<
F: FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a,
G: Fn(&QueryMatch, &Query, &[u8]) -> bool,
>(
source: &'a [u8], source: &'a [u8],
parent_name: Option<&str>, parent_name: Option<&str>,
highlighter: &mut Highlighter, highlighter: &mut Highlighter,
cancellation_flag: Option<&'a AtomicUsize>, cancellation_flag: Option<&'a AtomicUsize>,
injection_callback: &mut F, injection_callback: &mut F,
query_filter: &'a G,
mut config: &'a HighlightConfiguration, mut config: &'a HighlightConfiguration,
mut depth: usize, mut depth: usize,
mut ranges: Vec<Range>, mut ranges: Vec<Range>,
@ -601,12 +612,22 @@ impl<'a> HighlightIterLayer<'a> {
let cursor_ref = unsafe { let cursor_ref = unsafe {
mem::transmute::<&mut QueryCursor, &'static mut QueryCursor>(&mut cursor) mem::transmute::<&mut QueryCursor, &'static mut QueryCursor>(&mut cursor)
}; };
let captures = unsafe { let captures = unsafe {
std::mem::transmute::<QueryCaptures<_, _>, _QueryCaptures<_, _>>( std::mem::transmute::<
cursor_ref.captures(&config.query, tree_ref.root_node(), source), QueryCaptures<_, _>,
) _QueryCaptures<'a, 'a, &'a [u8], &'a [u8]>,
} >(cursor_ref.captures(
.peekable(); &config.query,
tree_ref.root_node(),
source,
))
};
let captures: Box<dyn Iterator<Item = _>> =
Box::new(captures.filter(|(result, _): &(_, _)| {
query_filter(result, &config.query, source)
}));
result.push(HighlightIterLayer { result.push(HighlightIterLayer {
highlight_end_stack: Vec::new(), highlight_end_stack: Vec::new(),
@ -618,7 +639,7 @@ impl<'a> HighlightIterLayer<'a> {
cursor, cursor,
depth, depth,
_tree: tree, _tree: tree,
captures, captures: captures.peekable(),
config, config,
ranges, ranges,
}); });
@ -756,9 +777,10 @@ impl<'a> HighlightIterLayer<'a> {
} }
} }
impl<'a, F> HighlightIter<'a, F> impl<'a, F, G> HighlightIter<'a, F, G>
where where
F: FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a, F: FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a,
G: Fn(&QueryMatch, &Query, &[u8]) -> bool,
{ {
fn emit_event( fn emit_event(
&mut self, &mut self,
@ -822,9 +844,10 @@ where
} }
} }
impl<'a, F> Iterator for HighlightIter<'a, F> impl<'a, F, G> Iterator for HighlightIter<'a, F, G>
where where
F: FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a, F: FnMut(&str) -> Option<&'a HighlightConfiguration> + 'a,
G: Fn(&QueryMatch, &Query, &[u8]) -> bool,
{ {
type Item = Result<HighlightEvent, Error>; type Item = Result<HighlightEvent, Error>;
@ -921,6 +944,7 @@ where
self.highlighter, self.highlighter,
self.cancellation_flag, self.cancellation_flag,
&mut self.injection_callback, &mut self.injection_callback,
self.capture_filter,
config, config,
self.layers[0].depth + 1, self.layers[0].depth + 1,
ranges, ranges,

View file

@ -23,9 +23,15 @@ typedef long unsigned int size_t;
typedef long unsigned int uintptr_t; typedef long unsigned int uintptr_t;
#define UINT16_MAX 65535 #define INT8_MAX 127
#define INT16_MAX 32767
#define INT32_MAX 2147483647L
#define INT64_MAX 9223372036854775807LL
#define UINT8_MAX 255
#define UINT16_MAX 65535
#define UINT32_MAX 4294967295U #define UINT32_MAX 4294967295U
#define UINT64_MAX 18446744073709551615ULL
#if defined(__wasm32__) #if defined(__wasm32__)

View file

@ -13,4 +13,6 @@ void *memset(void *dst, int value, size_t count);
int strncmp(const char *left, const char *right, size_t n); int strncmp(const char *left, const char *right, size_t n);
size_t strlen(const char *str);
#endif // TREE_SITTER_WASM_STRING_H_ #endif // TREE_SITTER_WASM_STRING_H_

View file

@ -1,4 +1,5 @@
#include <stdio.h> #include <stdio.h>
#include <string.h>
typedef struct { typedef struct {
bool left_justify; // - bool left_justify; // -
@ -105,12 +106,6 @@ static int ptr_to_str(void *ptr, char *buffer) {
return 2 + len; return 2 + len;
} }
size_t strlen(const char *str) {
const char *s = str;
while (*s) s++;
return s - str;
}
char *strncpy(char *dest, const char *src, size_t n) { char *strncpy(char *dest, const char *src, size_t n) {
char *d = dest; char *d = dest;
const char *s = src; const char *s = src;

View file

@ -58,3 +58,9 @@ int strncmp(const char *left, const char *right, size_t n) {
} }
return 0; return 0;
} }
size_t strlen(const char *str) {
const char *s = str;
while (*s) s++;
return s - str;
}

View file

@ -8,6 +8,7 @@ use std::sync::Mutex;
use std::{ use std::{
collections::HashMap, collections::HashMap,
env, fs, env, fs,
hash::{Hash as _, Hasher as _},
io::{BufRead, BufReader}, io::{BufRead, BufReader},
marker::PhantomData, marker::PhantomData,
mem, mem,
@ -1025,20 +1026,26 @@ impl Loader {
return Ok(wasm_store.load_language(&config.name, &wasm_bytes)?); return Ok(wasm_store.load_language(&config.name, &wasm_bytes)?);
} }
// Create a unique lock path based on the output path hash to prevent
// interference when multiple processes build the same grammar (by name)
// to different output locations
let lock_hash = {
let mut hasher = std::hash::DefaultHasher::new();
output_path.hash(&mut hasher);
format!("{:x}", hasher.finish())
};
let lock_path = if env::var("CROSS_RUNNER").is_ok() { let lock_path = if env::var("CROSS_RUNNER").is_ok() {
tempfile::tempdir() tempfile::tempdir()
.unwrap() .expect("create a temp dir")
.path() .path()
.join("tree-sitter") .to_path_buf()
.join("lock")
.join(format!("{}.lock", config.name))
} else { } else {
etcetera::choose_base_strategy()? etcetera::choose_base_strategy()?.cache_dir()
.cache_dir() }
.join("tree-sitter") .join("tree-sitter")
.join("lock") .join("lock")
.join(format!("{}.lock", config.name)) .join(format!("{}-{lock_hash}.lock", config.name));
};
if let Ok(lock_file) = fs::OpenOptions::new().write(true).open(&lock_path) { if let Ok(lock_file) = fs::OpenOptions::new().write(true).open(&lock_path) {
recompile = false; recompile = false;
@ -1089,6 +1096,26 @@ impl Loader {
} }
} }
// Ensure the dynamic library exists before trying to load it. This can
// happen in race conditions where we couldn't acquire the lock because
// another process was compiling but it still hasn't finished by the
// time we reach this point, so the output file still doesn't exist.
//
// Instead of allowing the `load_language` call below to fail, return a
// clearer error to the user here.
if !output_path.exists() {
let msg = format!(
"Dynamic library `{}` not found after build attempt. \
Are you running multiple processes building to the same output location?",
output_path.display()
);
Err(LoaderError::IO(IoError::new(
std::io::Error::new(std::io::ErrorKind::NotFound, msg),
Some(output_path.as_path()),
)))?;
}
Self::load_language(&output_path, &language_fn_name) Self::load_language(&output_path, &language_fn_name)
} }
@ -1278,6 +1305,11 @@ impl Loader {
})); }));
} }
} }
} else {
warn!(
"Failed to run `nm` to verify symbols in {}",
library_path.display()
);
} }
Ok(()) Ok(())

View file

@ -199,6 +199,7 @@ pub fn run_wasm(args: &BuildWasm) -> Result<()> {
"-D", "NDEBUG=", "-D", "NDEBUG=",
"-D", "_POSIX_C_SOURCE=200112L", "-D", "_POSIX_C_SOURCE=200112L",
"-D", "_DEFAULT_SOURCE=", "-D", "_DEFAULT_SOURCE=",
"-D", "_BSD_SOURCE=",
"-D", "_DARWIN_C_SOURCE=", "-D", "_DARWIN_C_SOURCE=",
"-I", "lib/src", "-I", "lib/src",
"-I", "lib/include", "-I", "lib/include",

View file

@ -61,7 +61,7 @@ function initializeCustomSelect({ initialValue = null, addListeners = false }) {
} }
window.initializePlayground = async (opts) => { window.initializePlayground = async (opts) => {
const { Parser, Language } = window.TreeSitter; const { Parser, Language, Query } = window.TreeSitter;
const { local } = opts; const { local } = opts;
if (local) { if (local) {
@ -357,11 +357,10 @@ window.initializePlayground = async (opts) => {
marks.forEach((m) => m.clear()); marks.forEach((m) => m.clear());
if (tree && query) { if (tree && query) {
const captures = query.captures( const captures = query.captures(tree.rootNode, {
tree.rootNode, startPosition: { row: startRow, column: 0 },
{ row: startRow, column: 0 }, endPosition: { row: endRow, column: 0 },
{ row: endRow, column: 0 }, });
);
let lastNodeId; let lastNodeId;
for (const { name, node } of captures) { for (const { name, node } of captures) {
if (node.id === lastNodeId) continue; if (node.id === lastNodeId) continue;
@ -410,7 +409,7 @@ window.initializePlayground = async (opts) => {
const queryText = queryEditor.getValue(); const queryText = queryEditor.getValue();
try { try {
query = parser.language.query(queryText); query = new Query(parser.language, queryText);
let match; let match;
let row = 0; let row = 0;

View file

@ -49,6 +49,7 @@ fn main() {
.include(&include_path) .include(&include_path)
.define("_POSIX_C_SOURCE", "200112L") .define("_POSIX_C_SOURCE", "200112L")
.define("_DEFAULT_SOURCE", None) .define("_DEFAULT_SOURCE", None)
.define("_BSD_SOURCE", None)
.define("_DARWIN_C_SOURCE", None) .define("_DARWIN_C_SOURCE", None)
.warnings(false) .warnings(false)
.file(src_path.join("lib.c")) .file(src_path.join("lib.c"))

File diff suppressed because it is too large Load diff