aboutsummaryrefslogtreecommitdiff
path: root/helix-term
diff options
context:
space:
mode:
Diffstat (limited to 'helix-term')
-rw-r--r--helix-term/src/commands.rs6
-rw-r--r--helix-term/src/commands/typed.rs26
-rw-r--r--helix-term/src/config.rs136
-rw-r--r--helix-term/src/keymap.rs79
-rw-r--r--helix-term/src/main.rs27
-rw-r--r--helix-term/tests/test/helpers.rs12
6 files changed, 193 insertions, 93 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 2f41a2dc..e4d0d753 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -12,7 +12,7 @@ pub use typed::*;
use helix_core::{
char_idx_at_visual_offset, comment,
doc_formatter::TextFormat,
- encoding, find_first_non_whitespace_char, find_root, graphemes,
+ encoding, find_first_non_whitespace_char, find_workspace, graphemes,
history::UndoKind,
increment, indent,
indent::IndentStyle,
@@ -2419,9 +2419,7 @@ fn append_mode(cx: &mut Context) {
}
fn file_picker(cx: &mut Context) {
- // We don't specify language markers, root will be the root of the current
- // git repo or the current dir if we're not in a repo
- let root = find_root(None, &[]);
+ let root = find_workspace();
let picker = ui::file_picker(root, &cx.editor.config());
cx.push_layer(Box::new(overlayed(picker)));
}
diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs
index 2c72686d..ca55151a 100644
--- a/helix-term/src/commands/typed.rs
+++ b/helix-term/src/commands/typed.rs
@@ -1371,13 +1371,16 @@ fn lsp_restart(
return Ok(());
}
+ let editor_config = cx.editor.config.load();
let (_view, doc) = current!(cx.editor);
let config = doc
.language_config()
.context("LSP not defined for the current document")?;
let scope = config.scope.clone();
- cx.editor.language_servers.restart(config, doc.path())?;
+ cx.editor
+ .language_servers
+ .restart(config, doc.path(), &editor_config.workspace_lsp_roots)?;
// This collect is needed because refresh_language_server would need to re-borrow editor.
let document_ids_to_refresh: Vec<DocumentId> = cx
@@ -1970,6 +1973,20 @@ fn open_config(
Ok(())
}
+fn open_workspace_config(
+ cx: &mut compositor::Context,
+ _args: &[Cow<str>],
+ event: PromptEvent,
+) -> anyhow::Result<()> {
+ if event != PromptEvent::Validate {
+ return Ok(());
+ }
+
+ cx.editor
+ .open(&helix_loader::workspace_config_file(), Action::Replace)?;
+ Ok(())
+}
+
fn open_log(
cx: &mut compositor::Context,
_args: &[Cow<str>],
@@ -2647,6 +2664,13 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
signature: CommandSignature::none(),
},
TypableCommand {
+ name: "config-open-workspace",
+ aliases: &[],
+ doc: "Open the workspace config.toml file.",
+ fun: open_workspace_config,
+ signature: CommandSignature::none(),
+ },
+ TypableCommand {
name: "log-open",
aliases: &[],
doc: "Open the helix log file.",
diff --git a/helix-term/src/config.rs b/helix-term/src/config.rs
index 4407a882..9776ef7a 100644
--- a/helix-term/src/config.rs
+++ b/helix-term/src/config.rs
@@ -1,27 +1,34 @@
-use crate::keymap::{default::default, merge_keys, Keymap};
+use crate::keymap;
+use crate::keymap::{merge_keys, Keymap};
+use helix_loader::merge_toml_values;
use helix_view::document::Mode;
use serde::Deserialize;
use std::collections::HashMap;
use std::fmt::Display;
+use std::fs;
use std::io::Error as IOError;
-use std::path::PathBuf;
use toml::de::Error as TomlError;
-#[derive(Debug, Clone, PartialEq, Deserialize)]
-#[serde(deny_unknown_fields)]
+#[derive(Debug, Clone, PartialEq)]
pub struct Config {
pub theme: Option<String>,
- #[serde(default = "default")]
pub keys: HashMap<Mode, Keymap>,
- #[serde(default)]
pub editor: helix_view::editor::Config,
}
+#[derive(Debug, Clone, PartialEq, Deserialize)]
+#[serde(deny_unknown_fields)]
+pub struct ConfigRaw {
+ pub theme: Option<String>,
+ pub keys: Option<HashMap<Mode, Keymap>>,
+ pub editor: Option<toml::Value>,
+}
+
impl Default for Config {
fn default() -> Config {
Config {
theme: None,
- keys: default(),
+ keys: keymap::default(),
editor: helix_view::editor::Config::default(),
}
}
@@ -33,6 +40,12 @@ pub enum ConfigLoadError {
Error(IOError),
}
+impl Default for ConfigLoadError {
+ fn default() -> Self {
+ ConfigLoadError::Error(IOError::new(std::io::ErrorKind::NotFound, "place holder"))
+ }
+}
+
impl Display for ConfigLoadError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
@@ -43,17 +56,72 @@ impl Display for ConfigLoadError {
}
impl Config {
- pub fn load(config_path: PathBuf) -> Result<Config, ConfigLoadError> {
- match std::fs::read_to_string(config_path) {
- Ok(config) => toml::from_str(&config)
- .map(merge_keys)
- .map_err(ConfigLoadError::BadConfig),
- Err(err) => Err(ConfigLoadError::Error(err)),
- }
+ pub fn load(
+ global: Result<String, ConfigLoadError>,
+ local: Result<String, ConfigLoadError>,
+ ) -> Result<Config, ConfigLoadError> {
+ let global_config: Result<ConfigRaw, ConfigLoadError> =
+ global.and_then(|file| toml::from_str(&file).map_err(ConfigLoadError::BadConfig));
+ let local_config: Result<ConfigRaw, ConfigLoadError> =
+ local.and_then(|file| toml::from_str(&file).map_err(ConfigLoadError::BadConfig));
+ let res = match (global_config, local_config) {
+ (Ok(global), Ok(local)) => {
+ 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)
+ }
+
+ let editor = match (global.editor, local.editor) {
+ (None, None) => helix_view::editor::Config::default(),
+ (None, Some(val)) | (Some(val), None) => {
+ val.try_into().map_err(ConfigLoadError::BadConfig)?
+ }
+ (Some(global), Some(local)) => merge_toml_values(global, local, 3)
+ .try_into()
+ .map_err(ConfigLoadError::BadConfig)?,
+ };
+
+ Config {
+ theme: local.theme.or(global.theme),
+ keys,
+ editor,
+ }
+ }
+ // 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)) => {
+ let mut keys = keymap::default();
+ if let Some(keymap) = config.keys {
+ merge_keys(&mut keys, keymap);
+ }
+ Config {
+ theme: config.theme,
+ keys,
+ editor: config.editor.map_or_else(
+ || Ok(helix_view::editor::Config::default()),
+ |val| val.try_into().map_err(ConfigLoadError::BadConfig),
+ )?,
+ }
+ }
+ // these are just two io errors return the one for the global config
+ (Err(err), Err(_)) => return Err(err),
+ };
+
+ Ok(res)
}
pub fn load_default() -> Result<Config, ConfigLoadError> {
- Config::load(helix_loader::config_file())
+ let global_config =
+ fs::read_to_string(helix_loader::config_file()).map_err(ConfigLoadError::Error);
+ let local_config = fs::read_to_string(helix_loader::workspace_config_file())
+ .map_err(ConfigLoadError::Error);
+ Config::load(global_config, local_config)
}
}
@@ -61,6 +129,12 @@ impl Config {
mod tests {
use super::*;
+ impl Config {
+ fn load_test(config: &str) -> Config {
+ Config::load(Ok(config.to_owned()), Err(ConfigLoadError::default())).unwrap()
+ }
+ }
+
#[test]
fn parsing_keymaps_config_file() {
use crate::keymap;
@@ -77,18 +151,24 @@ mod tests {
A-F12 = "move_next_word_end"
"#;
+ let mut keys = keymap::default();
+ merge_keys(
+ &mut keys,
+ hashmap! {
+ Mode::Insert => Keymap::new(keymap!({ "Insert mode"
+ "y" => move_line_down,
+ "S-C-a" => delete_selection,
+ })),
+ Mode::Normal => Keymap::new(keymap!({ "Normal mode"
+ "A-F12" => move_next_word_end,
+ })),
+ },
+ );
+
assert_eq!(
- toml::from_str::<Config>(sample_keymaps).unwrap(),
+ Config::load_test(sample_keymaps),
Config {
- keys: hashmap! {
- Mode::Insert => Keymap::new(keymap!({ "Insert mode"
- "y" => move_line_down,
- "S-C-a" => delete_selection,
- })),
- Mode::Normal => Keymap::new(keymap!({ "Normal mode"
- "A-F12" => move_next_word_end,
- })),
- },
+ keys,
..Default::default()
}
);
@@ -97,11 +177,11 @@ mod tests {
#[test]
fn keys_resolve_to_correct_defaults() {
// From serde default
- let default_keys = toml::from_str::<Config>("").unwrap().keys;
- assert_eq!(default_keys, default());
+ let default_keys = Config::load_test("").keys;
+ assert_eq!(default_keys, keymap::default());
// From the Default trait
let default_keys = Config::default().keys;
- assert_eq!(default_keys, default());
+ assert_eq!(default_keys, keymap::default());
}
}
diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs
index e94a5f66..3033c6a4 100644
--- a/helix-term/src/keymap.rs
+++ b/helix-term/src/keymap.rs
@@ -2,7 +2,6 @@ pub mod default;
pub mod macros;
pub use crate::commands::MappableCommand;
-use crate::config::Config;
use arc_swap::{
access::{DynAccess, DynGuard},
ArcSwap,
@@ -16,7 +15,7 @@ use std::{
sync::Arc,
};
-use default::default;
+pub use default::default;
use macros::key;
#[derive(Debug, Clone)]
@@ -417,12 +416,10 @@ impl Default for Keymaps {
}
/// Merge default config keys with user overwritten keys for custom user config.
-pub fn merge_keys(mut config: Config) -> Config {
- let mut delta = std::mem::replace(&mut config.keys, default());
- for (mode, keys) in &mut config.keys {
+pub fn merge_keys(dst: &mut HashMap<Mode, Keymap>, mut delta: HashMap<Mode, Keymap>) {
+ for (mode, keys) in dst {
keys.merge(delta.remove(mode).unwrap_or_default())
}
- config
}
#[cfg(test)]
@@ -449,26 +446,24 @@ mod tests {
#[test]
fn merge_partial_keys() {
- let config = Config {
- keys: hashmap! {
- Mode::Normal => Keymap::new(
- keymap!({ "Normal mode"
- "i" => normal_mode,
- "无" => insert_mode,
- "z" => jump_backward,
- "g" => { "Merge into goto mode"
- "$" => goto_line_end,
- "g" => delete_char_forward,
- },
- })
- )
- },
- ..Default::default()
+ let keymap = hashmap! {
+ Mode::Normal => Keymap::new(
+ keymap!({ "Normal mode"
+ "i" => normal_mode,
+ "无" => insert_mode,
+ "z" => jump_backward,
+ "g" => { "Merge into goto mode"
+ "$" => goto_line_end,
+ "g" => delete_char_forward,
+ },
+ })
+ )
};
- let mut merged_config = merge_keys(config.clone());
- assert_ne!(config, merged_config);
+ let mut merged_keyamp = default();
+ merge_keys(&mut merged_keyamp, keymap.clone());
+ assert_ne!(keymap, merged_keyamp);
- let mut keymap = Keymaps::new(Box::new(Constant(merged_config.keys.clone())));
+ let mut keymap = Keymaps::new(Box::new(Constant(merged_keyamp.clone())));
assert_eq!(
keymap.get(Mode::Normal, key!('i')),
KeymapResult::Matched(MappableCommand::normal_mode),
@@ -486,7 +481,7 @@ mod tests {
"Leaf should replace node"
);
- let keymap = merged_config.keys.get_mut(&Mode::Normal).unwrap();
+ let keymap = merged_keyamp.get_mut(&Mode::Normal).unwrap();
// Assumes that `g` is a node in default keymap
assert_eq!(
keymap.root().search(&[key!('g'), key!('$')]).unwrap(),
@@ -506,30 +501,28 @@ mod tests {
"Old leaves in subnode should be present in merged node"
);
- assert!(merged_config.keys.get(&Mode::Normal).unwrap().len() > 1);
- assert!(merged_config.keys.get(&Mode::Insert).unwrap().len() > 0);
+ assert!(merged_keyamp.get(&Mode::Normal).unwrap().len() > 1);
+ assert!(merged_keyamp.get(&Mode::Insert).unwrap().len() > 0);
}
#[test]
fn order_should_be_set() {
- let config = Config {
- keys: hashmap! {
- Mode::Normal => Keymap::new(
- keymap!({ "Normal mode"
- "space" => { ""
- "s" => { ""
- "v" => vsplit,
- "c" => hsplit,
- },
+ let keymap = hashmap! {
+ Mode::Normal => Keymap::new(
+ keymap!({ "Normal mode"
+ "space" => { ""
+ "s" => { ""
+ "v" => vsplit,
+ "c" => hsplit,
},
- })
- )
- },
- ..Default::default()
+ },
+ })
+ )
};
- let mut merged_config = merge_keys(config.clone());
- assert_ne!(config, merged_config);
- let keymap = merged_config.keys.get_mut(&Mode::Normal).unwrap();
+ let mut merged_keyamp = default();
+ merge_keys(&mut merged_keyamp, keymap.clone());
+ assert_ne!(keymap, merged_keyamp);
+ let keymap = merged_keyamp.get_mut(&Mode::Normal).unwrap();
// Make sure mapping works
assert_eq!(
keymap
diff --git a/helix-term/src/main.rs b/helix-term/src/main.rs
index aac5c537..e0c3f6e7 100644
--- a/helix-term/src/main.rs
+++ b/helix-term/src/main.rs
@@ -3,7 +3,7 @@ use crossterm::event::EventStream;
use helix_loader::VERSION_AND_GIT_HASH;
use helix_term::application::Application;
use helix_term::args::Args;
-use helix_term::config::Config;
+use helix_term::config::{Config, ConfigLoadError};
use std::path::PathBuf;
fn setup_logging(logpath: PathBuf, verbosity: u64) -> Result<()> {
@@ -126,18 +126,19 @@ FLAGS:
helix_loader::initialize_config_file(args.config_file.clone());
- let config = match std::fs::read_to_string(helix_loader::config_file()) {
- Ok(config) => toml::from_str(&config)
- .map(helix_term::keymap::merge_keys)
- .unwrap_or_else(|err| {
- eprintln!("Bad config: {}", err);
- eprintln!("Press <ENTER> to continue with default config");
- use std::io::Read;
- 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 config = match Config::load_default() {
+ Ok(config) => config,
+ Err(ConfigLoadError::Error(err)) if err.kind() == std::io::ErrorKind::NotFound => {
+ Config::default()
+ }
+ Err(ConfigLoadError::Error(err)) => return Err(Error::new(err)),
+ Err(ConfigLoadError::BadConfig(err)) => {
+ eprintln!("Bad config: {}", err);
+ eprintln!("Press <ENTER> to continue with default config");
+ use std::io::Read;
+ let _ = std::io::stdin().read(&mut []);
+ Config::default()
+ }
};
let syn_loader_conf = helix_core::config::user_syntax_loader().unwrap_or_else(|err| {
diff --git a/helix-term/tests/test/helpers.rs b/helix-term/tests/test/helpers.rs
index ccd07bfa..30fe7d0e 100644
--- a/helix-term/tests/test/helpers.rs
+++ b/helix-term/tests/test/helpers.rs
@@ -1,6 +1,7 @@
use std::{
fs::File,
io::{Read, Write},
+ mem::replace,
path::PathBuf,
time::Duration,
};
@@ -222,10 +223,11 @@ pub fn temp_file_with_contents<S: AsRef<str>>(
/// Generates a config with defaults more suitable for integration tests
pub fn test_config() -> Config {
- merge_keys(Config {
+ Config {
editor: test_editor_config(),
+ keys: helix_term::keymap::default(),
..Default::default()
- })
+ }
}
pub fn test_editor_config() -> helix_view::editor::Config {
@@ -300,8 +302,10 @@ impl AppBuilder {
// Remove this attribute once `with_config` is used in a test:
#[allow(dead_code)]
- pub fn with_config(mut self, config: Config) -> Self {
- self.config = helix_term::keymap::merge_keys(config);
+ pub fn with_config(mut self, mut config: Config) -> Self {
+ let keys = replace(&mut config.keys, helix_term::keymap::default());
+ merge_keys(&mut config.keys, keys);
+ self.config = config;
self
}