@@ -52,77 +52,110 @@ pub struct Application {
52
52
impl Application {
53
53
pub fn new ( args : Args , mut config : Config ) -> Result < Self , Error > {
54
54
use helix_view:: editor:: Action ;
55
- let mut compositor = Compositor :: new ( ) ?;
56
- let size = compositor. size ( ) ;
57
-
58
- let conf_dir = helix_core:: config_dir ( ) ;
59
55
60
- let theme_loader =
61
- std:: sync:: Arc :: new ( theme:: Loader :: new ( & conf_dir, & helix_core:: runtime_dir ( ) ) ) ;
62
-
63
- // load default and user config, and merge both
64
- let builtin_err_msg =
65
- "Could not parse built-in languages.toml, something must be very wrong" ;
66
- let def_lang_conf: toml:: Value =
67
- toml:: from_slice ( include_bytes ! ( "../../languages.toml" ) ) . expect ( builtin_err_msg) ;
68
- let def_syn_loader_conf: helix_core:: syntax:: Configuration =
69
- def_lang_conf. clone ( ) . try_into ( ) . expect ( builtin_err_msg) ;
70
- let user_lang_conf = std:: fs:: read ( conf_dir. join ( "languages.toml" ) )
71
- . ok ( )
72
- . map ( |raw| toml:: from_slice ( & raw ) ) ;
73
- let lang_conf = match user_lang_conf {
74
- Some ( Ok ( value) ) => Ok ( merge_toml_values ( def_lang_conf, value) ) ,
75
- Some ( err @ Err ( _) ) => err,
76
- None => Ok ( def_lang_conf) ,
77
- } ;
78
-
79
- let theme = if let Some ( theme) = & config. theme {
80
- match theme_loader. load ( theme) {
81
- Ok ( theme) => theme,
82
- Err ( e) => {
83
- log:: warn!( "failed to load theme `{}` - {}" , theme, e) ;
84
- theme_loader. default ( )
85
- }
86
- }
87
- } else {
88
- theme_loader. default ( )
89
- } ;
56
+ // These configuration directories can contain `config.toml` and `languages.toml`.
57
+ // `local_config_dir` is a `.helix` folder within the projec directory.
58
+ let config_dir = helix_core:: config_dir ( ) ;
59
+ let local_config_dir = helix_core:: local_config_dir ( ) ;
60
+
61
+ // Config override order: local -> global -> default.
62
+ // Read and parse the `languages.toml` files as TOML objects.
63
+ let default_lang_config: toml:: Value =
64
+ toml:: from_slice ( include_bytes ! ( "../../languages.toml" ) )
65
+ . expect ( "failed to read the default `languages.toml`" ) ;
66
+ let lang_config =
67
+ {
68
+ let local_config = match std:: fs:: read ( local_config_dir. join ( "languages.toml" ) ) {
69
+ Ok ( config) => toml:: from_slice ( & config)
70
+ . expect ( "failed to read the local `languages.toml`" ) ,
71
+ Err ( err) if err. kind ( ) == std:: io:: ErrorKind :: NotFound => {
72
+ toml:: Value :: Table ( toml:: value:: Table :: default ( ) )
73
+ }
74
+ Err ( err) => return Err ( Error :: new ( err) ) ,
75
+ } ;
76
+ let global_config = match std:: fs:: read ( config_dir. join ( "languages.toml" ) ) {
77
+ Ok ( config) => toml:: from_slice ( & config)
78
+ . expect ( "failed to read the global `languages.toml`" ) ,
79
+ Err ( err) if err. kind ( ) == std:: io:: ErrorKind :: NotFound => {
80
+ toml:: Value :: Table ( toml:: value:: Table :: default ( ) )
81
+ }
82
+ Err ( err) => return Err ( Error :: new ( err) ) ,
83
+ } ;
90
84
91
- let syn_loader_conf: helix_core:: syntax:: Configuration = lang_conf
92
- . and_then ( |conf| conf. try_into ( ) )
93
- . unwrap_or_else ( |err| {
85
+ merge_toml_values (
86
+ default_lang_config. clone ( ) ,
87
+ merge_toml_values ( global_config, local_config) ,
88
+ )
89
+ } ;
90
+
91
+ // Convert previous `toml::Value`s into the config type.
92
+ let default_syn_loader_config: helix_core:: syntax:: Configuration = default_lang_config
93
+ . try_into ( )
94
+ . expect ( "failed to parse the default `languages.toml`" ) ;
95
+ let syn_loader_config: helix_core:: syntax:: Configuration =
96
+ lang_config. try_into ( ) . unwrap_or_else ( |err| {
94
97
eprintln ! ( "Bad language config: {}" , err) ;
95
98
eprintln ! ( "Press <ENTER> to continue with default language config" ) ;
96
99
use std:: io:: Read ;
97
100
// This waits for an enter press.
98
101
let _ = std:: io:: stdin ( ) . read ( & mut [ ] ) ;
99
- def_syn_loader_conf
102
+ default_syn_loader_config
100
103
} ) ;
101
- let syn_loader = std:: sync:: Arc :: new ( syntax:: Loader :: new ( syn_loader_conf ) ) ;
104
+ let syn_loader = std:: sync:: Arc :: new ( syntax:: Loader :: new ( syn_loader_config ) ) ;
102
105
106
+ // Initialize rendering.
107
+ let theme_loader =
108
+ std:: sync:: Arc :: new ( theme:: Loader :: new ( & config_dir, & helix_core:: runtime_dir ( ) ) ) ;
109
+ let mut compositor = Compositor :: new ( ) ?;
103
110
let mut editor = Editor :: new (
104
- size,
111
+ compositor . size ( ) ,
105
112
theme_loader. clone ( ) ,
106
113
syn_loader. clone ( ) ,
107
114
config. editor . clone ( ) ,
108
115
) ;
109
116
117
+ // Initialize the UI.
110
118
let editor_view = Box :: new ( ui:: EditorView :: new ( std:: mem:: take ( & mut config. keys ) ) ) ;
111
119
compositor. push ( editor_view) ;
112
120
121
+ // Grab and load the user's default theme.
122
+ let theme = if let Some ( theme) = & config. theme {
123
+ match theme_loader. load ( theme) {
124
+ Ok ( theme) => theme,
125
+ Err ( e) => {
126
+ log:: warn!( "failed to load theme `{}` - {}" , theme, e) ;
127
+ theme_loader. default ( )
128
+ }
129
+ }
130
+ } else {
131
+ theme_loader. default ( )
132
+ } ;
133
+ editor. set_theme ( theme) ;
134
+
135
+ #[ cfg( windows) ]
136
+ let signals = futures_util:: stream:: empty ( ) ;
137
+ #[ cfg( not( windows) ) ]
138
+ let signals = Signals :: new ( & [ signal:: SIGTSTP , signal:: SIGCONT ] ) ?;
139
+
140
+ // Handle CLI arguments.
113
141
if args. load_tutor {
114
142
let path = helix_core:: runtime_dir ( ) . join ( "tutor.txt" ) ;
115
143
editor. open ( path, Action :: VerticalSplit ) ?;
116
144
// Unset path to prevent accidentally saving to the original tutor file.
117
145
doc_mut ! ( editor) . set_path ( None ) ?;
118
146
} else if !args. files . is_empty ( ) {
119
- let first = & args. files [ 0 ] ; // we know it's not empty
147
+ // File paths passed as e.g. `hx foo.rs bar.rs`
148
+ // SAFETY: The file count is already known to be non-zero.
149
+ let first = & args. files [ 0 ] ;
150
+
151
+ // If the first argument is a directory, then only the file picker at that
152
+ // path is opened. Otherwise, all files are opened in separate vertical splits.
120
153
if first. is_dir ( ) {
121
154
std:: env:: set_current_dir ( & first) ?;
122
155
editor. new_file ( Action :: VerticalSplit ) ;
123
156
compositor. push ( Box :: new ( ui:: file_picker ( "." . into ( ) , & config. editor ) ) ) ;
124
157
} else {
125
- let nr_of_files = args. files . len ( ) ;
158
+ let file_count = args. files . len ( ) ;
126
159
editor. open ( first. to_path_buf ( ) , Action :: VerticalSplit ) ?;
127
160
for file in args. files {
128
161
if file. is_dir ( ) {
@@ -133,9 +166,11 @@ impl Application {
133
166
editor. open ( file. to_path_buf ( ) , Action :: Load ) ?;
134
167
}
135
168
}
136
- editor. set_status ( format ! ( "Loaded {} files." , nr_of_files ) ) ;
169
+ editor. set_status ( format ! ( "Loaded {} files." , file_count ) ) ;
137
170
}
138
171
} else if stdin ( ) . is_tty ( ) {
172
+ // If no arguments are passed and there is no stdin piping, then only a scratch
173
+ // buffer is opened.
139
174
editor. new_file ( Action :: VerticalSplit ) ;
140
175
} else if cfg ! ( target_os = "macos" ) {
141
176
// On Linux and Windows, we allow the output of a command to be piped into the new buffer.
@@ -148,14 +183,7 @@ impl Application {
148
183
. unwrap_or_else ( |_| editor. new_file ( Action :: VerticalSplit ) ) ;
149
184
}
150
185
151
- editor. set_theme ( theme) ;
152
-
153
- #[ cfg( windows) ]
154
- let signals = futures_util:: stream:: empty ( ) ;
155
- #[ cfg( not( windows) ) ]
156
- let signals = Signals :: new ( & [ signal:: SIGTSTP , signal:: SIGCONT ] ) ?;
157
-
158
- let app = Self {
186
+ Ok ( Self {
159
187
compositor,
160
188
editor,
161
189
@@ -167,9 +195,7 @@ impl Application {
167
195
signals,
168
196
jobs : Jobs :: new ( ) ,
169
197
lsp_progress : LspProgressMap :: new ( ) ,
170
- } ;
171
-
172
- Ok ( app)
198
+ } )
173
199
}
174
200
175
201
fn render ( & mut self ) {
0 commit comments