Something went wrong. Try again.
Render code to ANSI, HTML or Hiccup, syntax highlighted the same way Helix does it github.com/waddie/dathan/releases
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183//! Discovery of Helix runtime directories.//!//! Helix ships grammars at `runtime/grammars/<lang>.{dylib,so}` and queries at//! `runtime/queries/<lang>/{highlights,injections,locals}.scm`. Several runtime//! trees may coexist; user config shadows the source distribution.
use std::path::{Path, PathBuf};
/// Platform shared-library extension for compiled grammars.#[cfg(target_os = "macos")]pub const DYLIB_EXT: &str = "dylib";#[cfg(all(unix, not(target_os = "macos")))]pub const DYLIB_EXT: &str = "so";#[cfg(windows)]pub const DYLIB_EXT: &str = "dll";
/// An ordered set of runtime roots. Earlier roots take precedence.pub struct Runtime { roots: Vec<PathBuf>, /// How many leading entries of `roots` came from `--runtime` overrides. override_count: usize,}
impl Runtime { /// Build the runtime search path. `overrides` (from `--runtime`) win, then /// the user config runtime, then `$HELIX_RUNTIME`. pub fn new(overrides: &[PathBuf]) -> Self { let mut roots: Vec<PathBuf> = overrides.iter().filter(|p| p.is_dir()).cloned().collect(); let override_count = roots.len();
if let Some(home) = home_dir() { let dir = home.join(".config/helix/runtime"); if dir.is_dir() { roots.push(dir); } } if let Some(env) = std::env::var_os("HELIX_RUNTIME") { let dir = PathBuf::from(env); if dir.is_dir() { roots.push(dir); } }
Runtime { roots, override_count, } }
/// First existing match for a path relative to a runtime root. pub fn find_file(&self, rel: &Path) -> Option<PathBuf> { self.roots.iter().map(|r| r.join(rel)).find(|p| p.exists()) }
pub fn roots(&self) -> &[PathBuf] { &self.roots }
/// Roots supplied via `--runtime` (highest priority). pub fn override_roots(&self) -> &[PathBuf] { &self.roots[..self.override_count] }
/// Roots discovered from config dir / `$HELIX_RUNTIME` (below overrides). pub fn config_roots(&self) -> &[PathBuf] { &self.roots[self.override_count..] }}
/// The user's home directory, if known.pub fn home_dir() -> Option<PathBuf> { std::env::var_os("HOME").map(PathBuf::from)}
/// Serialises every test that touches the process environment. `set_var` is/// unsound while another thread reads the environment, and `cargo test` runs/// tests in parallel, so readers (`Runtime::new`, `temp_dir`) must hold this/// too - not just the writers in `EnvGuard`.#[cfg(test)]pub(crate) static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
/// Take [`ENV_LOCK`], ignoring poisoning: a panicking test leaves the/// environment restored by `EnvGuard`'s `Drop`, so the data is still sound.#[cfg(test)]pub(crate) fn env_lock() -> std::sync::MutexGuard<'static, ()> { ENV_LOCK .lock() .unwrap_or_else(std::sync::PoisonError::into_inner)}
/// Set an env var for the test, restoring the previous value on drop/// (including on panic) so other tests in the process are unaffected.////// Callers must hold [`env_lock`] for at least as long as the guard.#[cfg(test)]pub(crate) struct EnvGuard { key: &'static str, prev: Option<std::ffi::OsString>,}
#[cfg(test)]impl EnvGuard { pub(crate) fn set(key: &'static str, value: impl AsRef<std::ffi::OsStr>) -> Self { let prev = std::env::var_os(key); // SAFETY: the caller holds `env_lock`, which every environment // reader in the test suite also takes, so no other thread is in // the environment while this writes. unsafe { std::env::set_var(key, value) }; Self { key, prev } }}
#[cfg(test)]impl Drop for EnvGuard { fn drop(&mut self) { // SAFETY: as in `set` - the guard is dropped before the `env_lock` // guard that the test holds, so this still runs exclusively. unsafe { match &self.prev { Some(v) => std::env::set_var(self.key, v), None => std::env::remove_var(self.key), } } }}
#[cfg(test)]mod tests { use super::*; use std::fs;
/// Create `<root>/grammars/<marker>.txt` so `find_file` has something to hit, /// returning the runtime root. fn seed_root(base: &Path, name: &str, marker: &str) -> PathBuf { let root = base.join(name); let dir = root.join("grammars"); fs::create_dir_all(&dir).unwrap(); fs::write(dir.join("test.txt"), marker).unwrap(); root }
#[test] fn config_runtime_beats_helix_runtime_and_overrides_win() { // Declared first so it outlives the `EnvGuard`s below, which restore // the environment on drop and so must also run under the lock. let _env = env_lock();
let base = std::env::temp_dir().join(format!("dathan-rt-{}", std::process::id())); let _ = fs::remove_dir_all(&base);
let home = base.join("home"); let config_rt = seed_root(&home, ".config/helix/runtime", "config"); let env_rt = seed_root(&base, "env-runtime", "env"); let override_rt = seed_root(&base, "override", "override");
let _home_guard = EnvGuard::set("HOME", &home); let _runtime_guard = EnvGuard::set("HELIX_RUNTIME", &env_rt);
let rel = Path::new("grammars/test.txt");
// No override: the user config runtime wins over `$HELIX_RUNTIME`. let rt = Runtime::new(&[]); let found = rt.find_file(rel).unwrap(); assert!( found.starts_with(&config_rt), "expected config copy, got {found:?}" ); assert_eq!(fs::read_to_string(found).unwrap(), "config");
// With an override, it wins over everything. let rt = Runtime::new(std::slice::from_ref(&override_rt)); let found = rt.find_file(rel).unwrap(); assert!( found.starts_with(&override_rt), "expected override copy, got {found:?}" ); assert_eq!(fs::read_to_string(found).unwrap(), "override"); assert_eq!(rt.override_roots(), &[override_rt]);
let _ = fs::remove_dir_all(&base); }}