Skip to content

Commit c2a40d9

Browse files
authored
Add support for local language configuration (#1249)
* add local configuration * move config loading to Application::new * simplify find_root_impl
1 parent be656c1 commit c2a40d9

File tree

10 files changed

+102
-89
lines changed

10 files changed

+102
-89
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

book/src/languages.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
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.
44

5+
A local `languages.toml` can be created within a `.helix` directory. Its settings will be merged with both the global and default configs.
6+
57
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:
68

79
```toml

helix-core/src/config.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
/// Syntax configuration loader based on built-in languages.toml.
22
pub fn default_syntax_loader() -> crate::syntax::Configuration {
3-
helix_loader::default_lang_config()
3+
helix_loader::config::default_lang_config()
44
.try_into()
55
.expect("Could not serialize built-in languages.toml")
66
}
77
/// Syntax configuration loader based on user configured languages.toml.
88
pub fn user_syntax_loader() -> Result<crate::syntax::Configuration, toml::de::Error> {
9-
helix_loader::user_lang_config()?.try_into()
9+
helix_loader::config::user_lang_config()?.try_into()
1010
}

helix-core/src/lib.rs

Lines changed: 3 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -46,41 +46,9 @@ pub fn find_first_non_whitespace_char(line: RopeSlice) -> Option<usize> {
4646
/// * Top-most folder containing a root marker if not git repository detected
4747
/// * Current working directory as fallback
4848
pub fn find_root(root: Option<&str>, root_markers: &[String]) -> Option<std::path::PathBuf> {
49-
let current_dir = std::env::current_dir().expect("unable to determine current directory");
50-
51-
let root = match root {
52-
Some(root) => {
53-
let root = std::path::Path::new(root);
54-
if root.is_absolute() {
55-
root.to_path_buf()
56-
} else {
57-
current_dir.join(root)
58-
}
59-
}
60-
None => current_dir.clone(),
61-
};
62-
63-
let mut top_marker = None;
64-
for ancestor in root.ancestors() {
65-
for marker in root_markers {
66-
if ancestor.join(marker).exists() {
67-
top_marker = Some(ancestor);
68-
break;
69-
}
70-
}
71-
// don't go higher than repo
72-
if ancestor.join(".git").is_dir() {
73-
// Use workspace if detected from marker
74-
return Some(top_marker.unwrap_or(ancestor).to_path_buf());
75-
}
76-
}
77-
78-
// In absence of git repo, use workspace if detected
79-
if top_marker.is_some() {
80-
top_marker.map(|a| a.to_path_buf())
81-
} else {
82-
Some(current_dir)
83-
}
49+
helix_loader::find_root_impl(root, root_markers)
50+
.first()
51+
.cloned()
8452
}
8553

8654
pub use ropey::{Rope, RopeBuilder, RopeSlice};

helix-loader/Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ tree-sitter = "0.20"
1818
libloading = "0.7"
1919
once_cell = "1.9"
2020

21+
log = "0.4"
22+
2123
# cloning/compiling tree-sitter grammars
2224
cc = { version = "1" }
2325
threadpool = { version = "1.0" }

helix-loader/src/config.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/// Default bultin-in languages.toml.
2+
pub fn default_lang_config() -> toml::Value {
3+
toml::from_slice(include_bytes!("../../languages.toml"))
4+
.expect("Could not parse bultin-in languages.toml to valid toml")
5+
}
6+
7+
/// User configured languages.toml file, merged with the default config.
8+
pub fn user_lang_config() -> Result<toml::Value, toml::de::Error> {
9+
let config = crate::local_config_dirs()
10+
.into_iter()
11+
.chain([crate::config_dir()].into_iter())
12+
.map(|path| path.join("languages.toml"))
13+
.filter_map(|file| {
14+
std::fs::read(&file)
15+
.map(|config| toml::from_slice(&config))
16+
.ok()
17+
})
18+
.collect::<Result<Vec<_>, _>>()?
19+
.into_iter()
20+
.chain([default_lang_config()].into_iter())
21+
.fold(toml::Value::Table(toml::value::Table::default()), |a, b| {
22+
crate::merge_toml_values(b, a)
23+
});
24+
25+
Ok(config)
26+
}

helix-loader/src/grammar.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ pub fn build_grammars() -> Result<()> {
9292
// merged. The `grammar_selection` key of the config is then used to filter
9393
// down all grammars into a subset of the user's choosing.
9494
fn get_grammar_configs() -> Result<Vec<GrammarConfiguration>> {
95-
let config: Configuration = crate::user_lang_config()
95+
let config: Configuration = crate::config::user_lang_config()
9696
.context("Could not parse languages.toml")?
9797
.try_into()?;
9898

helix-loader/src/lib.rs

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
pub mod config;
12
pub mod grammar;
23

34
use etcetera::base_strategy::{choose_base_strategy, BaseStrategy};
@@ -36,6 +37,15 @@ pub fn config_dir() -> std::path::PathBuf {
3637
path
3738
}
3839

40+
pub fn local_config_dirs() -> Vec<std::path::PathBuf> {
41+
let directories = find_root_impl(None, &[".helix".to_string()])
42+
.into_iter()
43+
.map(|path| path.join(".helix"))
44+
.collect();
45+
log::debug!("Located configuration folders: {:?}", directories);
46+
directories
47+
}
48+
3949
pub fn cache_dir() -> std::path::PathBuf {
4050
// TODO: allow env var override
4151
let strategy = choose_base_strategy().expect("Unable to find the config directory!");
@@ -56,25 +66,36 @@ pub fn log_file() -> std::path::PathBuf {
5666
cache_dir().join("helix.log")
5767
}
5868

59-
/// Default bultin-in languages.toml.
60-
pub fn default_lang_config() -> toml::Value {
61-
toml::from_slice(include_bytes!("../../languages.toml"))
62-
.expect("Could not parse bultin-in languages.toml to valid toml")
63-
}
64-
65-
/// User configured languages.toml file, merged with the default config.
66-
pub fn user_lang_config() -> Result<toml::Value, toml::de::Error> {
67-
let def_lang_conf = default_lang_config();
68-
let data = std::fs::read(crate::config_dir().join("languages.toml"));
69-
let user_lang_conf = match data {
70-
Ok(raw) => {
71-
let value = toml::from_slice(&raw)?;
72-
merge_toml_values(def_lang_conf, value)
69+
pub fn find_root_impl(root: Option<&str>, root_markers: &[String]) -> Vec<std::path::PathBuf> {
70+
let current_dir = std::env::current_dir().expect("unable to determine current directory");
71+
let mut directories = Vec::new();
72+
73+
let root = match root {
74+
Some(root) => {
75+
let root = std::path::Path::new(root);
76+
if root.is_absolute() {
77+
root.to_path_buf()
78+
} else {
79+
current_dir.join(root)
80+
}
7381
}
74-
Err(_) => def_lang_conf,
82+
None => current_dir,
7583
};
7684

77-
Ok(user_lang_conf)
85+
for ancestor in root.ancestors() {
86+
// don't go higher than repo
87+
if ancestor.join(".git").is_dir() {
88+
// Use workspace if detected from marker
89+
directories.push(ancestor.to_path_buf());
90+
break;
91+
} else if root_markers
92+
.iter()
93+
.any(|marker| ancestor.join(marker).exists())
94+
{
95+
directories.push(ancestor.to_path_buf());
96+
}
97+
}
98+
directories
7899
}
79100

80101
// right overrides left

helix-term/src/application.rs

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,33 @@ pub struct Application {
5656
}
5757

5858
impl Application {
59-
pub fn new(args: Args, config: Config) -> Result<Self, Error> {
59+
pub fn new(args: Args) -> Result<Self, Error> {
6060
use helix_view::editor::Action;
61-
let mut compositor = Compositor::new()?;
62-
let size = compositor.size();
6361

64-
let conf_dir = helix_loader::config_dir();
62+
let config_dir = helix_loader::config_dir();
63+
if !config_dir.exists() {
64+
std::fs::create_dir_all(&config_dir).ok();
65+
}
6566

66-
let theme_loader =
67-
std::sync::Arc::new(theme::Loader::new(&conf_dir, &helix_loader::runtime_dir()));
67+
let config = match std::fs::read_to_string(config_dir.join("config.toml")) {
68+
Ok(config) => toml::from_str(&config)
69+
.map(crate::keymap::merge_keys)
70+
.unwrap_or_else(|err| {
71+
eprintln!("Bad config: {}", err);
72+
eprintln!("Press <ENTER> to continue with default config");
73+
use std::io::Read;
74+
// This waits for an enter press.
75+
let _ = std::io::stdin().read(&mut []);
76+
Config::default()
77+
}),
78+
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Config::default(),
79+
Err(err) => return Err(Error::new(err)),
80+
};
81+
82+
let theme_loader = std::sync::Arc::new(theme::Loader::new(
83+
&config_dir,
84+
&helix_loader::runtime_dir(),
85+
));
6886

6987
let true_color = config.editor.true_color || crate::true_color();
7088
let theme = config
@@ -98,9 +116,10 @@ impl Application {
98116
});
99117
let syn_loader = std::sync::Arc::new(syntax::Loader::new(syn_loader_conf));
100118

119+
let mut compositor = Compositor::new()?;
101120
let config = Arc::new(ArcSwap::from_pointee(config));
102121
let mut editor = Editor::new(
103-
size,
122+
compositor.size(),
104123
theme_loader.clone(),
105124
syn_loader.clone(),
106125
Box::new(Map::new(Arc::clone(&config), |config: &Config| {

helix-term/src/main.rs

Lines changed: 2 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
1-
use anyhow::{Context, Error, Result};
1+
use anyhow::{Context, Result};
22
use helix_term::application::Application;
33
use helix_term::args::Args;
4-
use helix_term::config::{Config, ConfigLoadError};
54
use std::path::PathBuf;
65

76
fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> {
@@ -109,35 +108,10 @@ FLAGS:
109108
return Ok(0);
110109
}
111110

112-
let conf_dir = helix_loader::config_dir();
113-
if !conf_dir.exists() {
114-
std::fs::create_dir_all(&conf_dir).ok();
115-
}
116-
117-
let config = match Config::load_default() {
118-
Ok(config) => config,
119-
Err(err) => {
120-
match err {
121-
ConfigLoadError::BadConfig(err) => {
122-
eprintln!("Bad config: {}", err);
123-
eprintln!("Press <ENTER> to continue with default config");
124-
use std::io::Read;
125-
// This waits for an enter press.
126-
let _ = std::io::stdin().read(&mut []);
127-
Config::default()
128-
}
129-
ConfigLoadError::Error(err) if err.kind() == std::io::ErrorKind::NotFound => {
130-
Config::default()
131-
}
132-
ConfigLoadError::Error(err) => return Err(Error::new(err)),
133-
}
134-
}
135-
};
136-
137111
setup_logging(logpath, args.verbosity).context("failed to initialize logging")?;
138112

139113
// TODO: use the thread local executor to spawn the application task separately from the work pool
140-
let mut app = Application::new(args, config).context("unable to create new application")?;
114+
let mut app = Application::new(args).context("unable to create new application")?;
141115

142116
let exit_code = app.run().await?;
143117

0 commit comments

Comments
 (0)