2024-12-23 19:36:44 +08:00
use crate ::keymap ::{ self } ;
2023-06-03 17:29:08 +08:00
use crate ::keymap ::{ merge_keys , KeyTrie } ;
2023-01-31 07:31:21 +08:00
use helix_loader ::merge_toml_values ;
2024-12-23 19:36:44 +08:00
use helix_view ::commands ::custom ::CustomTypableCommand ;
2022-03-25 17:05:20 +08:00
use helix_view ::document ::Mode ;
2021-06-23 01:04:04 +08:00
use serde ::Deserialize ;
2022-03-25 17:05:20 +08:00
use std ::collections ::HashMap ;
use std ::fmt ::Display ;
2023-01-31 07:31:21 +08:00
use std ::fs ;
2022-03-25 17:05:20 +08:00
use std ::io ::Error as IOError ;
2024-12-23 19:36:44 +08:00
use std ::sync ::Arc ;
2022-03-25 17:05:20 +08:00
use toml ::de ::Error as TomlError ;
2021-06-17 19:08:05 +08:00
2023-01-31 07:31:21 +08:00
#[ derive(Debug, Clone, PartialEq) ]
2021-06-17 19:08:05 +08:00
pub struct Config {
2021-06-21 03:31:45 +08:00
pub theme : Option < String > ,
2023-06-03 17:29:08 +08:00
pub keys : HashMap < Mode , KeyTrie > ,
2021-08-08 13:07:14 +08:00
pub editor : helix_view ::editor ::Config ,
2021-06-17 19:08:05 +08:00
}
2023-01-31 07:31:21 +08:00
#[ derive(Debug, Clone, PartialEq, Deserialize) ]
#[ serde(deny_unknown_fields) ]
pub struct ConfigRaw {
pub theme : Option < String > ,
2023-06-03 17:29:08 +08:00
pub keys : Option < HashMap < Mode , KeyTrie > > ,
2023-01-31 07:31:21 +08:00
pub editor : Option < toml ::Value > ,
2024-12-23 19:36:44 +08:00
commands : Option < Commands > ,
2023-01-31 07:31:21 +08:00
}
2022-03-25 17:05:20 +08:00
impl Default for Config {
fn default ( ) -> Config {
Config {
theme : None ,
2023-01-31 07:31:21 +08:00
keys : keymap ::default ( ) ,
2022-03-25 17:05:20 +08:00
editor : helix_view ::editor ::Config ::default ( ) ,
}
}
}
2024-12-23 19:36:44 +08:00
#[ derive(Debug, Deserialize, PartialEq, Eq, Clone) ]
struct Commands {
#[ serde(flatten) ]
commands : HashMap < String , CustomTypableCommand > ,
}
impl Commands {
/// Adds the `key` of the command as the `name` and checks for the `hidden` status
/// and adds it to the `CustomTypableCommand`.
fn process ( mut self ) -> Self {
for ( key , value ) in & mut self . commands {
value . name = key . trim_start_matches ( ':' ) . to_string ( ) ;
value . hidden = ! key . starts_with ( ':' ) ;
}
self
}
}
2022-03-25 17:05:20 +08:00
#[ derive(Debug) ]
pub enum ConfigLoadError {
BadConfig ( TomlError ) ,
Error ( IOError ) ,
}
2023-01-31 07:31:21 +08:00
impl Default for ConfigLoadError {
fn default ( ) -> Self {
ConfigLoadError ::Error ( IOError ::new ( std ::io ::ErrorKind ::NotFound , " place holder " ) )
}
}
2022-03-25 17:05:20 +08:00
impl Display for ConfigLoadError {
fn fmt ( & self , f : & mut std ::fmt ::Formatter < '_ > ) -> std ::fmt ::Result {
match self {
ConfigLoadError ::BadConfig ( err ) = > err . fmt ( f ) ,
ConfigLoadError ::Error ( err ) = > err . fmt ( f ) ,
}
}
}
impl Config {
2023-01-31 07:31:21 +08:00
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 ) {
2024-12-23 19:36:44 +08:00
( Ok ( mut global ) , Ok ( local ) ) = > {
2023-01-31 07:31:21 +08:00
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 )
}
2024-12-23 19:36:44 +08:00
let mut editor = match ( global . editor , local . editor ) {
2023-01-31 07:31:21 +08:00
( 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 ) ? ,
} ;
2024-12-23 19:36:44 +08:00
// Merge locally defined commands, overwriting global space commands if encountered
if let Some ( lcommands ) = local . commands {
if let Some ( gcommands ) = & mut global . commands {
for ( name , details ) in lcommands . commands {
gcommands . commands . insert ( name , details ) ;
}
} else {
global . commands = Some ( lcommands ) ;
}
}
// If any commands were defined anywhere, add to editor
if let Some ( commands ) = global . commands . map ( Commands ::process ) {
let mut holder = Vec ::with_capacity ( commands . commands . len ( ) ) ;
for ( _ , command ) in commands . commands {
holder . push ( command ) ;
}
editor . commands . commands = Arc ::from ( holder ) ;
}
2023-01-31 07:31:21 +08:00
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 ) ;
}
2024-12-23 19:36:44 +08:00
let mut editor = config . editor . map_or_else (
| | Ok ( helix_view ::editor ::Config ::default ( ) ) ,
| val | val . try_into ( ) . map_err ( ConfigLoadError ::BadConfig ) ,
) ? ;
// Add custom commands
if let Some ( commands ) = config . commands . map ( Commands ::process ) {
let mut holder = Vec ::with_capacity ( commands . commands . len ( ) ) ;
for ( _ , command ) in commands . commands {
holder . push ( command ) ;
}
editor . commands . commands = Arc ::from ( holder ) ;
}
2023-01-31 07:31:21 +08:00
Config {
theme : config . theme ,
keys ,
2024-12-23 19:36:44 +08:00
editor ,
2023-01-31 07:31:21 +08:00
}
}
2022-10-09 06:14:49 +08:00
2023-01-31 07:31:21 +08:00
// these are just two io errors return the one for the global config
( Err ( err ) , Err ( _ ) ) = > return Err ( err ) ,
} ;
Ok ( res )
2022-03-25 17:05:20 +08:00
}
pub fn load_default ( ) -> Result < Config , ConfigLoadError > {
2023-01-31 07:31:21 +08:00
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 )
2022-03-25 17:05:20 +08:00
}
}
2022-01-08 23:32:50 +08:00
#[ cfg(test) ]
mod tests {
2024-12-23 19:36:44 +08:00
2022-01-08 23:32:50 +08:00
use super ::* ;
2023-01-31 07:31:21 +08:00
impl Config {
2024-12-23 19:36:44 +08:00
fn load_test ( config : & str ) -> Result < Config , ConfigLoadError > {
Config ::load ( Ok ( config . to_owned ( ) ) , Err ( ConfigLoadError ::default ( ) ) )
2023-01-31 07:31:21 +08:00
}
}
2022-01-08 23:32:50 +08:00
#[ test ]
fn parsing_keymaps_config_file ( ) {
use crate ::keymap ;
use helix_core ::hashmap ;
use helix_view ::document ::Mode ;
let sample_keymaps = r #"
2021-06-23 01:04:04 +08:00
[ keys . insert ]
y = " move_line_down "
S - C - a = " delete_selection "
[ keys . normal ]
A - F12 = " move_next_word_end "
" #;
2021-06-18 00:52:41 +08:00
2023-01-31 07:31:21 +08:00
let mut keys = keymap ::default ( ) ;
merge_keys (
& mut keys ,
hashmap! {
2023-06-03 17:29:08 +08:00
Mode ::Insert = > keymap! ( { " Insert mode "
2023-01-31 07:31:21 +08:00
" y " = > move_line_down ,
" S-C-a " = > delete_selection ,
2023-06-03 17:29:08 +08:00
} ) ,
Mode ::Normal = > keymap! ( { " Normal mode "
2023-01-31 07:31:21 +08:00
" A-F12 " = > move_next_word_end ,
2023-06-03 17:29:08 +08:00
} ) ,
2023-01-31 07:31:21 +08:00
} ,
) ;
2022-01-08 23:32:50 +08:00
assert_eq! (
2024-12-23 19:36:44 +08:00
Config ::load_test ( sample_keymaps ) . unwrap ( ) ,
2022-01-08 23:32:50 +08:00
Config {
2023-01-31 07:31:21 +08:00
keys ,
2022-01-08 23:32:50 +08:00
.. Default ::default ( )
}
) ;
}
2022-03-25 17:05:20 +08:00
#[ test ]
fn keys_resolve_to_correct_defaults ( ) {
// From serde default
2024-12-23 19:36:44 +08:00
let default_keys = Config ::load_test ( " " ) . unwrap ( ) . keys ;
2023-01-31 07:31:21 +08:00
assert_eq! ( default_keys , keymap ::default ( ) ) ;
2022-03-25 17:05:20 +08:00
// From the Default trait
let default_keys = Config ::default ( ) . keys ;
2023-01-31 07:31:21 +08:00
assert_eq! ( default_keys , keymap ::default ( ) ) ;
2022-03-25 17:05:20 +08:00
}
2024-12-23 19:36:44 +08:00
#[ test ]
fn should_deserialize_custom_commands ( ) {
let config = r #"
[ commands ]
" :wq " = [ " :write " , " :quit " ]
" :w " = " :write! "
" :wcd! " = { commands = [ ' :write ! % arg { 0 } ' , ' :cd % sh { % arg { 0 } | path dirname } ' ] , desc = " writes buffer to disk forcefully, then changes to its directory " , accepts = " <path> " , completer = " :write " }
" :0 " = { commands = [ " :goto 1 " ] }
" :static " = " no_op "
" :d " = " @100xd "
" :foo " = { commands = [ " no_op " , " :noop " ] }
[ commands . " :touch " ]
commands = [ " :noop %sh{ touch %arg{0} } " ]
desc = " creates file at path "
accepts = " <path> "
completer = " :write "
" #;
if let Err ( err ) = Config ::load_test ( config ) {
panic! ( " {err:#?} " )
} ;
}
#[ test ]
#[ should_panic ]
fn should_fail_to_deserialize_custom_command_with_macros_in_sequence ( ) {
let config = r #"
[ commands ]
" :fail " = { commands = [ " @100xd " , " @100xd " ] }
" #;
Config ::load_test ( config ) . unwrap ( ) ;
}
2021-06-17 19:08:05 +08:00
}