Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion book/src/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Example config:

```toml
theme = "onedark"
load-workspace-config = "always"

[editor]
line-number = "relative"
Expand All @@ -30,6 +31,6 @@ You can use a custom configuration file by specifying it with the `-c` or
You can reload the config file by issuing the `:config-reload` command. Alternatively, on Unix operating systems, you can reload it by sending the USR1
signal to the Helix process, such as by using the command `pkill -USR1 hx`.

Finally, you can have a `config.toml` local to a project by putting it under a `.helix` directory in your repository.
Finally, if you specify `load-workspace-config = "always"` in your global config file, you can have a `config.toml` local to a project by putting it under a `.helix` directory in your repository.
Its settings will be merged with the configuration directory `config.toml` and the built-in configuration.

3 changes: 2 additions & 1 deletion book/src/languages.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ There are three possible locations for a `languages.toml` file:
auto-format = false
```

3. In a `.helix` folder in your project. Language configuration may also be
3. If `load-workspace-config = "always"` in your global `config.toml` file,
it can also be in a `.helix` folder in your project. Language configuration may also be
overridden local to a project by creating a `languages.toml` file in a
`.helix` folder. Its settings will be merged with the language configuration
in the configuration directory and the built-in configuration.
Expand Down
8 changes: 4 additions & 4 deletions helix-core/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ impl std::fmt::Display for LanguageLoaderError {
impl std::error::Error for LanguageLoaderError {}

/// Language configuration based on user configured languages.toml.
pub fn user_lang_config() -> Result<Configuration, toml::de::Error> {
helix_loader::config::user_lang_config()?.try_into()
pub fn user_lang_config(use_local: bool) -> Result<Configuration, toml::de::Error> {
helix_loader::config::user_lang_config(use_local)?.try_into()
}

/// Language configuration loader based on user configured languages.toml.
pub fn user_lang_loader() -> Result<Loader, LanguageLoaderError> {
let config: Configuration = helix_loader::config::user_lang_config()
pub fn user_lang_loader(use_local: bool) -> Result<Loader, LanguageLoaderError> {
let config: Configuration = helix_loader::config::user_lang_config(use_local)
.map_err(LanguageLoaderError::DeserializeError)?
.try_into()
.map_err(LanguageLoaderError::DeserializeError)?;
Expand Down
36 changes: 19 additions & 17 deletions helix-loader/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,25 @@ pub fn default_lang_config() -> toml::Value {
}

/// User configured languages.toml file, merged with the default config.
pub fn user_lang_config() -> Result<toml::Value, toml::de::Error> {
let config = [
crate::config_dir(),
crate::find_workspace().0.join(".helix"),
]
.into_iter()
.map(|path| path.join("languages.toml"))
.filter_map(|file| {
std::fs::read_to_string(file)
.map(|config| toml::from_str(&config))
.ok()
})
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.fold(default_lang_config(), |a, b| {
crate::merge_toml_values(a, b, 3)
});
pub fn user_lang_config(use_local: bool) -> Result<toml::Value, toml::de::Error> {
let mut dirs = vec![crate::config_dir()];
if use_local {
dirs.push(crate::find_workspace().0.join(".helix"));
}

let config = dirs
.into_iter()
.map(|path| path.join("languages.toml"))
.filter_map(|file| {
std::fs::read_to_string(file)
.map(|config| toml::from_str(&config))
.ok()
})
.collect::<Result<Vec<_>, _>>()?
.into_iter()
.fold(default_lang_config(), |a, b| {
crate::merge_toml_values(a, b, 3)
});

Ok(config)
}
16 changes: 8 additions & 8 deletions helix-loader/src/grammar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,11 +83,11 @@ fn ensure_git_is_available() -> Result<()> {
Ok(())
}

pub fn fetch_grammars() -> Result<()> {
pub fn fetch_grammars(use_local_config: bool) -> Result<()> {
ensure_git_is_available()?;

// We do not need to fetch local grammars.
let mut grammars = get_grammar_configs()?;
let mut grammars = get_grammar_configs(use_local_config)?;
grammars.retain(|grammar| !matches!(grammar.source, GrammarSource::Local { .. }));

println!("Fetching {} grammars", grammars.len());
Expand Down Expand Up @@ -144,10 +144,10 @@ pub fn fetch_grammars() -> Result<()> {
Ok(())
}

pub fn build_grammars(target: Option<String>) -> Result<()> {
pub fn build_grammars(target: Option<String>, use_local_config: bool) -> Result<()> {
ensure_git_is_available()?;

let grammars = get_grammar_configs()?;
let grammars = get_grammar_configs(use_local_config)?;
println!("Building {} grammars", grammars.len());
let results = run_parallel(grammars, move |grammar| {
build_grammar(grammar, target.as_deref())
Expand Down Expand Up @@ -191,8 +191,8 @@ pub fn build_grammars(target: Option<String>) -> Result<()> {
// Grammars are configured in the default and user `languages.toml` and are
// 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::config::user_lang_config()
fn get_grammar_configs(use_local_config: bool) -> Result<Vec<GrammarConfiguration>> {
let config: Configuration = crate::config::user_lang_config(use_local_config)
.context("Could not parse languages.toml")?
.try_into()?;

Expand All @@ -213,8 +213,8 @@ fn get_grammar_configs() -> Result<Vec<GrammarConfiguration>> {
Ok(grammars)
}

pub fn get_grammar_names() -> Result<Option<HashSet<String>>> {
let config: Configuration = crate::config::user_lang_config()
pub fn get_grammar_names(use_local_config: bool) -> Result<Option<HashSet<String>>> {
let config: Configuration = crate::config::user_lang_config(use_local_config)
.context("Could not parse languages.toml")?
.try_into()?;

Expand Down
2 changes: 1 addition & 1 deletion helix-loader/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ use helix_loader::grammar::fetch_grammars;
// compilation time. This is not meant to be run manually.

fn main() -> Result<()> {
fetch_grammars()
fetch_grammars(true)
}
4 changes: 2 additions & 2 deletions helix-term/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use helix_loader::grammar::{build_grammars, fetch_grammars};

fn main() {
if std::env::var("HELIX_DISABLE_AUTO_GRAMMAR_BUILD").is_err() {
fetch_grammars().expect("Failed to fetch tree-sitter grammars");
build_grammars(Some(std::env::var("TARGET").unwrap()))
fetch_grammars(true).expect("Failed to fetch tree-sitter grammars");
build_grammars(Some(std::env::var("TARGET").unwrap()), true)
.expect("Failed to compile tree-sitter grammars");
}

Expand Down
6 changes: 4 additions & 2 deletions helix-term/src/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use tui::backend::Backend;
use crate::{
args::Args,
compositor::{Compositor, Event},
config::Config,
config::{Config, LoadWorkspaceConfig},
handlers,
job::Jobs,
keymap::Keymaps,
Expand Down Expand Up @@ -415,7 +415,9 @@ impl Application {
// Update the syntax language loader before setting the theme. Setting the theme will
// call `Loader::set_scopes` which must be done before the documents are re-parsed for
// the sake of locals highlighting.
let lang_loader = helix_core::config::user_lang_loader()?;
let lang_loader = helix_core::config::user_lang_loader(
self.config.load().load_workspace_config == LoadWorkspaceConfig::Always,
)?;
self.editor.syn_loader.store(Arc::new(lang_loader));
Self::load_configured_theme(
&mut self.editor,
Expand Down
52 changes: 40 additions & 12 deletions helix-term/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,29 @@ use std::fs;
use std::io::Error as IOError;
use toml::de::Error as TomlError;

#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum LoadWorkspaceConfig {
Always,
#[default]
Never,
}

#[derive(Debug, Clone, PartialEq)]
pub struct Config {
pub theme: Option<theme::Config>,
pub keys: HashMap<Mode, KeyTrie>,
pub editor: helix_view::editor::Config,
pub load_workspace_config: LoadWorkspaceConfig,
}

#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(deny_unknown_fields)]
#[serde(rename_all = "kebab-case", deny_unknown_fields)]
pub struct ConfigRaw {
pub theme: Option<theme::Config>,
pub keys: Option<HashMap<Mode, KeyTrie>>,
pub editor: Option<toml::Value>,
pub load_workspace_config: Option<LoadWorkspaceConfig>,
}

impl Default for Config {
Expand All @@ -30,6 +40,7 @@ impl Default for Config {
theme: None,
keys: keymap::default(),
editor: helix_view::editor::Config::default(),
load_workspace_config: LoadWorkspaceConfig::default(),
}
}
}
Expand Down Expand Up @@ -66,36 +77,52 @@ impl Config {
local.and_then(|file| toml::from_str(&file).map_err(ConfigLoadError::BadConfig));
let res = match (global_config, local_config) {
(Ok(global), Ok(local)) => {
let use_local = global.load_workspace_config == Some(LoadWorkspaceConfig::Always);
let mut keys = keymap::default();
if let Some(global_keys) = global.keys {
merge_keys(&mut keys, global_keys)
}
if let Some(local_keys) = local.keys {
merge_keys(&mut keys, local_keys)
if use_local {
if let Some(local_keys) = local.keys {
merge_keys(&mut keys, local_keys)
}
}

let editor = match (global.editor, local.editor) {
(None, None) => helix_view::editor::Config::default(),
(None, Some(val)) | (Some(val), None) => {
(None, Some(val)) if use_local => {
val.try_into().map_err(ConfigLoadError::BadConfig)?
}
(Some(global), Some(local)) => merge_toml_values(global, local, 3)
.try_into()
.map_err(ConfigLoadError::BadConfig)?,
(None, None) | (None, Some(_)) => helix_view::editor::Config::default(),

(Some(val), None) => val.try_into().map_err(ConfigLoadError::BadConfig)?,
(Some(global), Some(local)) => {
if use_local {
merge_toml_values(global, local, 3)
.try_into()
.map_err(ConfigLoadError::BadConfig)?
} else {
global.try_into().map_err(ConfigLoadError::BadConfig)?
}
}
};

Config {
theme: local.theme.or(global.theme),
theme: if use_local {
local.theme.or(global.theme)
} else {
global.theme
},
keys,
editor,
load_workspace_config: global.load_workspace_config.unwrap_or_default(),
}
}
// if any configs are invalid return that first
(_, Err(ConfigLoadError::BadConfig(err)))
| (Err(ConfigLoadError::BadConfig(err)), _) => {
return Err(ConfigLoadError::BadConfig(err))
}
(Ok(config), Err(_)) | (Err(_), Ok(config)) => {
(Ok(config), Err(_)) => {
let mut keys = keymap::default();
if let Some(keymap) = config.keys {
merge_keys(&mut keys, keymap);
Expand All @@ -107,11 +134,12 @@ impl Config {
|| Ok(helix_view::editor::Config::default()),
|val| val.try_into().map_err(ConfigLoadError::BadConfig),
)?,
load_workspace_config: config.load_workspace_config.unwrap_or_default(),
}
}

// these are just two io errors return the one for the global config
(Err(err), Err(_)) => return Err(err),
// if there is an io error with the global config, we shouldn't load the local config
(Err(err), _) => return Err(err),
};

Ok(res)
Expand Down
30 changes: 15 additions & 15 deletions helix-term/src/health.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,20 +150,20 @@ pub fn clipboard() -> std::io::Result<()> {
Ok(())
}

pub fn languages_all() -> std::io::Result<()> {
languages(None)
pub fn languages_all(use_local_config: bool) -> std::io::Result<()> {
languages(None, use_local_config)
}

pub fn languages_selection() -> std::io::Result<()> {
let selection = helix_loader::grammar::get_grammar_names().unwrap_or_default();
languages(selection)
pub fn languages_selection(use_local_config: bool) -> std::io::Result<()> {
let selection = helix_loader::grammar::get_grammar_names(use_local_config).unwrap_or_default();
languages(selection, use_local_config)
}

fn languages(selection: Option<HashSet<String>>) -> std::io::Result<()> {
fn languages(selection: Option<HashSet<String>>, use_local_config: bool) -> std::io::Result<()> {
let stdout = std::io::stdout();
let mut stdout = stdout.lock();

let mut syn_loader_conf = match user_lang_config() {
let mut syn_loader_conf = match user_lang_config(use_local_config) {
Ok(conf) => conf,
Err(err) => {
let stderr = std::io::stderr();
Expand Down Expand Up @@ -279,11 +279,11 @@ fn languages(selection: Option<HashSet<String>>) -> std::io::Result<()> {

/// Display diagnostics pertaining to a particular language (LSP,
/// highlight queries, etc).
pub fn language(lang_str: String) -> std::io::Result<()> {
pub fn language(lang_str: String, use_local_config: bool) -> std::io::Result<()> {
let stdout = std::io::stdout();
let mut stdout = stdout.lock();

let syn_loader_conf = match user_lang_config() {
let syn_loader_conf = match user_lang_config(use_local_config) {
Ok(conf) => conf,
Err(err) => {
let stderr = std::io::stderr();
Expand Down Expand Up @@ -433,24 +433,24 @@ fn probe_treesitter_feature(lang: &str, feature: TsFeature) -> std::io::Result<(
Ok(())
}

pub fn print_health(health_arg: Option<String>) -> std::io::Result<()> {
pub fn print_health(health_arg: Option<String>, use_local_config: bool) -> std::io::Result<()> {
match health_arg.as_deref() {
Some("languages") => languages_selection()?,
Some("all-languages") => languages_all()?,
Some("languages") => languages_selection(use_local_config)?,
Some("all-languages") => languages_all(use_local_config)?,
Some("clipboard") => clipboard()?,
None => {
general()?;
clipboard()?;
writeln!(std::io::stdout().lock())?;
languages_selection()?;
languages_selection(use_local_config)?;
}
Some("all") => {
general()?;
clipboard()?;
writeln!(std::io::stdout().lock())?;
languages_all()?;
languages_all(use_local_config)?;
}
Some(lang) => language(lang.to_string())?,
Some(lang) => language(lang.to_string(), use_local_config)?,
}
Ok(())
}
Loading