From 45313e0dfb78a3c29ab43b26632185c2d5b40248 Mon Sep 17 00:00:00 2001 From: Amaan Qureshi Date: Fri, 18 Aug 2023 16:14:01 -0400 Subject: [PATCH] feat: add `injection.self` to inject a node with itself --- cli/loader/src/lib.rs | 22 ++++++++++++++++++++++ cli/src/tests/helpers/fixtures.rs | 1 + cli/src/tests/highlight_test.rs | 6 +++++- docs/section-4-syntax-highlighting.md | 1 + highlight/src/c_lib.rs | 7 +++++++ highlight/src/lib.rs | 11 ++++++++++- 6 files changed, 46 insertions(+), 2 deletions(-) diff --git a/cli/loader/src/lib.rs b/cli/loader/src/lib.rs index a2c28be9..2b3896ef 100644 --- a/cli/loader/src/lib.rs +++ b/cli/loader/src/lib.rs @@ -86,6 +86,7 @@ pub struct LanguageConfiguration<'a> { pub injections_filenames: Option>, pub locals_filenames: Option>, pub tags_filenames: Option>, + pub language_name: String, language_id: usize, highlight_config: OnceCell>, tags_config: OnceCell>, @@ -569,6 +570,11 @@ impl Loader { tree_sitter: Vec, } + #[derive(Deserialize)] + struct GrammarJSON { + name: String, + } + let initial_language_configuration_count = self.language_configurations.len(); if let Ok(package_json_contents) = fs::read_to_string(&parser_path.join("package.json")) { @@ -580,6 +586,13 @@ impl Loader { // the package.json, but defaults to the directory containing the package.json. let language_path = parser_path.join(config_json.path); + let grammar_path = language_path.join("src").join("grammar.json"); + let mut grammar_file = fs::File::open(grammar_path) + .with_context(|| "Failed to read grammar.json")?; + let grammar_json: GrammarJSON = + serde_json::from_reader(BufReader::new(&mut grammar_file)) + .with_context(|| "Failed to parse grammar.json")?; + // Determine if a previous language configuration in this package.json file // already uses the same language. let mut language_id = None; @@ -599,6 +612,7 @@ impl Loader { let configuration = LanguageConfiguration { root_path: parser_path.to_path_buf(), + language_name: grammar_json.name, scope: config_json.scope, language_id, file_types: config_json.file_types.unwrap_or(Vec::new()), @@ -631,8 +645,15 @@ impl Loader { if self.language_configurations.len() == initial_language_configuration_count && parser_path.join("src").join("grammar.json").exists() { + let grammar_path = parser_path.join("src").join("grammar.json"); + let mut grammar_file = + fs::File::open(grammar_path).with_context(|| "Failed to read grammar.json")?; + let grammar_json: GrammarJSON = + serde_json::from_reader(BufReader::new(&mut grammar_file)) + .with_context(|| "Failed to parse grammar.json")?; let configuration = LanguageConfiguration { root_path: parser_path.to_owned(), + language_name: grammar_json.name, language_id: self.languages_by_id.len(), file_types: Vec::new(), scope: None, @@ -769,6 +790,7 @@ impl<'a> LanguageConfiguration<'a> { } else { let mut result = HighlightConfiguration::new( language, + &self.language_name, &highlights_query, &injections_query, &locals_query, diff --git a/cli/src/tests/helpers/fixtures.rs b/cli/src/tests/helpers/fixtures.rs index 7232e84a..5d27329b 100644 --- a/cli/src/tests/helpers/fixtures.rs +++ b/cli/src/tests/helpers/fixtures.rs @@ -52,6 +52,7 @@ pub fn get_highlight_config( let locals_query = fs::read_to_string(queries_path.join("locals.scm")).unwrap_or(String::new()); let mut result = HighlightConfiguration::new( language, + language_name, &highlights_query, &injections_query, &locals_query, diff --git a/cli/src/tests/highlight_test.rs b/cli/src/tests/highlight_test.rs index 8a5f22d3..5cdd0f1d 100644 --- a/cli/src/tests/highlight_test.rs +++ b/cli/src/tests/highlight_test.rs @@ -507,12 +507,14 @@ fn test_highlighting_via_c_api() { let js_scope = c_string("source.js"); let js_injection_regex = c_string("^javascript"); let language = get_language("javascript"); + let lang_name = c_string("javascript"); let queries = get_language_queries_path("javascript"); let highlights_query = fs::read_to_string(queries.join("highlights.scm")).unwrap(); let injections_query = fs::read_to_string(queries.join("injections.scm")).unwrap(); let locals_query = fs::read_to_string(queries.join("locals.scm")).unwrap(); c::ts_highlighter_add_language( highlighter, + lang_name.as_ptr(), js_scope.as_ptr(), js_injection_regex.as_ptr(), language, @@ -528,11 +530,13 @@ fn test_highlighting_via_c_api() { let html_scope = c_string("text.html.basic"); let html_injection_regex = c_string("^html"); let language = get_language("html"); + let lang_name = c_string("html"); let queries = get_language_queries_path("html"); let highlights_query = fs::read_to_string(queries.join("highlights.scm")).unwrap(); let injections_query = fs::read_to_string(queries.join("injections.scm")).unwrap(); c::ts_highlighter_add_language( highlighter, + lang_name.as_ptr(), html_scope.as_ptr(), html_injection_regex.as_ptr(), language, @@ -607,7 +611,7 @@ fn test_highlighting_with_all_captures_applied() { [ \"{\" \"}\" \"(\" \")\" ] @punctuation.bracket "}; let mut rust_highlight_reverse = - HighlightConfiguration::new(language, &highlights_query, "", "", true).unwrap(); + HighlightConfiguration::new(language, "rust", &highlights_query, "", "", true).unwrap(); rust_highlight_reverse.configure(&HIGHLIGHT_NAMES); assert_eq!( diff --git a/docs/section-4-syntax-highlighting.md b/docs/section-4-syntax-highlighting.md index 964fb677..fc2c9c17 100644 --- a/docs/section-4-syntax-highlighting.md +++ b/docs/section-4-syntax-highlighting.md @@ -364,6 +364,7 @@ The language injection behavior can also be configured by some properties associ * `injection.language` - can be used to hard-code the name of a specific language. * `injection.combined` - indicates that *all* of the matching nodes in the tree should have their content parsed as *one* nested document. * `injection.include-children` - indicates that the `@injection.content` node's *entire* text should be re-parsed, including the text of its child nodes. By default, child nodes' text will be *excluded* from the injected document. +* `injection.self` - indicates that the `@injection.content` node should be parsed using the same language as the parent node. This is useful for cases where the parent node's language is not known until runtime (e.g. via inheriting another language) #### Examples diff --git a/highlight/src/c_lib.rs b/highlight/src/c_lib.rs index 7f1c9490..33197088 100644 --- a/highlight/src/c_lib.rs +++ b/highlight/src/c_lib.rs @@ -29,6 +29,7 @@ pub enum ErrorCode { InvalidUtf8, InvalidRegex, InvalidQuery, + InvalidLanguageName, } #[no_mangle] @@ -61,6 +62,7 @@ pub extern "C" fn ts_highlighter_new( #[no_mangle] pub extern "C" fn ts_highlighter_add_language( this: *mut TSHighlighter, + language_name: *const c_char, scope_name: *const c_char, injection_regex: *const c_char, language: Language, @@ -110,8 +112,13 @@ pub extern "C" fn ts_highlighter_add_language( "" }; + let lang = unsafe { CStr::from_ptr(language_name) } + .to_str() + .or(Err(ErrorCode::InvalidLanguageName))?; + let mut config = HighlightConfiguration::new( language, + lang, highlight_query, injection_query, locals_query, diff --git a/highlight/src/lib.rs b/highlight/src/lib.rs index 4e96fe41..20ac5edf 100644 --- a/highlight/src/lib.rs +++ b/highlight/src/lib.rs @@ -102,6 +102,7 @@ pub enum HighlightEvent { /// This struct is immutable and can be shared between threads. pub struct HighlightConfiguration { pub language: Language, + pub language_name: String, pub query: Query, pub apply_all_captures: bool, combined_injections_query: Option, @@ -244,6 +245,7 @@ impl HighlightConfiguration { /// Returns a `HighlightConfiguration` that can then be used with the `highlight` method. pub fn new( language: Language, + name: impl Into, highlights_query: &str, injection_query: &str, locals_query: &str, @@ -327,6 +329,7 @@ impl HighlightConfiguration { let highlight_indices = vec![None; query.capture_names().len()]; Ok(HighlightConfiguration { language, + language_name: name.into(), query, apply_all_captures, combined_injections_query, @@ -1110,7 +1113,7 @@ impl HtmlRenderer { } fn injection_for_match<'a>( - config: &HighlightConfiguration, + config: &'a HighlightConfiguration, query: &'a Query, query_match: &QueryMatch<'a, 'a>, source: &'a [u8], @@ -1141,6 +1144,12 @@ fn injection_for_match<'a>( } } + "injection.self" => { + if language_name.is_none() { + language_name = Some(config.language_name.as_str()); + } + } + // By default, injections do not include the *children* of an // `injection.content` node - only the ranges that belong to the // node itself. This can be changed using a `#set!` predicate that