aboutsummaryrefslogtreecommitdiff
path: root/helix-loader
diff options
context:
space:
mode:
Diffstat (limited to 'helix-loader')
-rw-r--r--helix-loader/src/grammar.rs23
-rw-r--r--helix-loader/src/lib.rs81
2 files changed, 81 insertions, 23 deletions
diff --git a/helix-loader/src/grammar.rs b/helix-loader/src/grammar.rs
index 01c966c8..a85cb274 100644
--- a/helix-loader/src/grammar.rs
+++ b/helix-loader/src/grammar.rs
@@ -67,8 +67,9 @@ pub fn get_language(name: &str) -> Result<Language> {
#[cfg(not(target_arch = "wasm32"))]
pub fn get_language(name: &str) -> Result<Language> {
use libloading::{Library, Symbol};
- let mut library_path = crate::runtime_dir().join("grammars").join(name);
- library_path.set_extension(DYLIB_EXTENSION);
+ let mut rel_library_path = PathBuf::new().join("grammars").join(name);
+ rel_library_path.set_extension(DYLIB_EXTENSION);
+ let library_path = crate::runtime_file(&rel_library_path);
let library = unsafe { Library::new(&library_path) }
.with_context(|| format!("Error opening dynamic library {:?}", library_path))?;
@@ -252,7 +253,9 @@ fn fetch_grammar(grammar: GrammarConfiguration) -> Result<FetchStatus> {
remote, revision, ..
} = grammar.source
{
- let grammar_dir = crate::runtime_dir()
+ let grammar_dir = crate::runtime_dirs()
+ .first()
+ .expect("No runtime directories provided") // guaranteed by post-condition
.join("grammars")
.join("sources")
.join(&grammar.grammar_id);
@@ -350,7 +353,9 @@ fn build_grammar(grammar: GrammarConfiguration, target: Option<&str>) -> Result<
let grammar_dir = if let GrammarSource::Local { path } = &grammar.source {
PathBuf::from(&path)
} else {
- crate::runtime_dir()
+ crate::runtime_dirs()
+ .first()
+ .expect("No runtime directories provided") // guaranteed by post-condition
.join("grammars")
.join("sources")
.join(&grammar.grammar_id)
@@ -401,7 +406,10 @@ fn build_tree_sitter_library(
None
}
};
- let parser_lib_path = crate::runtime_dir().join("grammars");
+ let parser_lib_path = crate::runtime_dirs()
+ .first()
+ .expect("No runtime directories provided") // guaranteed by post-condition
+ .join("grammars");
let mut library_path = parser_lib_path.join(&grammar.grammar_id);
library_path.set_extension(DYLIB_EXTENSION);
@@ -511,9 +519,6 @@ fn mtime(path: &Path) -> Result<SystemTime> {
/// Gives the contents of a file from a language's `runtime/queries/<lang>`
/// directory
pub fn load_runtime_file(language: &str, filename: &str) -> Result<String, std::io::Error> {
- let path = crate::RUNTIME_DIR
- .join("queries")
- .join(language)
- .join(filename);
+ let path = crate::runtime_file(&PathBuf::new().join("queries").join(language).join(filename));
std::fs::read_to_string(path)
}
diff --git a/helix-loader/src/lib.rs b/helix-loader/src/lib.rs
index 8dc2928a..04b44b5a 100644
--- a/helix-loader/src/lib.rs
+++ b/helix-loader/src/lib.rs
@@ -2,11 +2,12 @@ pub mod config;
pub mod grammar;
use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
-use std::path::PathBuf;
+use std::path::{Path, PathBuf};
pub const VERSION_AND_GIT_HASH: &str = env!("VERSION_AND_GIT_HASH");
-pub static RUNTIME_DIR: once_cell::sync::Lazy<PathBuf> = once_cell::sync::Lazy::new(runtime_dir);
+static RUNTIME_DIRS: once_cell::sync::Lazy<Vec<PathBuf>> =
+ once_cell::sync::Lazy::new(prioritize_runtime_dirs);
static CONFIG_FILE: once_cell::sync::OnceCell<PathBuf> = once_cell::sync::OnceCell::new();
@@ -25,31 +26,83 @@ pub fn initialize_config_file(specified_file: Option<PathBuf>) {
CONFIG_FILE.set(config_file).ok();
}
-pub fn runtime_dir() -> PathBuf {
- if let Ok(dir) = std::env::var("HELIX_RUNTIME") {
- return dir.into();
- }
-
+/// A list of runtime directories from highest to lowest priority
+///
+/// The priority is:
+///
+/// 1. sibling directory to `CARGO_MANIFEST_DIR` (if environment variable is set)
+/// 2. subdirectory of user config directory (always included)
+/// 3. `HELIX_RUNTIME` (if environment variable is set)
+/// 4. subdirectory of path to helix executable (always included)
+///
+/// Postcondition: returns at least two paths (they might not exist).
+fn prioritize_runtime_dirs() -> Vec<PathBuf> {
+ const RT_DIR: &str = "runtime";
+ // Adding higher priority first
+ let mut rt_dirs = Vec::new();
if let Ok(dir) = std::env::var("CARGO_MANIFEST_DIR") {
// this is the directory of the crate being run by cargo, we need the workspace path so we take the parent
let path = std::path::PathBuf::from(dir).parent().unwrap().join(RT_DIR);
log::debug!("runtime dir: {}", path.to_string_lossy());
- return path;
+ rt_dirs.push(path);
}
- const RT_DIR: &str = "runtime";
- let conf_dir = config_dir().join(RT_DIR);
- if conf_dir.exists() {
- return conf_dir;
+ let conf_rt_dir = config_dir().join(RT_DIR);
+ rt_dirs.push(conf_rt_dir);
+
+ if let Ok(dir) = std::env::var("HELIX_RUNTIME") {
+ rt_dirs.push(dir.into());
}
// fallback to location of the executable being run
// canonicalize the path in case the executable is symlinked
- std::env::current_exe()
+ let exe_rt_dir = std::env::current_exe()
.ok()
.and_then(|path| std::fs::canonicalize(path).ok())
.and_then(|path| path.parent().map(|path| path.to_path_buf().join(RT_DIR)))
- .unwrap()
+ .unwrap();
+ rt_dirs.push(exe_rt_dir);
+ rt_dirs
+}
+
+/// Runtime directories ordered from highest to lowest priority
+///
+/// All directories should be checked when looking for files.
+///
+/// Postcondition: returns at least one path (it might not exist).
+pub fn runtime_dirs() -> &'static [PathBuf] {
+ &RUNTIME_DIRS
+}
+
+/// Find file with path relative to runtime directory
+///
+/// `rel_path` should be the relative path from within the `runtime/` directory.
+/// The valid runtime directories are searched in priority order and the first
+/// file found to exist is returned, otherwise None.
+fn find_runtime_file(rel_path: &Path) -> Option<PathBuf> {
+ RUNTIME_DIRS.iter().find_map(|rt_dir| {
+ let path = rt_dir.join(rel_path);
+ if path.exists() {
+ Some(path)
+ } else {
+ None
+ }
+ })
+}
+
+/// Find file with path relative to runtime directory
+///
+/// `rel_path` should be the relative path from within the `runtime/` directory.
+/// The valid runtime directories are searched in priority order and the first
+/// file found to exist is returned, otherwise the path to the final attempt
+/// that failed.
+pub fn runtime_file(rel_path: &Path) -> PathBuf {
+ find_runtime_file(rel_path).unwrap_or_else(|| {
+ RUNTIME_DIRS
+ .last()
+ .map(|dir| dir.join(rel_path))
+ .unwrap_or_default()
+ })
}
pub fn config_dir() -> PathBuf {