summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKirawi2022-04-18 03:10:51 +0000
committerGitHub2022-04-18 03:10:51 +0000
commitc2a40d9d5229c701fa1a6d0fb80ce4ba86e8dc0c (patch)
treeb27da29863432ca7ee48c582f5aeb538d7a39ea1
parentbe656c14e32243fc32ed68f9a3240301f2902df7 (diff)
Add support for local language configuration (#1249)
* add local configuration * move config loading to Application::new * simplify find_root_impl
-rw-r--r--Cargo.lock1
-rw-r--r--book/src/languages.md2
-rw-r--r--helix-core/src/config.rs4
-rw-r--r--helix-core/src/lib.rs38
-rw-r--r--helix-loader/Cargo.toml2
-rw-r--r--helix-loader/src/config.rs26
-rw-r--r--helix-loader/src/grammar.rs2
-rw-r--r--helix-loader/src/lib.rs53
-rw-r--r--helix-term/src/application.rs33
-rw-r--r--helix-term/src/main.rs30
10 files changed, 102 insertions, 89 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 4bbd4c25..61346d45 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -404,6 +404,7 @@ dependencies = [
"cc",
"etcetera",
"libloading",
+ "log",
"once_cell",
"serde",
"threadpool",
diff --git a/book/src/languages.md b/book/src/languages.md
index 3372a120..1fa24787 100644
--- a/book/src/languages.md
+++ b/book/src/languages.md
@@ -2,6 +2,8 @@
Language-specific settings and settings for particular language servers can be configured in a `languages.toml` file placed in your [configuration directory](./configuration.md). Helix actually uses two `languages.toml` files, the [first one](https://github.com/helix-editor/helix/blob/master/languages.toml) is in the main helix repository; it contains the default settings for each language and is included in the helix binary at compile time. Users who want to see the available settings and options can either reference the helix repo's `languages.toml` file, or consult the table in the [adding languages](./guides/adding_languages.md) section.
+A local `languages.toml` can be created within a `.helix` directory. Its settings will be merged with both the global and default configs.
+
Changes made to the `languages.toml` file in a user's [configuration directory](./configuration.md) are merged with helix's defaults on start-up, such that a user's settings will take precedence over defaults in the event of a collision. For example, the default `languages.toml` sets rust's `auto-format` to `true`. If a user wants to disable auto-format, they can change the `languages.toml` in their [configuration directory](./configuration.md) to make the rust entry read like the example below; the new key/value pair `auto-format = false` will override the default when the two sets of settings are merged on start-up:
```toml
diff --git a/helix-core/src/config.rs b/helix-core/src/config.rs
index f399850e..2076fc22 100644
--- a/helix-core/src/config.rs
+++ b/helix-core/src/config.rs
@@ -1,10 +1,10 @@
/// Syntax configuration loader based on built-in languages.toml.
pub fn default_syntax_loader() -> crate::syntax::Configuration {
- helix_loader::default_lang_config()
+ helix_loader::config::default_lang_config()
.try_into()
.expect("Could not serialize built-in languages.toml")
}
/// Syntax configuration loader based on user configured languages.toml.
pub fn user_syntax_loader() -> Result<crate::syntax::Configuration, toml::de::Error> {
- helix_loader::user_lang_config()?.try_into()
+ helix_loader::config::user_lang_config()?.try_into()
}
diff --git a/helix-core/src/lib.rs b/helix-core/src/lib.rs
index 0ae68f91..02341265 100644
--- a/helix-core/src/lib.rs
+++ b/helix-core/src/lib.rs
@@ -46,41 +46,9 @@ pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
/// * Top-most folder containing a root marker if not git repository detected
/// * Current working directory as fallback
pub fn find_root(root: Option<&str>, root_markers: &[String]) -> Option<std::path::PathBuf> {
- let current_dir = std::env::current_dir().expect("unable to determine current directory");
-
- let root = match root {
- Some(root) => {
- let root = std::path::Path::new(root);
- if root.is_absolute() {
- root.to_path_buf()
- } else {
- current_dir.join(root)
- }
- }
- None => current_dir.clone(),
- };
-
- let mut top_marker = None;
- for ancestor in root.ancestors() {
- for marker in root_markers {
- if ancestor.join(marker).exists() {
- top_marker = Some(ancestor);
- break;
- }
- }
- // don't go higher than repo
- if ancestor.join(".git").is_dir() {
- // Use workspace if detected from marker
- return Some(top_marker.unwrap_or(ancestor).to_path_buf());
- }
- }
-
- // In absence of git repo, use workspace if detected
- if top_marker.is_some() {
- top_marker.map(|a| a.to_path_buf())
- } else {
- Some(current_dir)
- }
+ helix_loader::find_root_impl(root, root_markers)
+ .first()
+ .cloned()
}
pub use ropey::{Rope, RopeBuilder, RopeSlice};
diff --git a/helix-loader/Cargo.toml b/helix-loader/Cargo.toml
index 21b37333..607d1a0a 100644
--- a/helix-loader/Cargo.toml
+++ b/helix-loader/Cargo.toml
@@ -18,6 +18,8 @@ tree-sitter = "0.20"
libloading = "0.7"
once_cell = "1.9"
+log = "0.4"
+
# cloning/compiling tree-sitter grammars
cc = { version = "1" }
threadpool = { version = "1.0" }
diff --git a/helix-loader/src/config.rs b/helix-loader/src/config.rs
new file mode 100644
index 00000000..3d71baed
--- /dev/null
+++ b/helix-loader/src/config.rs
@@ -0,0 +1,26 @@
+/// Default bultin-in languages.toml.
+pub fn default_lang_config() -> toml::Value {
+ toml::from_slice(include_bytes!("../../languages.toml"))
+ .expect("Could not parse bultin-in languages.toml to valid toml")
+}
+
+/// User configured languages.toml file, merged with the default config.
+pub fn user_lang_config() -> Result<toml::Value, toml::de::Error> {
+ let config = crate::local_config_dirs()
+ .into_iter()
+ .chain([crate::config_dir()].into_iter())
+ .map(|path| path.join("languages.toml"))
+ .filter_map(|file| {
+ std::fs::read(&file)
+ .map(|config| toml::from_slice(&config))
+ .ok()
+ })
+ .collect::<Result<Vec<_>, _>>()?
+ .into_iter()
+ .chain([default_lang_config()].into_iter())
+ .fold(toml::Value::Table(toml::value::Table::default()), |a, b| {
+ crate::merge_toml_values(b, a)
+ });
+
+ Ok(config)
+}
diff --git a/helix-loader/src/grammar.rs b/helix-loader/src/grammar.rs
index 7474713a..9d63a2be 100644
--- a/helix-loader/src/grammar.rs
+++ b/helix-loader/src/grammar.rs
@@ -92,7 +92,7 @@ pub fn build_grammars() -> Result<()> {
// merged. The `grammar_selection` key of the config is then used to filter
// down all grammars into a subset of the user's choosing.
fn get_grammar_configs() -> Result<Vec<GrammarConfiguration>> {
- let config: Configuration = crate::user_lang_config()
+ let config: Configuration = crate::config::user_lang_config()
.context("Could not parse languages.toml")?
.try_into()?;
diff --git a/helix-loader/src/lib.rs b/helix-loader/src/lib.rs
index a2c4d96f..de2951f8 100644
--- a/helix-loader/src/lib.rs
+++ b/helix-loader/src/lib.rs
@@ -1,3 +1,4 @@
+pub mod config;
pub mod grammar;
use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
@@ -36,6 +37,15 @@ pub fn config_dir() -> std::path::PathBuf {
path
}
+pub fn local_config_dirs() -> Vec<std::path::PathBuf> {
+ let directories = find_root_impl(None, &[".helix".to_string()])
+ .into_iter()
+ .map(|path| path.join(".helix"))
+ .collect();
+ log::debug!("Located configuration folders: {:?}", directories);
+ directories
+}
+
pub fn cache_dir() -> std::path::PathBuf {
// TODO: allow env var override
let strategy = choose_base_strategy().expect("Unable to find the config directory!");
@@ -56,25 +66,36 @@ pub fn log_file() -> std::path::PathBuf {
cache_dir().join("helix.log")
}
-/// Default bultin-in languages.toml.
-pub fn default_lang_config() -> toml::Value {
- toml::from_slice(include_bytes!("../../languages.toml"))
- .expect("Could not parse bultin-in languages.toml to valid toml")
-}
-
-/// User configured languages.toml file, merged with the default config.
-pub fn user_lang_config() -> Result<toml::Value, toml::de::Error> {
- let def_lang_conf = default_lang_config();
- let data = std::fs::read(crate::config_dir().join("languages.toml"));
- let user_lang_conf = match data {
- Ok(raw) => {
- let value = toml::from_slice(&raw)?;
- merge_toml_values(def_lang_conf, value)
+pub fn find_root_impl(root: Option<&str>, root_markers: &[String]) -> Vec<std::path::PathBuf> {
+ let current_dir = std::env::current_dir().expect("unable to determine current directory");
+ let mut directories = Vec::new();
+
+ let root = match root {
+ Some(root) => {
+ let root = std::path::Path::new(root);
+ if root.is_absolute() {
+ root.to_path_buf()
+ } else {
+ current_dir.join(root)
+ }
}
- Err(_) => def_lang_conf,
+ None => current_dir,
};
- Ok(user_lang_conf)
+ for ancestor in root.ancestors() {
+ // don't go higher than repo
+ if ancestor.join(".git").is_dir() {
+ // Use workspace if detected from marker
+ directories.push(ancestor.to_path_buf());
+ break;
+ } else if root_markers
+ .iter()
+ .any(|marker| ancestor.join(marker).exists())
+ {
+ directories.push(ancestor.to_path_buf());
+ }
+ }
+ directories
}
// right overrides left
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index bc5f3bd7..7733c2c6 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -56,15 +56,33 @@ pub struct Application {
}
impl Application {
- pub fn new(args: Args, config: Config) -> Result<Self, Error> {
+ pub fn new(args: Args) -> Result<Self, Error> {
use helix_view::editor::Action;
- let mut compositor = Compositor::new()?;
- let size = compositor.size();
- let conf_dir = helix_loader::config_dir();
+ let config_dir = helix_loader::config_dir();
+ if !config_dir.exists() {
+ std::fs::create_dir_all(&config_dir).ok();
+ }
- let theme_loader =
- std::sync::Arc::new(theme::Loader::new(&conf_dir, &helix_loader::runtime_dir()));
+ let config = match std::fs::read_to_string(config_dir.join("config.toml")) {
+ Ok(config) => toml::from_str(&config)
+ .map(crate::keymap::merge_keys)
+ .unwrap_or_else(|err| {
+ eprintln!("Bad config: {}", err);
+ eprintln!("Press <ENTER> to continue with default config");
+ use std::io::Read;
+ // This waits for an enter press.
+ let _ = std::io::stdin().read(&mut []);
+ Config::default()
+ }),
+ Err(err) if err.kind() == std::io::ErrorKind::NotFound => Config::default(),
+ Err(err) => return Err(Error::new(err)),
+ };
+
+ let theme_loader = std::sync::Arc::new(theme::Loader::new(
+ &config_dir,
+ &helix_loader::runtime_dir(),
+ ));
let true_color = config.editor.true_color || crate::true_color();
let theme = config
@@ -98,9 +116,10 @@ impl Application {
});
let syn_loader = std::sync::Arc::new(syntax::Loader::new(syn_loader_conf));
+ let mut compositor = Compositor::new()?;
let config = Arc::new(ArcSwap::from_pointee(config));
let mut editor = Editor::new(
- size,
+ compositor.size(),
theme_loader.clone(),
syn_loader.clone(),
Box::new(Map::new(Arc::clone(&config), |config: &Config| {
diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs
index 4a3434d1..58a90131 100644
--- a/helix-term/src/main.rs
+++ b/helix-term/src/main.rs
@@ -1,7 +1,6 @@
-use anyhow::{Context, Error, Result};
+use anyhow::{Context, Result};
use helix_term::application::Application;
use helix_term::args::Args;
-use helix_term::config::{Config, ConfigLoadError};
use std::path::PathBuf;
fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> {
@@ -109,35 +108,10 @@ FLAGS:
return Ok(0);
}
- let conf_dir = helix_loader::config_dir();
- 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 <ENTER> 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")?;
// 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")?;
+ let mut app = Application::new(args).context("unable to create new application")?;
let exit_code = app.run().await?;