From d9ba5f81c5adb271a3dcf1eb2066914aca30b589 Mon Sep 17 00:00:00 2001 From: Martin Matous Date: Wed, 13 Apr 2022 15:53:46 +0200 Subject: [PATCH 1/8] expose only select paths from loader Signed-off-by: Martin Matous --- helix-loader/src/grammar.rs | 15 +++++---------- helix-loader/src/lib.rs | 22 +++++++++++++++++++--- helix-term/src/application.rs | 10 +++++----- helix-term/src/commands/typed.rs | 2 +- helix-term/src/main.rs | 7 ++++--- helix-term/src/ui/mod.rs | 6 ++---- helix-view/src/theme.rs | 4 ++-- 7 files changed, 38 insertions(+), 28 deletions(-) diff --git a/helix-loader/src/grammar.rs b/helix-loader/src/grammar.rs index 7474713a50be..a0deba478e94 100644 --- a/helix-loader/src/grammar.rs +++ b/helix-loader/src/grammar.rs @@ -59,7 +59,7 @@ const REMOTE_NAME: &str = "origin"; pub fn get_language(name: &str) -> Result { let name = name.to_ascii_lowercase(); - let mut library_path = crate::runtime_dir().join("grammars").join(&name); + let mut library_path = crate::grammar_dir().join(&name); library_path.set_extension(DYLIB_EXTENSION); let library = unsafe { Library::new(&library_path) } @@ -142,8 +142,7 @@ fn fetch_grammar(grammar: GrammarConfiguration) -> Result<()> { remote, revision, .. } = grammar.source { - let grammar_dir = crate::runtime_dir() - .join("grammars") + let grammar_dir = crate::grammar_dir() .join("sources") .join(&grammar.grammar_id); @@ -233,8 +232,7 @@ fn build_grammar(grammar: GrammarConfiguration) -> Result<()> { let grammar_dir = if let GrammarSource::Local { path } = &grammar.source { PathBuf::from(&path) } else { - crate::runtime_dir() - .join("grammars") + crate::grammar_dir() .join("sources") .join(&grammar.grammar_id) }; @@ -280,7 +278,7 @@ fn build_tree_sitter_library(src_path: &Path, grammar: GrammarConfiguration) -> None } }; - let parser_lib_path = crate::runtime_dir().join("grammars"); + let parser_lib_path = crate::grammar_dir(); let mut library_path = parser_lib_path.join(&grammar.grammar_id); library_path.set_extension(DYLIB_EXTENSION); @@ -385,9 +383,6 @@ fn mtime(path: &Path) -> Result { /// Gives the contents of a file from a language's `runtime/queries/` /// directory pub fn load_runtime_file(language: &str, filename: &str) -> Result { - let path = crate::RUNTIME_DIR - .join("queries") - .join(language) - .join(filename); + let path = crate::query_dir().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 a2c4d96f0688..7ac2233bf162 100644 --- a/helix-loader/src/lib.rs +++ b/helix-loader/src/lib.rs @@ -28,7 +28,7 @@ pub fn runtime_dir() -> std::path::PathBuf { .unwrap() } -pub fn config_dir() -> std::path::PathBuf { +fn config_dir() -> std::path::PathBuf { // TODO: allow env var override let strategy = choose_base_strategy().expect("Unable to find the config directory!"); let mut path = strategy.config_dir(); @@ -36,7 +36,7 @@ pub fn config_dir() -> std::path::PathBuf { path } -pub fn cache_dir() -> std::path::PathBuf { +fn cache_dir() -> std::path::PathBuf { // TODO: allow env var override let strategy = choose_base_strategy().expect("Unable to find the config directory!"); let mut path = strategy.cache_dir(); @@ -48,6 +48,10 @@ pub fn config_file() -> std::path::PathBuf { config_dir().join("config.toml") } +pub fn grammar_dir() -> std::path::PathBuf { + config_dir().join("grammars") +} + pub fn lang_config_file() -> std::path::PathBuf { config_dir().join("languages.toml") } @@ -56,6 +60,18 @@ pub fn log_file() -> std::path::PathBuf { cache_dir().join("helix.log") } +pub fn theme_dir() -> std::path::PathBuf { + config_dir().join("themes") +} + +pub fn tutor_file() -> std::path::PathBuf { + config_dir().join("tutor.txt") +} + +pub fn query_dir() -> std::path::PathBuf { + config_dir().join("queries") +} + /// Default bultin-in languages.toml. pub fn default_lang_config() -> toml::Value { toml::from_slice(include_bytes!("../../languages.toml")) @@ -65,7 +81,7 @@ pub fn default_lang_config() -> toml::Value { /// User configured languages.toml file, merged with the default config. pub fn user_lang_config() -> Result { let def_lang_conf = default_lang_config(); - let data = std::fs::read(crate::config_dir().join("languages.toml")); + let data = std::fs::read(lang_config_file()); let user_lang_conf = match data { Ok(raw) => { let value = toml::from_slice(&raw)?; diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index bc5f3bd774f2..6bce94492f8f 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -61,10 +61,10 @@ impl Application { let mut compositor = Compositor::new()?; let size = compositor.size(); - let conf_dir = helix_loader::config_dir(); - - let theme_loader = - std::sync::Arc::new(theme::Loader::new(&conf_dir, &helix_loader::runtime_dir())); + let theme_loader = std::sync::Arc::new(theme::Loader::new( + &helix_loader::theme_dir(), + &helix_loader::runtime_dir(), + )); let true_color = config.editor.true_color || crate::true_color(); let theme = config @@ -115,7 +115,7 @@ impl Application { compositor.push(editor_view); if args.load_tutor { - let path = helix_loader::runtime_dir().join("tutor.txt"); + let path = helix_loader::tutor_file(); editor.open(path, Action::VerticalSplit)?; // Unset path to prevent accidentally saving to the original tutor file. doc_mut!(editor).set_path(None)?; diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs index d158388fcbf5..afd8d24bcfaf 100644 --- a/helix-term/src/commands/typed.rs +++ b/helix-term/src/commands/typed.rs @@ -886,7 +886,7 @@ fn tutor( _args: &[Cow], _event: PromptEvent, ) -> anyhow::Result<()> { - let path = helix_loader::runtime_dir().join("tutor.txt"); + let path = helix_loader::tutor_file(); cx.editor.open(path, Action::Replace)?; // Unset path to prevent accidentally saving to the original tutor file. doc_mut!(cx.editor).set_path(None)?; diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index 4a3434d1f01c..78ea2cfd3cd5 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -109,9 +109,10 @@ FLAGS: return Ok(0); } - let conf_dir = helix_loader::config_dir(); - if !conf_dir.exists() { - std::fs::create_dir_all(&conf_dir).ok(); + if let Some(conf_dir) = helix_loader::config_file().parent() { + if !conf_dir.exists() { + std::fs::create_dir_all(&conf_dir).ok(); + } } let config = match Config::load_default() { diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 2dca870baa42..a76ac925cb86 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -234,10 +234,8 @@ pub mod completers { } pub fn theme(_editor: &Editor, input: &str) -> Vec { - let mut names = theme::Loader::read_names(&helix_loader::runtime_dir().join("themes")); - names.extend(theme::Loader::read_names( - &helix_loader::config_dir().join("themes"), - )); + let mut names = theme::Loader::read_names(&helix_loader::theme_dir()); + names.extend(theme::Loader::read_names(&helix_loader::theme_dir())); names.push("default".into()); names.push("base16_default".into()); diff --git a/helix-view/src/theme.rs b/helix-view/src/theme.rs index 3f45aac6ea3f..583b72252cbf 100644 --- a/helix-view/src/theme.rs +++ b/helix-view/src/theme.rs @@ -29,8 +29,8 @@ impl Loader { /// Creates a new loader that can load themes from two directories. pub fn new>(user_dir: P, default_dir: P) -> Self { Self { - user_dir: user_dir.as_ref().join("themes"), - default_dir: default_dir.as_ref().join("themes"), + user_dir: PathBuf::from(user_dir.as_ref()), + default_dir: PathBuf::from(default_dir.as_ref()), } } From 3c17e68ee2e6392f479ebe525b496f453801be70 Mon Sep 17 00:00:00 2001 From: Martin Matous Date: Wed, 13 Apr 2022 16:03:03 +0200 Subject: [PATCH 2/8] drop etcetera in favor of directories It wasn't updated to support STATE_HOME, directories make better effort at being multiplatform and have better fallbacks Signed-off-by: Martin Matous --- Cargo.lock | 22 +++++++++++++++++++++- helix-loader/Cargo.toml | 2 +- helix-loader/src/lib.rs | 19 +++++++------------ 3 files changed, 29 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4bbd4c252a9e..f00ed86f1b91 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -155,6 +155,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "directories" +version = "4.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" +dependencies = [ + "dirs-sys", +] + [[package]] name = "dirs-next" version = "2.0.0" @@ -165,6 +174,17 @@ dependencies = [ "dirs-sys-next", ] +[[package]] +name = "dirs-sys" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -402,7 +422,7 @@ version = "0.6.0" dependencies = [ "anyhow", "cc", - "etcetera", + "directories", "libloading", "once_cell", "serde", diff --git a/helix-loader/Cargo.toml b/helix-loader/Cargo.toml index 21b37333afc8..800a004abbbd 100644 --- a/helix-loader/Cargo.toml +++ b/helix-loader/Cargo.toml @@ -13,7 +13,7 @@ homepage = "https://helix-editor.com" anyhow = "1" serde = { version = "1.0", features = ["derive"] } toml = "0.5" -etcetera = "0.3" +directories = "4" tree-sitter = "0.20" libloading = "0.7" once_cell = "1.9" diff --git a/helix-loader/src/lib.rs b/helix-loader/src/lib.rs index 7ac2233bf162..041d7584458d 100644 --- a/helix-loader/src/lib.rs +++ b/helix-loader/src/lib.rs @@ -1,10 +1,13 @@ pub mod grammar; -use etcetera::base_strategy::{choose_base_strategy, BaseStrategy}; - pub static RUNTIME_DIR: once_cell::sync::Lazy = once_cell::sync::Lazy::new(runtime_dir); +fn project_dirs() -> directories::ProjectDirs { + directories::ProjectDirs::from("com", "helix-editor", "helix") + .expect("Unable to continue. User has no home") +} + pub fn runtime_dir() -> std::path::PathBuf { if let Ok(dir) = std::env::var("HELIX_RUNTIME") { return dir.into(); @@ -29,19 +32,11 @@ pub fn runtime_dir() -> std::path::PathBuf { } fn config_dir() -> std::path::PathBuf { - // TODO: allow env var override - let strategy = choose_base_strategy().expect("Unable to find the config directory!"); - let mut path = strategy.config_dir(); - path.push("helix"); - path + project_dirs().config_dir().to_path_buf() } fn cache_dir() -> std::path::PathBuf { - // TODO: allow env var override - let strategy = choose_base_strategy().expect("Unable to find the config directory!"); - let mut path = strategy.cache_dir(); - path.push("helix"); - path + project_dirs().cache_dir().to_path_buf() } pub fn config_file() -> std::path::PathBuf { From 78e756d7ebf0440b80c22fafc4043917d782f128 Mon Sep 17 00:00:00 2001 From: Martin Matous Date: Wed, 13 Apr 2022 16:28:55 +0200 Subject: [PATCH 3/8] allow setting config location via HELIX_CONFIG Signed-off-by: Martin Matous --- helix-loader/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/helix-loader/src/lib.rs b/helix-loader/src/lib.rs index 041d7584458d..794c5e8f7e4b 100644 --- a/helix-loader/src/lib.rs +++ b/helix-loader/src/lib.rs @@ -40,6 +40,9 @@ fn cache_dir() -> std::path::PathBuf { } pub fn config_file() -> std::path::PathBuf { + if let Ok(dir) = std::env::var("HELIX_CONFIG") { + return std::path::PathBuf::from(dir); + } config_dir().join("config.toml") } From 1d46f55685aea4887733db6a3bd652e2ea2d9eee Mon Sep 17 00:00:00 2001 From: Martin Matous Date: Wed, 13 Apr 2022 16:42:58 +0200 Subject: [PATCH 4/8] support setting paths from config file Final executable doesn't try to detect build environment anymore, build env simply sets HELIX_CONFIG envvar and supplies it's own config.toml with build-specific paths. Signed-off-by: Martin Matous --- .cargo/config | 3 + helix-core/src/syntax.rs | 30 ++++++ helix-core/tests/indent.rs | 27 ++++++ helix-loader/src/lib.rs | 177 ++++++++++++++++++++++++++++++---- helix-term/Cargo.toml | 2 + helix-term/build.rs | 18 +++- helix-term/src/application.rs | 2 +- helix-term/src/config.rs | 56 +++++++++++ helix-term/src/main.rs | 67 +++++++------ helix-term/src/ui/mod.rs | 3 +- runtime/config.toml | 6 ++ 11 files changed, 333 insertions(+), 58 deletions(-) create mode 100644 runtime/config.toml diff --git a/.cargo/config b/.cargo/config index 35049cbcb13c..8e11c63edfcf 100644 --- a/.cargo/config +++ b/.cargo/config @@ -1,2 +1,5 @@ [alias] xtask = "run --package xtask --" + +[env] +HELIX_CONFIG = { value = "./runtime/config.toml", force = true, relative = true } diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs index 72b0e95623e8..356014b1d9ce 100644 --- a/helix-core/src/syntax.rs +++ b/helix-core/src/syntax.rs @@ -1962,8 +1962,34 @@ mod test { use super::*; use crate::{Rope, Transaction}; + fn setup() { + use std::sync::Once; + // mirror struct for helix_term's Config + // original can't be used because it's not built yet + #[derive(Deserialize)] + struct BuildConfig { + paths: helix_loader::Paths, + } + + static SETUP: Once = Once::new(); + SETUP.call_once(|| { + // config.toml is written relative to workspace root, cd there + assert!(std::env::set_current_dir("..").is_ok()); + + let config = std::env::var("HELIX_CONFIG").unwrap(); + let config = std::fs::read_to_string(config).unwrap(); + let paths = toml::from_str::(&config).unwrap().paths; + helix_loader::init_paths(paths).unwrap(); + + // cd back, don't interfere with other tests + assert!(std::env::set_current_dir("./helix-core").is_ok()); + }); + } + #[test] fn test_textobject_queries() { + setup(); + let query_str = r#" (line_comment)+ @quantified_nodes ((line_comment)+) @quantified_nodes_grouped @@ -2010,6 +2036,8 @@ mod test { #[test] fn test_parser() { + setup(); + let highlight_names: Vec = [ "attribute", "constant", @@ -2130,6 +2158,8 @@ mod test { #[test] fn test_load_runtime_file() { + setup(); + // Test to make sure we can load some data from the runtime directory. let contents = load_runtime_file("rust", "indents.scm").unwrap(); assert!(!contents.is_empty()); diff --git a/helix-core/tests/indent.rs b/helix-core/tests/indent.rs index ff04d05f5bbe..ade6c9fd8c78 100644 --- a/helix-core/tests/indent.rs +++ b/helix-core/tests/indent.rs @@ -5,12 +5,39 @@ use helix_core::{ }; use std::path::PathBuf; +fn setup() { + use std::sync::Once; + + // mirror struct for helix_term's Config + // original can't be used because it's not built yet + #[derive(serde::Deserialize)] + struct BuildConfig { + paths: helix_loader::Paths, + } + static SETUP: Once = Once::new(); + + SETUP.call_once(|| { + // config.toml is written relative to workspace root, cd there + std::env::set_current_dir("..").unwrap(); + + let config = std::env::var("HELIX_CONFIG").unwrap(); + let config = std::fs::read_to_string(config).unwrap(); + let paths = toml::from_str::(&config).unwrap().paths; + helix_loader::init_paths(paths).unwrap(); + + // cd back, don't interfere with other tests + std::env::set_current_dir("./helix-core").unwrap(); + }); +} + #[test] fn test_treesitter_indent_rust() { + setup(); test_treesitter_indent("rust.rs", "source.rust"); } #[test] fn test_treesitter_indent_rust_2() { + setup(); test_treesitter_indent("indent.rs", "source.rust"); // TODO Use commands.rs as indentation test. // Currently this fails because we can't align the parameters of a closure yet diff --git a/helix-loader/src/lib.rs b/helix-loader/src/lib.rs index 794c5e8f7e4b..cb598a8b4c60 100644 --- a/helix-loader/src/lib.rs +++ b/helix-loader/src/lib.rs @@ -1,14 +1,132 @@ pub mod grammar; -pub static RUNTIME_DIR: once_cell::sync::Lazy = - once_cell::sync::Lazy::new(runtime_dir); +use anyhow::{bail, Result}; +use serde::Deserialize; +use std::collections::HashMap; +use std::path::PathBuf; + +static PATHS: once_cell::sync::OnceCell = once_cell::sync::OnceCell::new(); + +#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Eq, Hash)] +#[serde(try_from = "String")] +pub enum Path { + LanguageFile, + LogFile, + GrammarDir, + ThemeDir, + #[serde(skip)] // no point in tutor file being configurable + TutorFile, + QueryDir, +} + +impl TryFrom for Path { + type Error = anyhow::Error; + fn try_from(s: String) -> Result { + match s.as_str() { + "language-file" => Ok(Path::LanguageFile), + "log-file" => Ok(Path::LogFile), + "grammar-dir" => Ok(Path::GrammarDir), + "theme-dir" => Ok(Path::ThemeDir), + "query-dir" => Ok(Path::QueryDir), + _ => bail!("Invalid path key '{}'", s), + } + } +} + +/// Ensure that Paths always contain all required paths +/// by setting whatever isn't specified in config to default values +impl<'de> Deserialize<'de> for Paths { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let mut loaded = HashMap::::deserialize(deserializer)?; + let mut defaults = Self::default(); + for (k, v) in defaults.0.drain() { + if loaded.get(&k).is_none() { + loaded.insert(k, v); + } + } + Ok(Self(loaded)) + } +} + +#[derive(Debug, Clone, PartialEq)] +pub struct Paths(HashMap); + +impl Default for Paths { + fn default() -> Self { + let grammars = runtime_dir().join("grammars"); + let log = cache_dir().join("helix.log"); + let languages = project_dirs().config_dir().join("languages.toml"); + let themes = runtime_dir().join("themes"); + let queries = runtime_dir().join("queries"); + + Self(HashMap::from([ + (Path::GrammarDir, grammars), + (Path::LanguageFile, languages), + (Path::LogFile, log), + (Path::ThemeDir, themes), + (Path::QueryDir, queries), + ])) + } +} + +impl Paths { + pub fn get(&self, path_type: &Path) -> &std::path::Path { + // unwrap: all types always present, either from config or default() + self.0.get(path_type).unwrap() + } +} + +/// Get paths loaded from config, complemented by defaults +fn get_path(path_type: &Path) -> &std::path::Path { + PATHS + .get() + .expect("must init paths from config first") + .get(path_type) +} + +/// Set static PATHS to valid values +/// Should be called before getting any path except config_dir +pub fn init_paths(mut paths: Paths) -> Result<()> { + for (k, v) in &mut paths.0 { + let dir = match k { + Path::LanguageFile | Path::LogFile | Path::TutorFile => v.parent().unwrap(), + Path::GrammarDir | Path::ThemeDir | Path::QueryDir => v, + }; + create_dir(dir)?; + // grammar building can't handle relative paths + // todo: replace canonicalize with std::path::absolute + // then simply call `*v = std::path::absolute(k)`; + // tracking issue: https://github.com/rust-lang/rust/issues/92750 + if k == &Path::LanguageFile || k == &Path::LogFile || k == &Path::TutorFile { + *v = std::fs::canonicalize(dir)?.join(v.file_name().unwrap()); + } else { + *v = std::fs::canonicalize(dir)?; + } + } + PATHS + .set(paths) + .expect("trying to set paths multiple times"); + Ok(()) +} + +fn create_dir(dir: &std::path::Path) -> Result<()> { + if dir.exists() && !dir.is_dir() { + bail!("{} exists but is not a directory!", dir.display()) + } else if !dir.exists() { + std::fs::create_dir_all(&dir)?; + } + Ok(()) +} fn project_dirs() -> directories::ProjectDirs { directories::ProjectDirs::from("com", "helix-editor", "helix") - .expect("Unable to continue. User has no home") + .expect("Unable to continue. User has no home.") } -pub fn runtime_dir() -> std::path::PathBuf { +pub fn runtime_dir() -> PathBuf { if let Ok(dir) = std::env::var("HELIX_RUNTIME") { return dir.into(); } @@ -21,7 +139,7 @@ pub fn runtime_dir() -> std::path::PathBuf { 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 - return std::path::PathBuf::from(dir).parent().unwrap().join(RT_DIR); + return PathBuf::from(dir).parent().unwrap().join(RT_DIR); } // fallback to location of the executable being run @@ -31,43 +149,43 @@ pub fn runtime_dir() -> std::path::PathBuf { .unwrap() } -fn config_dir() -> std::path::PathBuf { +fn config_dir() -> PathBuf { project_dirs().config_dir().to_path_buf() } -fn cache_dir() -> std::path::PathBuf { +fn cache_dir() -> PathBuf { project_dirs().cache_dir().to_path_buf() } -pub fn config_file() -> std::path::PathBuf { +pub fn config_file() -> PathBuf { if let Ok(dir) = std::env::var("HELIX_CONFIG") { - return std::path::PathBuf::from(dir); + return PathBuf::from(dir); } config_dir().join("config.toml") } -pub fn grammar_dir() -> std::path::PathBuf { - config_dir().join("grammars") +pub fn grammar_dir() -> &'static std::path::Path { + get_path(&Path::GrammarDir) } -pub fn lang_config_file() -> std::path::PathBuf { - config_dir().join("languages.toml") +pub fn lang_config_file() -> &'static std::path::Path { + get_path(&Path::LanguageFile) } -pub fn log_file() -> std::path::PathBuf { - cache_dir().join("helix.log") +pub fn log_file() -> &'static std::path::Path { + get_path(&Path::LogFile) } -pub fn theme_dir() -> std::path::PathBuf { - config_dir().join("themes") +pub fn theme_dir() -> &'static std::path::Path { + get_path(&Path::ThemeDir) } -pub fn tutor_file() -> std::path::PathBuf { +pub fn tutor_file() -> PathBuf { config_dir().join("tutor.txt") } -pub fn query_dir() -> std::path::PathBuf { - config_dir().join("queries") +pub fn query_dir() -> &'static std::path::Path { + get_path(&Path::QueryDir) } /// Default bultin-in languages.toml. @@ -173,3 +291,22 @@ mod merge_toml_tests { assert_eq!(nix.get("comment-token").unwrap().as_str().unwrap(), "#"); } } + + +// multiple test mods due to work around shared static +#[cfg(test)] +mod test { + use super::*; + + #[test] + #[should_panic(expected = "trying to set paths multiple times")] + fn calling_init_paths_repeatedly_errors() { + assert!(init_paths(Paths::default()).is_ok()); + let _ = init_paths(Paths::default()); + } + + #[test] + fn config_is_always_available() { + config_file(); + } +} diff --git a/helix-term/Cargo.toml b/helix-term/Cargo.toml index 2e0b774ba6a7..91a4c90ce63b 100644 --- a/helix-term/Cargo.toml +++ b/helix-term/Cargo.toml @@ -74,3 +74,5 @@ signal-hook-tokio = { version = "0.3", features = ["futures-v0_3"] } [build-dependencies] helix-loader = { version = "0.6", path = "../helix-loader" } +serde = { version = "1", default-features = false, features = ["derive"] } +toml = "0.5" diff --git a/helix-term/build.rs b/helix-term/build.rs index 974f4b5ed7f0..fb4aa5b7fca5 100644 --- a/helix-term/build.rs +++ b/helix-term/build.rs @@ -4,6 +4,14 @@ use std::process::Command; const VERSION: &str = include_str!("../VERSION"); +// mirror struct for helix_term::Config +// original can't be used because it's not built yet +use serde::Deserialize; +#[derive(Deserialize)] +struct BuildConfig { + paths: helix_loader::Paths, +} + fn main() { let git_hash = Command::new("git") .args(&["rev-parse", "HEAD"]) @@ -17,12 +25,20 @@ fn main() { None => VERSION.into(), }; + std::env::set_current_dir("..").unwrap(); + let config = std::env::var("HELIX_CONFIG").unwrap(); + let config = std::fs::read_to_string(config).unwrap(); + let paths = toml::from_str::(&config).unwrap().paths; + helix_loader::init_paths(paths).unwrap(); + std::env::set_current_dir("./helix-term").unwrap(); + + let grammar_dir = helix_loader::grammar_dir(); if std::env::var("HELIX_DISABLE_AUTO_GRAMMAR_BUILD").is_err() { fetch_grammars().expect("Failed to fetch tree-sitter grammars"); build_grammars().expect("Failed to compile tree-sitter grammars"); } - println!("cargo:rerun-if-changed=../runtime/grammars/"); + println!("cargo:rerun-if-changed={}", grammar_dir.display()); println!("cargo:rerun-if-changed=../VERSION"); println!("cargo:rustc-env=VERSION_AND_GIT_HASH={}", version); diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 6bce94492f8f..190dbd260f41 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -62,7 +62,7 @@ impl Application { let size = compositor.size(); let theme_loader = std::sync::Arc::new(theme::Loader::new( - &helix_loader::theme_dir(), + helix_loader::theme_dir(), &helix_loader::runtime_dir(), )); diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs index 4407a882f838..3b0ea1ed1180 100644 --- a/helix-term/src/config.rs +++ b/helix-term/src/config.rs @@ -15,6 +15,8 @@ pub struct Config { pub keys: HashMap, #[serde(default)] pub editor: helix_view::editor::Config, + #[serde(default)] + pub paths: helix_loader::Paths, } impl Default for Config { @@ -23,6 +25,7 @@ impl Default for Config { theme: None, keys: default(), editor: helix_view::editor::Config::default(), + paths: helix_loader::Paths::default(), } } } @@ -104,4 +107,57 @@ mod tests { let default_keys = Config::default().keys; assert_eq!(default_keys, default()); } + + #[test] + fn directories_resolve_to_correct_defaults() { + // From serde default + let paths = toml::from_str::("").unwrap().paths; + assert_eq!(paths, helix_loader::Paths::default()); + + // From the Default trait + let paths = Config::default().paths; + assert_eq!(paths, helix_loader::Paths::default()); + } + + #[test] + fn partialy_specified_directories_resolve_correctly() { + use helix_loader::Path; + use std::path::PathBuf; + + const CONFIG: &str = r#" + [paths] + log-file = "../rel/path/log.file" + grammar-dir = "/somewhere/else" + "#; + let defaults = helix_loader::Paths::default(); + let paths = toml::from_str::(CONFIG).unwrap().paths; + + assert_eq!( + paths.get(&Path::LogFile), + PathBuf::from("../rel/path/log.file") + ); + assert_eq!( + paths.get(&Path::GrammarDir), + PathBuf::from("/somewhere/else") + ); + + assert_eq!( + paths.get(&Path::LanguageFile), + defaults.get(&Path::LanguageFile) + ); + assert_eq!(paths.get(&Path::ThemeDir), defaults.get(&Path::ThemeDir)); + assert_eq!(paths.get(&Path::QueryDir), defaults.get(&Path::QueryDir)); + } + + #[test] + fn invalid_path_key_specified() { + const CONFIG: &str = r#" + [paths] + log-dir = "../rel/path/log.file" + grammar-file = "/somewhere/else" + "#; + let paths = toml::from_str::(CONFIG); + + assert!(paths.is_err()) + } } diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs index 78ea2cfd3cd5..08ca0b2f79fc 100644 --- a/helix-term/src/main.rs +++ b/helix-term/src/main.rs @@ -39,12 +39,6 @@ fn main() -> Result<()> { #[tokio::main] async fn main_impl() -> Result { - let logpath = helix_loader::log_file(); - let parent = logpath.parent().unwrap(); - if !parent.exists() { - std::fs::create_dir_all(parent).ok(); - } - let help = format!( "\ {} {} @@ -71,7 +65,9 @@ FLAGS: env!("VERSION_AND_GIT_HASH"), env!("CARGO_PKG_AUTHORS"), env!("CARGO_PKG_DESCRIPTION"), - logpath.display(), + helix_loader::Paths::default() + .get(&helix_loader::Path::LogFile) + .display(), ); let args = Args::parse_args().context("could not parse arguments")?; @@ -87,6 +83,34 @@ FLAGS: std::process::exit(0); } + let config = match Config::load_default() { + Ok(config) => config, + Err(err) => { + match err { + ConfigLoadError::BadConfig(err) => { + eprintln!("Bad config: {}", err); + eprintln!("Press to continue with default config"); + use std::io::Read; + // This waits for an enter press. + let _ = std::io::stdin().read(&mut []); + Config::default() + } + ConfigLoadError::Error(err) if err.kind() == std::io::ErrorKind::NotFound => { + Config::default() + } + ConfigLoadError::Error(err) => return Err(Error::new(err)), + } + } + }; + + if let Err(err) = helix_loader::init_paths(config.paths.clone()) { + eprintln!("Error in [paths] config section: {}", err); + eprintln!("Some features like highlighting or logging may not be available."); + eprintln!("Press to continue anyway"); + use std::io::Read; + let _ = std::io::stdin().read(&mut []); + } + if args.health { if let Err(err) = helix_term::health::print_health(args.health_arg) { // Piping to for example `head -10` requires special handling: @@ -109,33 +133,8 @@ FLAGS: return Ok(0); } - if let Some(conf_dir) = helix_loader::config_file().parent() { - if !conf_dir.exists() { - std::fs::create_dir_all(&conf_dir).ok(); - } - } - - let config = match Config::load_default() { - Ok(config) => config, - Err(err) => { - match err { - ConfigLoadError::BadConfig(err) => { - eprintln!("Bad config: {}", err); - eprintln!("Press to continue with default config"); - use std::io::Read; - // This waits for an enter press. - let _ = std::io::stdin().read(&mut []); - Config::default() - } - ConfigLoadError::Error(err) if err.kind() == std::io::ErrorKind::NotFound => { - Config::default() - } - ConfigLoadError::Error(err) => return Err(Error::new(err)), - } - } - }; - - setup_logging(logpath, args.verbosity).context("failed to initialize logging")?; + setup_logging(helix_loader::log_file().to_path_buf(), args.verbosity) + .context("failed to initialize logging")?; // TODO: use the thread local executor to spawn the application task separately from the work pool let mut app = Application::new(args, config).context("unable to create new application")?; diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index a76ac925cb86..d4f888b02c8a 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -234,8 +234,7 @@ pub mod completers { } pub fn theme(_editor: &Editor, input: &str) -> Vec { - let mut names = theme::Loader::read_names(&helix_loader::theme_dir()); - names.extend(theme::Loader::read_names(&helix_loader::theme_dir())); + let mut names = theme::Loader::read_names(helix_loader::theme_dir()); names.push("default".into()); names.push("base16_default".into()); diff --git a/runtime/config.toml b/runtime/config.toml new file mode 100644 index 000000000000..7d239329ac26 --- /dev/null +++ b/runtime/config.toml @@ -0,0 +1,6 @@ +[paths] +grammar-dir = "./runtime/grammars" +log-file = "./runtime/helix.log" +language-file = "./languages.toml" +query-dir = "./runtime/queries" +theme-dir = "./runtime/themes" From ea4bae005206fd1a5b3233681da8d85e56d736ee Mon Sep 17 00:00:00 2001 From: Martin Matous Date: Wed, 13 Apr 2022 16:52:38 +0200 Subject: [PATCH 5/8] reorganize project directory structure Theme loader is now operating on a single theme dir. It was left as is. It'll be useful when loading from system dirs is supported. Signed-off-by: Martin Matous --- helix-loader/src/lib.rs | 51 ++++++++++------------------------- helix-term/src/application.rs | 3 ++- helix-term/src/health.rs | 16 +++++------ 3 files changed, 24 insertions(+), 46 deletions(-) diff --git a/helix-loader/src/lib.rs b/helix-loader/src/lib.rs index cb598a8b4c60..351cd81809f3 100644 --- a/helix-loader/src/lib.rs +++ b/helix-loader/src/lib.rs @@ -56,11 +56,11 @@ pub struct Paths(HashMap); impl Default for Paths { fn default() -> Self { - let grammars = runtime_dir().join("grammars"); - let log = cache_dir().join("helix.log"); + let grammars = project_dirs().data_dir().join("grammars"); + let log = state_dir().join("helix.log"); let languages = project_dirs().config_dir().join("languages.toml"); - let themes = runtime_dir().join("themes"); - let queries = runtime_dir().join("queries"); + let themes = project_dirs().data_dir().join("themes"); + let queries = project_dirs().data_dir().join("queries"); Self(HashMap::from([ (Path::GrammarDir, grammars), @@ -126,42 +126,11 @@ fn project_dirs() -> directories::ProjectDirs { .expect("Unable to continue. User has no home.") } -pub fn runtime_dir() -> PathBuf { - if let Ok(dir) = std::env::var("HELIX_RUNTIME") { - return dir.into(); - } - - const RT_DIR: &str = "runtime"; - let conf_dir = config_dir().join(RT_DIR); - if conf_dir.exists() { - return conf_dir; - } - - 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 - return PathBuf::from(dir).parent().unwrap().join(RT_DIR); - } - - // fallback to location of the executable being run - std::env::current_exe() - .ok() - .and_then(|path| path.parent().map(|path| path.to_path_buf().join(RT_DIR))) - .unwrap() -} - -fn config_dir() -> PathBuf { - project_dirs().config_dir().to_path_buf() -} - -fn cache_dir() -> PathBuf { - project_dirs().cache_dir().to_path_buf() -} - pub fn config_file() -> PathBuf { if let Ok(dir) = std::env::var("HELIX_CONFIG") { return PathBuf::from(dir); } - config_dir().join("config.toml") + project_dirs().config_dir().join("config.toml") } pub fn grammar_dir() -> &'static std::path::Path { @@ -181,13 +150,21 @@ pub fn theme_dir() -> &'static std::path::Path { } pub fn tutor_file() -> PathBuf { - config_dir().join("tutor.txt") + project_dirs().config_dir().join("tutor.txt") } pub fn query_dir() -> &'static std::path::Path { get_path(&Path::QueryDir) } +fn state_dir() -> PathBuf { + match project_dirs().state_dir() { + Some(dir) => dir.to_path_buf(), + // state_dir is Linux-specific, fallback to data_local + None => project_dirs().data_local_dir().to_path_buf(), + } +} + /// Default bultin-in languages.toml. pub fn default_lang_config() -> toml::Value { toml::from_slice(include_bytes!("../../languages.toml")) diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs index 190dbd260f41..b04d0838345e 100644 --- a/helix-term/src/application.rs +++ b/helix-term/src/application.rs @@ -63,7 +63,8 @@ impl Application { let theme_loader = std::sync::Arc::new(theme::Loader::new( helix_loader::theme_dir(), - &helix_loader::runtime_dir(), + // temporarily identical, until system dirs are supported + helix_loader::theme_dir(), )); let true_color = config.editor.true_color || crate::true_color(); diff --git a/helix-term/src/health.rs b/helix-term/src/health.rs index bd74f4787c2c..9b3e00d093ed 100644 --- a/helix-term/src/health.rs +++ b/helix-term/src/health.rs @@ -48,7 +48,7 @@ pub fn general() -> std::io::Result<()> { let config_file = helix_loader::config_file(); let lang_file = helix_loader::lang_config_file(); let log_file = helix_loader::log_file(); - let rt_dir = helix_loader::runtime_dir(); + let grammar_dir = helix_loader::grammar_dir(); if config_file.exists() { writeln!(stdout, "Config file: {}", config_file.display())?; @@ -61,17 +61,17 @@ pub fn general() -> std::io::Result<()> { writeln!(stdout, "Language file: default")?; } writeln!(stdout, "Log file: {}", log_file.display())?; - writeln!(stdout, "Runtime directory: {}", rt_dir.display())?; + writeln!(stdout, "Grammar directory: {}", grammar_dir.display())?; - if let Ok(path) = std::fs::read_link(&rt_dir) { - let msg = format!("Runtime directory is symlinked to {}", path.display()); + if let Ok(path) = std::fs::read_link(&grammar_dir) { + let msg = format!("Grammar directory is symlinked to {}", path.display()); writeln!(stdout, "{}", msg.yellow())?; } - if !rt_dir.exists() { - writeln!(stdout, "{}", "Runtime directory does not exist.".red())?; + if !grammar_dir.exists() { + writeln!(stdout, "{}", "Grammar directory does not exist.".red())?; } - if rt_dir.read_dir().ok().map(|it| it.count()) == Some(0) { - writeln!(stdout, "{}", "Runtime directory is empty.".red())?; + if grammar_dir.read_dir().ok().map(|it| it.count()) == Some(0) { + writeln!(stdout, "{}", "Grammar directory is empty.".red())?; } Ok(()) From 8f5813a384211dff9e97d2ad78c68ce4484d2c4c Mon Sep 17 00:00:00 2001 From: Martin Matous Date: Wed, 13 Apr 2022 17:14:29 +0200 Subject: [PATCH 6/8] update docs Signed-off-by: Martin Matous --- README.md | 9 ++++----- book/src/configuration.md | 30 +++++++++++++++++++++++++++++- book/src/install.md | 4 ++-- 3 files changed, 35 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6fc98e7287f4..794cca8a225e 100644 --- a/README.md +++ b/README.md @@ -46,14 +46,13 @@ hx --grammar build This will install the `hx` binary to `$HOME/.cargo/bin` and build tree-sitter grammars. Helix also needs its runtime files so make sure to copy/symlink the `runtime/` directory into the -config directory (for example `~/.config/helix/runtime` on Linux/macOS, or `%AppData%/helix/runtime` on Windows). -This location can be overridden via the `HELIX_RUNTIME` environment variable. +data directory (default `$XDG_DATA_HOME/helix` on Linux/macOS, or `%AppData%/helix/` on Windows). +This location can be overridden via appropriate entries in helix config file. Packages already solve this for you by wrapping the `hx` binary with a wrapper that sets the variable to the install dir. -> NOTE: running via cargo also doesn't require setting explicit `HELIX_RUNTIME` path, it will automatically -> detect the `runtime` directory in the project root. +> NOTE: running via cargo also doesn't require setting explicit `HELIX_CONFIG` path, it is set for you automatically by cargo. In order to use LSP features like auto-complete, you will need to [install the appropriate Language Server](https://github.com/helix-editor/helix/wiki/How-to-install-the-default-language-servers) @@ -69,7 +68,7 @@ Helix can be installed on MacOS through homebrew via: brew tap helix-editor/helix brew install helix ``` - + # Contributing Contributing guidelines can be found [here](./docs/CONTRIBUTING.md). diff --git a/book/src/configuration.md b/book/src/configuration.md index 3ec2bedb9033..8e66581d660c 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -2,7 +2,7 @@ To override global configuration parameters, create a `config.toml` file located in your config directory: -* Linux and Mac: `~/.config/helix/config.toml` +* Linux and Mac: `$XDG_CONFIG_HOME/helix/config.toml` * Windows: `%AppData%\helix\config.toml` > Hint: You can easily open the config file by typing `:config-open` within Helix normal mode. @@ -23,6 +23,9 @@ select = "underline" [editor.file-picker] hidden = false + +[paths] +log-file = "/home/user/logs/helix.log" ``` ## Editor @@ -136,3 +139,28 @@ Search specific options. |--|--|---------| | `smart-case` | Enable smart case regex searching (case insensitive unless pattern contains upper case characters) | `true` | | `wrap-around`| Whether the search should wrap after depleting the matches | `true` | + +### `[paths]` Section + +Make Helix use specific files and directories. Generally there's no need +to add/edit this section since Helix will use sensible defaults for given system. + +#### Linux + +| Key | Description | Default | +|--|--|---------| +| `grammar-dir` | Grammars location | `$XDG_DATA_HOME/helix/grammars` | +| `language-file` | [language.toml](./languages.md) location | `$XDG_CONFIG_HOME/helix/languages.toml` | +| `log-file` | Log file location | `$XDG_STATE_HOME/helix/helix.log` | +| `query-dir` | Queries location | `$XDG_DATA_HOME/helix/queries` | +| `theme-dir` | Themes location | `$XDG_CONFIG_HOME/helix/themes` | + +#### Windows + +| Key | Description | Default | +|--|--|---------| +| `grammar-dir` | Grammars location | `%AppData%/helix/grammars` | +| `language-file` | [language.toml](./languages.md) location | `%AppData%/helix/languages.toml` | +| `log-file` | Log file location | `%LocalAppData%/helix/helix.log` | +| `query-dir` | Queries location | `%AppData%/helix/queries` | +| `theme-dir` | Themes location | `%AppData%/helix/themes` | diff --git a/book/src/install.md b/book/src/install.md index 372ce12a12f1..fd3434eb2217 100644 --- a/book/src/install.md +++ b/book/src/install.md @@ -58,8 +58,8 @@ cargo install --path helix-term This will install the `hx` binary to `$HOME/.cargo/bin`. Helix also needs it's runtime files so make sure to copy/symlink the `runtime/` directory into the -config directory (for example `~/.config/helix/runtime` on Linux/macOS). This location can be overriden -via the `HELIX_RUNTIME` environment variable. +data directory (for example `$XDG_DATA_HOME/helix` on Linux/macOS). This location can be overriden +via appropriate entries in helix config file. ## Building tree-sitter grammars From 5683e80f94e1001b9afdfc918a1f8c0cca7b0f8a Mon Sep 17 00:00:00 2001 From: Martin Matous Date: Wed, 11 May 2022 14:27:28 +0200 Subject: [PATCH 7/8] Switch to updated etcetera Signed-off-by: Martin Matous --- Cargo.lock | 35 +++++++++++++---------------------- book/src/configuration.md | 2 +- helix-loader/Cargo.toml | 2 +- helix-loader/src/lib.rs | 17 +++++++++++------ 4 files changed, 26 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f00ed86f1b91..df72f5000f20 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -155,15 +155,6 @@ dependencies = [ "winapi", ] -[[package]] -name = "directories" -version = "4.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f51c5d4ddabd36886dd3e1438cb358cdcb0d7c499cb99cb4ac2e38e18b5cb210" -dependencies = [ - "dirs-sys", -] - [[package]] name = "dirs-next" version = "2.0.0" @@ -174,17 +165,6 @@ dependencies = [ "dirs-sys-next", ] -[[package]] -name = "dirs-sys" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - [[package]] name = "dirs-sys-next" version = "0.1.2" @@ -241,6 +221,17 @@ dependencies = [ "thiserror", ] +[[package]] +name = "etcetera" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d017fce18e4e9bfa75e1db51f49f4487bd3f8a7df509b24a46474a956ee962fd" +dependencies = [ + "cfg-if", + "dirs-next", + "thiserror", +] + [[package]] name = "fern" version = "0.6.0" @@ -381,7 +372,7 @@ dependencies = [ "arc-swap", "chrono", "encoding_rs", - "etcetera", + "etcetera 0.3.2", "helix-loader", "log", "once_cell", @@ -422,7 +413,7 @@ version = "0.6.0" dependencies = [ "anyhow", "cc", - "directories", + "etcetera 0.4.0", "libloading", "once_cell", "serde", diff --git a/book/src/configuration.md b/book/src/configuration.md index 8e66581d660c..92d506087c68 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -161,6 +161,6 @@ to add/edit this section since Helix will use sensible defaults for given system |--|--|---------| | `grammar-dir` | Grammars location | `%AppData%/helix/grammars` | | `language-file` | [language.toml](./languages.md) location | `%AppData%/helix/languages.toml` | -| `log-file` | Log file location | `%LocalAppData%/helix/helix.log` | +| `log-file` | Log file location | `%AppData%/helix/helix.log` | | `query-dir` | Queries location | `%AppData%/helix/queries` | | `theme-dir` | Themes location | `%AppData%/helix/themes` | diff --git a/helix-loader/Cargo.toml b/helix-loader/Cargo.toml index 800a004abbbd..df0568247dc3 100644 --- a/helix-loader/Cargo.toml +++ b/helix-loader/Cargo.toml @@ -11,9 +11,9 @@ homepage = "https://helix-editor.com" [dependencies] anyhow = "1" +etcetera = "0.4" serde = { version = "1.0", features = ["derive"] } toml = "0.5" -directories = "4" tree-sitter = "0.20" libloading = "0.7" once_cell = "1.9" diff --git a/helix-loader/src/lib.rs b/helix-loader/src/lib.rs index 351cd81809f3..c45c0b8973c1 100644 --- a/helix-loader/src/lib.rs +++ b/helix-loader/src/lib.rs @@ -1,6 +1,7 @@ pub mod grammar; use anyhow::{bail, Result}; +use etcetera::app_strategy::{choose_app_strategy, AppStrategy, AppStrategyArgs}; use serde::Deserialize; use std::collections::HashMap; use std::path::PathBuf; @@ -121,9 +122,13 @@ fn create_dir(dir: &std::path::Path) -> Result<()> { Ok(()) } -fn project_dirs() -> directories::ProjectDirs { - directories::ProjectDirs::from("com", "helix-editor", "helix") - .expect("Unable to continue. User has no home.") +fn project_dirs() -> impl AppStrategy { + let args = AppStrategyArgs { + top_level_domain: "com".to_string(), + author: "helix-editor".to_string(), + app_name: "helix".to_string(), + }; + choose_app_strategy(args).expect("Unable to continue. User environment not sane.") } pub fn config_file() -> PathBuf { @@ -159,9 +164,9 @@ pub fn query_dir() -> &'static std::path::Path { fn state_dir() -> PathBuf { match project_dirs().state_dir() { - Some(dir) => dir.to_path_buf(), - // state_dir is Linux-specific, fallback to data_local - None => project_dirs().data_local_dir().to_path_buf(), + Some(dir) => dir, + // state_dir is Linux-specific, fallback to data + None => project_dirs().data_dir(), } } From 020182f0a178f6f0f4ef733b92afa78eab9bc566 Mon Sep 17 00:00:00 2001 From: Martin Matous Date: Wed, 11 May 2022 14:28:40 +0200 Subject: [PATCH 8/8] Fix docs/impl disparity Signed-off-by: Martin Matous --- book/src/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/book/src/configuration.md b/book/src/configuration.md index 92d506087c68..832b32584a35 100644 --- a/book/src/configuration.md +++ b/book/src/configuration.md @@ -153,7 +153,7 @@ to add/edit this section since Helix will use sensible defaults for given system | `language-file` | [language.toml](./languages.md) location | `$XDG_CONFIG_HOME/helix/languages.toml` | | `log-file` | Log file location | `$XDG_STATE_HOME/helix/helix.log` | | `query-dir` | Queries location | `$XDG_DATA_HOME/helix/queries` | -| `theme-dir` | Themes location | `$XDG_CONFIG_HOME/helix/themes` | +| `theme-dir` | Themes location | `$XDG_DATA_HOME/helix/themes` | #### Windows