From 04826e1e95eee78d65bca4e13c134362dec04635 Mon Sep 17 00:00:00 2001 From: flamestro Date: Mon, 2 Mar 2026 03:33:50 +0800 Subject: [PATCH 1/2] fix: add syntax definitions in assets directory to binary --- README.md | 11 ++----- assets/syntaxes/README.md | 8 ++--- build.rs | 69 +++++++++++++++++++++++++++++++++++++++ src/syntax.rs | 61 +++++++++++++++++++++++++++------- 4 files changed, 125 insertions(+), 24 deletions(-) create mode 100644 build.rs diff --git a/README.md b/README.md index ab6321f..4db3aec 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ curl -fsSL https://raw.githubusercontent.com/flamestro/deff/main/install.sh | ba Installer source: https://github.com/flamestro/deff/blob/main/install.sh The script checks for `cargo`, clones this project into a temporary directory, installs it, and removes the temporary checkout. +Bundled syntax grammars are compiled into the binary, so removing the checkout does not affect highlighting. If you have local edits (including untracked files) and want to review them before committing, run: @@ -109,16 +110,10 @@ Theme selection: Custom syntax grammars: -- `deff` loads syntect defaults plus any extra `.sublime-syntax` files found in: +- `deff` loads syntect defaults, bundled deff grammars, plus any extra `.sublime-syntax` files found in: - `assets/syntaxes` (current working directory) - `.deff/syntaxes` (current working directory) - - `DEFF_SYNTAX_DIR` - - `DEFF_SYNTAX_PATHS` (path list, colon-separated on macOS/Linux) -- Example: - - ```bash - DEFF_SYNTAX_DIR="$HOME/.config/deff/syntaxes" deff - ``` +- Any `*.sublime-syntax` file added under this repo's `assets/syntaxes` is auto-bundled at build time. Search and reviewed workflow: diff --git a/assets/syntaxes/README.md b/assets/syntaxes/README.md index 3868cc5..b80a296 100644 --- a/assets/syntaxes/README.md +++ b/assets/syntaxes/README.md @@ -1,12 +1,12 @@ -This directory contains extra `.sublime-syntax` grammars loaded by deff at startup. +These `.sublime-syntax` grammars are bundled into the deff binary at compile time. -You can add more grammar files here to extend language coverage further. +Any `*.sublime-syntax` file added here is automatically bundled; no manual `src/syntax.rs` update is required. + +You can also add more grammar files here (or in the locations below) to extend language coverage further. deff loads custom syntaxes from these locations (if present): - `assets/syntaxes` (relative to the current working directory) - `.deff/syntaxes` (relative to the current working directory) -- `DEFF_SYNTAX_DIR` -- `DEFF_SYNTAX_PATHS` (OS path list, e.g. colon-separated on macOS/Linux) Syntactic detection still uses syntect APIs first, then first-line/shebang fallback. diff --git a/build.rs b/build.rs new file mode 100644 index 0000000..2fe73f7 --- /dev/null +++ b/build.rs @@ -0,0 +1,69 @@ +use std::{ + env, fs, io, + path::{Path, PathBuf}, +}; + +fn main() { + let syntax_directory = Path::new("assets/syntaxes"); + println!("cargo:rerun-if-changed={}", syntax_directory.display()); + + let syntax_files = collect_syntax_files(syntax_directory) + .unwrap_or_else(|error| panic!("failed to scan {}: {error}", syntax_directory.display())); + + for syntax_file in &syntax_files { + println!("cargo:rerun-if-changed={}", syntax_file.display()); + } + + let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR is set by cargo")); + let output_path = out_dir.join("bundled_syntaxes.rs"); + write_bundled_syntaxes(&output_path, &syntax_files) + .unwrap_or_else(|error| panic!("failed to write {}: {error}", output_path.display())); +} + +fn collect_syntax_files(directory: &Path) -> io::Result> { + let mut files = Vec::new(); + let mut pending = vec![directory.to_path_buf()]; + + while let Some(current_directory) = pending.pop() { + for entry in fs::read_dir(¤t_directory)? { + let entry = entry?; + let path = entry.path(); + let file_type = entry.file_type()?; + + if file_type.is_dir() { + pending.push(path); + continue; + } + + if file_type.is_file() + && path + .extension() + .is_some_and(|extension| extension == "sublime-syntax") + { + files.push(path); + } + } + } + + files.sort(); + Ok(files) +} + +fn write_bundled_syntaxes(output_path: &Path, syntax_files: &[PathBuf]) -> io::Result<()> { + let mut contents = String::from("const BUNDLED_SYNTAXES: &[(&str, &str)] = &[\n"); + + for syntax_file in syntax_files { + let file_name = syntax_file + .file_name() + .and_then(|name| name.to_str()) + .expect("syntax file name should be valid UTF-8"); + let relative_path = syntax_file.to_string_lossy().replace('\\', "/"); + + contents.push_str(&format!( + " ({file_name:?}, include_str!(concat!(env!(\"CARGO_MANIFEST_DIR\"), \"/{relative_path}\"))),\n" + )); + } + + contents.push_str("];\n"); + fs::write(output_path, contents) +} diff --git a/src/syntax.rs b/src/syntax.rs index cf6e4e6..07ed8ad 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -4,11 +4,11 @@ use std::{ }; use once_cell::sync::Lazy; -use syntect::parsing::SyntaxSet; +use syntect::parsing::{SyntaxDefinition, SyntaxSet, SyntaxSetBuilder}; const DEFAULT_RELATIVE_SYNTAX_DIRS: &[&str] = &["assets/syntaxes", ".deff/syntaxes"]; -const ENV_SYNTAX_DIR: &str = "DEFF_SYNTAX_DIR"; -const ENV_SYNTAX_PATHS: &str = "DEFF_SYNTAX_PATHS"; + +include!(concat!(env!("OUT_DIR"), "/bundled_syntaxes.rs")); static SYNTAX_SET: Lazy = Lazy::new(load_syntax_set); @@ -18,6 +18,7 @@ pub(crate) fn syntax_set() -> &'static SyntaxSet { fn load_syntax_set() -> SyntaxSet { let mut builder = SyntaxSet::load_defaults_newlines().into_builder(); + add_bundled_syntaxes(&mut builder); for directory in syntax_directories() { if let Err(error) = builder.add_from_folder(&directory, true) { @@ -31,18 +32,24 @@ fn load_syntax_set() -> SyntaxSet { builder.build() } -fn syntax_directories() -> Vec { - let mut candidates = Vec::new(); - candidates.push(Path::new(env!("CARGO_MANIFEST_DIR")).join("assets/syntaxes")); - candidates.extend(DEFAULT_RELATIVE_SYNTAX_DIRS.iter().map(PathBuf::from)); +fn add_bundled_syntaxes(builder: &mut SyntaxSetBuilder) { + for (file_name, source) in BUNDLED_SYNTAXES { + let fallback_name = Path::new(file_name) + .file_stem() + .and_then(|stem| stem.to_str()); - if let Some(value) = std::env::var_os(ENV_SYNTAX_PATHS) { - candidates.extend(std::env::split_paths(&value)); + match SyntaxDefinition::load_from_str(source, true, fallback_name) { + Ok(definition) => builder.add(definition), + Err(error) => { + eprintln!("deff: failed to load bundled syntax {}: {error}", file_name); + } + } } +} - if let Some(value) = std::env::var_os(ENV_SYNTAX_DIR) { - candidates.push(PathBuf::from(value)); - } +fn syntax_directories() -> Vec { + let mut candidates = Vec::new(); + candidates.extend(DEFAULT_RELATIVE_SYNTAX_DIRS.iter().map(PathBuf::from)); let cwd = std::env::current_dir().ok(); let mut unique = HashSet::new(); @@ -68,3 +75,33 @@ fn syntax_directories() -> Vec { resolved } + +#[cfg(test)] +mod tests { + use std::path::Path; + + use syntect::parsing::SyntaxDefinition; + + use super::{load_syntax_set, BUNDLED_SYNTAXES}; + + #[test] + fn every_bundled_syntax_file_is_loaded() { + let syntaxes = load_syntax_set(); + + for (file_name, source) in BUNDLED_SYNTAXES { + let fallback_name = Path::new(file_name) + .file_stem() + .and_then(|stem| stem.to_str()); + let definition = SyntaxDefinition::load_from_str(source, true, fallback_name) + .unwrap_or_else(|error| { + panic!("failed to parse bundled syntax {file_name}: {error}") + }); + + assert!( + syntaxes.find_syntax_by_name(&definition.name).is_some(), + "expected bundled syntax {} from {file_name}", + definition.name + ); + } + } +} From 9a73d67620977edb08cc0098940c758f8baeb557 Mon Sep 17 00:00:00 2001 From: flamestro Date: Mon, 2 Mar 2026 03:35:57 +0800 Subject: [PATCH 2/2] fmt --- src/syntax.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/syntax.rs b/src/syntax.rs index 07ed8ad..0776b75 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -82,7 +82,7 @@ mod tests { use syntect::parsing::SyntaxDefinition; - use super::{load_syntax_set, BUNDLED_SYNTAXES}; + use super::{BUNDLED_SYNTAXES, load_syntax_set}; #[test] fn every_bundled_syntax_file_is_loaded() {