on master help sign in
Configurable keys 2 (Mapping keys to commands) (#268)

* Add convenience/clarity wrapper for Range initialization * Add keycode parse and display methods * Add remapping functions and tests * Implement key remapping * Add remapping book entry * Use raw string literal for toml * Add command constants * Make command functions private * Map directly to commands * Match key parsing/displaying to Kakoune * Formatting pass * Update documentation * Formatting * Fix example in the book * Refactor into single config file * Formatting * Refactor configuration and add keymap newtype wrappers * Address first batch of PR comments * Replace FromStr with custom deserialize

commitf7e00cf720f55ea82933ac6625b7511e9d38139e authorPabloMansanet <pablomansanet@gmail.com> dateThu 17 Jun 2021, 13:08 (5 years ago) parent47d2e3a
10 files changed+928−340
book/src/SUMMARY.md+1 book/src/remapping.md+48 helix-term/src/application.rs+4−3 helix-term/src/commands.rs+294−131 helix-term/src/config.rs+33 helix-term/src/keymap.rs+500−188 helix-term/src/lib.rs+1 helix-term/src/main.rs+9−2 helix-term/src/ui/editor.rs+12−15 helix-view/src/document.rs+26−1
book/src/SUMMARY.md+1
@@ -4,4 +4,5 @@
44- [Usage](./usage.md)
55- [Configuration](./configuration.md)
66 - [Keymap](./keymap.md)
7+ - [Key Remapping](./remapping.md)
78 - [Hooks](./hooks.md)
book/src/remapping.mdnew file+48
@@ -0,0 +1,48 @@
1+# Key Remapping
2+
3+One-way key remapping is temporarily supported via a simple TOML configuration
4+file. (More powerful solutions such as rebinding via commands will be
5+available in the feature).
6+
7+To remap keys, write a `config.toml` file in your `helix` configuration
8+directory (default `~/.config/helix` in Linux systems) with a structure like
9+this:
10+
11+```toml
12+# At most one section each of 'keys.normal', 'keys.insert' and 'keys.select'
13+[keys.normal]
14+a = "move_char_left" # Maps the 'a' key to the move_char_left command
15+w = "move_line_up" # Maps the 'w' key move_line_up
16+C-S-esc = "select_line" # Maps Control-Shift-Escape to select_line
17+
18+[keys.insert]
19+A-x = "normal_mode" # Maps Alt-X to enter normal mode
20+```
21+
22+Control, Shift and Alt modifiers are encoded respectively with the prefixes
23+`C-`, `S-` and `A-`. Special keys are encoded as follows:
24+
25+* Backspace => "backspace"
26+* Space => "space"
27+* Return/Enter => "ret"
28+* < => "lt"
29+* > => "gt"
30+* + => "plus"
31+* - => "minus"
32+* ; => "semicolon"
33+* % => "percent"
34+* Left => "left"
35+* Right => "right"
36+* Up => "up"
37+* Home => "home"
38+* End => "end"
39+* Page Up => "pageup"
40+* Page Down => "pagedown"
41+* Tab => "tab"
42+* Back Tab => "backtab"
43+* Delete => "del"
44+* Insert => "ins"
45+* Null => "null"
46+* Escape => "esc"
47+
48+Commands can be found in the source code at `../../helix-term/src/commands.rs`
helix-term/src/application.rs+4−3
@@ -1,7 +1,7 @@
11use helix_lsp::lsp;
22use helix_view::{document::Mode, Document, Editor, Theme, View};
33
4−use crate::{args::Args, compositor::Compositor, ui};
4+use crate::{args::Args, compositor::Compositor, config::Config, keymap::Keymaps, ui};
55
66use log::{error, info};
77
@@ -40,13 +40,14 @@ pub struct Application {
4040}
4141
4242impl Application {
43− pub fn new(mut args: Args) -> Result<Self, Error> {
43+ pub fn new(mut args: Args, config: Config) -> Result<Self, Error> {
4444 use helix_view::editor::Action;
4545 let mut compositor = Compositor::new()?;
4646 let size = compositor.size();
4747 let mut editor = Editor::new(size);
4848
49− compositor.push(Box::new(ui::EditorView::new()));
49+ let mut editor_view = Box::new(ui::EditorView::new(config.keymaps));
50+ compositor.push(editor_view);
5051
5152 if !args.files.is_empty() {
5253 let first = &args.files[0]; // we know it's not empty
helix-term/src/commands.rs+294−131
@@ -15,11 +15,13 @@ use helix_view::{
1515 Document, DocumentId, Editor, ViewId,
1616};
1717
18+use anyhow::anyhow;
1819use helix_lsp::{
1920 lsp,
2021 util::{lsp_pos_to_pos, lsp_range_to_range, pos_to_lsp_pos, range_to_lsp_range},
2122 OffsetEncoding,
2223};
24+use insert::*;
2325use movement::Movement;
2426
2527use crate::{
@@ -29,7 +31,7 @@ use crate::{
2931
3032use crate::application::{LspCallbackWrapper, LspCallbacks};
3133use futures_util::FutureExt;
32−use std::future::Future;
34+use std::{fmt, future::Future, path::Display, str::FromStr};
3335
3436use std::{
3537 borrow::Cow,
@@ -133,11 +135,145 @@ fn align_view(doc: &Document, view: &mut View, align: Align) {
133135 view.first_line = line.saturating_sub(relative);
134136}
135137
136−/// A command is a function that takes the current state and a count, and does a side-effect on the
137−/// state (usually by creating and applying a transaction).
138−pub type Command = fn(cx: &mut Context);
138+/// A command is composed of a static name, and a function that takes the current state plus a count,
139+/// and does a side-effect on the state (usually by creating and applying a transaction).
140+#[derive(Copy, Clone)]
141+pub struct Command(&'static str, fn(cx: &mut Context));
142+
143+macro_rules! commands {
144+ ( $($name:ident),* ) => {
145+ $(
146+ #[allow(non_upper_case_globals)]
147+ pub const $name: Self = Self(stringify!($name), $name);
148+ )*
149+
150+ pub const COMMAND_LIST: &'static [Self] = &[
151+ $( Self::$name, )*
152+ ];
153+ }
154+}
139155
140−pub fn move_char_left(cx: &mut Context) {
156+impl Command {
157+ pub fn execute(&self, cx: &mut Context) {
158+ (self.1)(cx);
159+ }
160+
161+ pub fn name(&self) -> &'static str {
162+ self.0
163+ }
164+
165+ commands!(
166+ move_char_left,
167+ move_char_right,
168+ move_line_up,
169+ move_line_down,
170+ move_line_end,
171+ move_line_start,
172+ move_first_nonwhitespace,
173+ move_next_word_start,
174+ move_prev_word_start,
175+ move_next_word_end,
176+ move_file_start,
177+ move_file_end,
178+ extend_next_word_start,
179+ extend_prev_word_start,
180+ extend_next_word_end,
181+ find_till_char,
182+ find_next_char,
183+ extend_till_char,
184+ extend_next_char,
185+ till_prev_char,
186+ find_prev_char,
187+ extend_till_prev_char,
188+ extend_prev_char,
189+ extend_first_nonwhitespace,
190+ replace,
191+ page_up,
192+ page_down,
193+ half_page_up,
194+ half_page_down,
195+ extend_char_left,
196+ extend_char_right,
197+ extend_line_up,
198+ extend_line_down,
199+ extend_line_end,
200+ extend_line_start,
201+ select_all,
202+ select_regex,
203+ split_selection,
204+ split_selection_on_newline,
205+ search,
206+ search_next,
207+ extend_search_next,
208+ search_selection,
209+ select_line,
210+ extend_line,
211+ delete_selection,
212+ change_selection,
213+ collapse_selection,
214+ flip_selections,
215+ insert_mode,
216+ append_mode,
217+ command_mode,
218+ file_picker,
219+ buffer_picker,
220+ symbol_picker,
221+ prepend_to_line,
222+ append_to_line,
223+ open_below,
224+ open_above,
225+ normal_mode,
226+ goto_mode,
227+ select_mode,
228+ exit_select_mode,
229+ goto_definition,
230+ goto_type_definition,
231+ goto_implementation,
232+ goto_reference,
233+ goto_first_diag,
234+ goto_last_diag,
235+ goto_next_diag,
236+ goto_prev_diag,
237+ signature_help,
238+ insert_tab,
239+ insert_newline,
240+ delete_char_backward,
241+ delete_char_forward,
242+ delete_word_backward,
243+ undo,
244+ redo,
245+ yank,
246+ replace_with_yanked,
247+ paste_after,
248+ paste_before,
249+ indent,
250+ unindent,
251+ format_selections,
252+ join_selections,
253+ keep_selections,
254+ keep_primary_selection,
255+ save,
256+ completion,
257+ hover,
258+ toggle_comments,
259+ expand_selection,
260+ match_brackets,
261+ jump_forward,
262+ jump_backward,
263+ window_mode,
264+ rotate_view,
265+ hsplit,
266+ vsplit,
267+ wclose,
268+ select_register,
269+ space_mode,
270+ view_mode,
271+ left_bracket_mode,
272+ right_bracket_mode
273+ );
274+}
275+
276+fn move_char_left(cx: &mut Context) {
141277 let count = cx.count();
142278 let (view, doc) = cx.current();
143279 let text = doc.text().slice(..);
@@ -147,7 +283,7 @@ pub fn move_char_left(cx: &mut Context) {
147283 doc.set_selection(view.id, selection);
148284}
149285
150−pub fn move_char_right(cx: &mut Context) {
286+fn move_char_right(cx: &mut Context) {
151287 let count = cx.count();
152288 let (view, doc) = cx.current();
153289 let text = doc.text().slice(..);
@@ -157,7 +293,7 @@ pub fn move_char_right(cx: &mut Context) {
157293 doc.set_selection(view.id, selection);
158294}
159295
160−pub fn move_line_up(cx: &mut Context) {
296+fn move_line_up(cx: &mut Context) {
161297 let count = cx.count();
162298 let (view, doc) = cx.current();
163299 let text = doc.text().slice(..);
@@ -167,7 +303,7 @@ pub fn move_line_up(cx: &mut Context) {
167303 doc.set_selection(view.id, selection);
168304}
169305
170−pub fn move_line_down(cx: &mut Context) {
306+fn move_line_down(cx: &mut Context) {
171307 let count = cx.count();
172308 let (view, doc) = cx.current();
173309 let text = doc.text().slice(..);
@@ -177,7 +313,7 @@ pub fn move_line_down(cx: &mut Context) {
177313 doc.set_selection(view.id, selection);
178314}
179315
180−pub fn move_line_end(cx: &mut Context) {
316+fn move_line_end(cx: &mut Context) {
181317 let (view, doc) = cx.current();
182318
183319 let selection = doc.selection(view.id).transform(|range| {
@@ -193,7 +329,7 @@ pub fn move_line_end(cx: &mut Context) {
193329 doc.set_selection(view.id, selection);
194330}
195331
196−pub fn move_line_start(cx: &mut Context) {
332+fn move_line_start(cx: &mut Context) {
197333 let (view, doc) = cx.current();
198334
199335 let selection = doc.selection(view.id).transform(|range| {
@@ -208,7 +344,7 @@ pub fn move_line_start(cx: &mut Context) {
208344 doc.set_selection(view.id, selection);
209345}
210346
211−pub fn move_first_nonwhitespace(cx: &mut Context) {
347+fn move_first_nonwhitespace(cx: &mut Context) {
212348 let (view, doc) = cx.current();
213349
214350 let selection = doc.selection(view.id).transform(|range| {
@@ -230,7 +366,7 @@ pub fn move_first_nonwhitespace(cx: &mut Context) {
230366// Range::new(if Move { pos } if Extend { range.anchor }, pos)
231367// since these all really do the same thing
232368
233−pub fn move_next_word_start(cx: &mut Context) {
369+fn move_next_word_start(cx: &mut Context) {
234370 let count = cx.count();
235371 let (view, doc) = cx.current();
236372 let text = doc.text().slice(..);
@@ -242,7 +378,7 @@ pub fn move_next_word_start(cx: &mut Context) {
242378 doc.set_selection(view.id, selection);
243379}
244380
245−pub fn move_prev_word_start(cx: &mut Context) {
381+fn move_prev_word_start(cx: &mut Context) {
246382 let count = cx.count();
247383 let (view, doc) = cx.current();
248384 let text = doc.text().slice(..);
@@ -254,7 +390,7 @@ pub fn move_prev_word_start(cx: &mut Context) {
254390 doc.set_selection(view.id, selection);
255391}
256392
257−pub fn move_next_word_end(cx: &mut Context) {
393+fn move_next_word_end(cx: &mut Context) {
258394 let count = cx.count();
259395 let (view, doc) = cx.current();
260396 let text = doc.text().slice(..);
@@ -266,13 +402,13 @@ pub fn move_next_word_end(cx: &mut Context) {
266402 doc.set_selection(view.id, selection);
267403}
268404
269−pub fn move_file_start(cx: &mut Context) {
405+fn move_file_start(cx: &mut Context) {
270406 push_jump(cx.editor);
271407 let (view, doc) = cx.current();
272408 doc.set_selection(view.id, Selection::point(0));
273409}
274410
275−pub fn move_file_end(cx: &mut Context) {
411+fn move_file_end(cx: &mut Context) {
276412 push_jump(cx.editor);
277413 let (view, doc) = cx.current();
278414 let text = doc.text();
@@ -280,7 +416,7 @@ pub fn move_file_end(cx: &mut Context) {
280416 doc.set_selection(view.id, Selection::point(last_line));
281417}
282418
283−pub fn extend_next_word_start(cx: &mut Context) {
419+fn extend_next_word_start(cx: &mut Context) {
284420 let count = cx.count();
285421 let (view, doc) = cx.current();
286422 let text = doc.text().slice(..);
@@ -294,7 +430,7 @@ pub fn extend_next_word_start(cx: &mut Context) {
294430 doc.set_selection(view.id, selection);
295431}
296432
297−pub fn extend_prev_word_start(cx: &mut Context) {
433+fn extend_prev_word_start(cx: &mut Context) {
298434 let count = cx.count();
299435 let (view, doc) = cx.current();
300436 let text = doc.text().slice(..);
@@ -307,7 +443,7 @@ pub fn extend_prev_word_start(cx: &mut Context) {
307443 doc.set_selection(view.id, selection);
308444}
309445
310−pub fn extend_next_word_end(cx: &mut Context) {
446+fn extend_next_word_end(cx: &mut Context) {
311447 let count = cx.count();
312448 let (view, doc) = cx.current();
313449 let text = doc.text().slice(..);
@@ -365,7 +501,7 @@ where
365501 })
366502}
367503
368−pub fn find_till_char(cx: &mut Context) {
504+fn find_till_char(cx: &mut Context) {
369505 find_char_impl(
370506 cx,
371507 search::find_nth_next,
@@ -374,7 +510,7 @@ pub fn find_till_char(cx: &mut Context) {
374510 )
375511}
376512
377−pub fn find_next_char(cx: &mut Context) {
513+fn find_next_char(cx: &mut Context) {
378514 find_char_impl(
379515 cx,
380516 search::find_nth_next,
@@ -383,7 +519,7 @@ pub fn find_next_char(cx: &mut Context) {
383519 )
384520}
385521
386−pub fn extend_till_char(cx: &mut Context) {
522+fn extend_till_char(cx: &mut Context) {
387523 find_char_impl(
388524 cx,
389525 search::find_nth_next,
@@ -392,7 +528,7 @@ pub fn extend_till_char(cx: &mut Context) {
392528 )
393529}
394530
395−pub fn extend_next_char(cx: &mut Context) {
531+fn extend_next_char(cx: &mut Context) {
396532 find_char_impl(
397533 cx,
398534 search::find_nth_next,
@@ -401,7 +537,7 @@ pub fn extend_next_char(cx: &mut Context) {
401537 )
402538}
403539
404−pub fn till_prev_char(cx: &mut Context) {
540+fn till_prev_char(cx: &mut Context) {
405541 find_char_impl(
406542 cx,
407543 search::find_nth_prev,
@@ -410,7 +546,7 @@ pub fn till_prev_char(cx: &mut Context) {
410546 )
411547}
412548
413−pub fn find_prev_char(cx: &mut Context) {
549+fn find_prev_char(cx: &mut Context) {
414550 find_char_impl(
415551 cx,
416552 search::find_nth_prev,
@@ -419,7 +555,7 @@ pub fn find_prev_char(cx: &mut Context) {
419555 )
420556}
421557
422−pub fn extend_till_prev_char(cx: &mut Context) {
558+fn extend_till_prev_char(cx: &mut Context) {
423559 find_char_impl(
424560 cx,
425561 search::find_nth_prev,
@@ -428,7 +564,7 @@ pub fn extend_till_prev_char(cx: &mut Context) {
428564 )
429565}
430566
431−pub fn extend_prev_char(cx: &mut Context) {
567+fn extend_prev_char(cx: &mut Context) {
432568 find_char_impl(
433569 cx,
434570 search::find_nth_prev,
@@ -437,7 +573,7 @@ pub fn extend_prev_char(cx: &mut Context) {
437573 )
438574}
439575
440−pub fn extend_first_nonwhitespace(cx: &mut Context) {
576+fn extend_first_nonwhitespace(cx: &mut Context) {
441577 let (view, doc) = cx.current();
442578
443579 let selection = doc.selection(view.id).transform(|range| {
@@ -455,7 +591,7 @@ pub fn extend_first_nonwhitespace(cx: &mut Context) {
455591 doc.set_selection(view.id, selection);
456592}
457593
458−pub fn replace(cx: &mut Context) {
594+fn replace(cx: &mut Context) {
459595 // need to wait for next key
460596 cx.on_next_key(move |cx, event| {
461597 let ch = match event {
@@ -535,31 +671,31 @@ fn scroll(cx: &mut Context, offset: usize, direction: Direction) {
535671 doc.set_selection(view.id, Selection::point(pos));
536672}
537673
538−pub fn page_up(cx: &mut Context) {
674+fn page_up(cx: &mut Context) {
539675 let view = cx.view();
540676 let offset = view.area.height as usize;
541677 scroll(cx, offset, Direction::Backward);
542678}
543679
544−pub fn page_down(cx: &mut Context) {
680+fn page_down(cx: &mut Context) {
545681 let view = cx.view();
546682 let offset = view.area.height as usize;
547683 scroll(cx, offset, Direction::Forward);
548684}
549685
550−pub fn half_page_up(cx: &mut Context) {
686+fn half_page_up(cx: &mut Context) {
551687 let view = cx.view();
552688 let offset = view.area.height as usize / 2;
553689 scroll(cx, offset, Direction::Backward);
554690}
555691
556−pub fn half_page_down(cx: &mut Context) {
692+fn half_page_down(cx: &mut Context) {
557693 let view = cx.view();
558694 let offset = view.area.height as usize / 2;
559695 scroll(cx, offset, Direction::Forward);
560696}
561697
562−pub fn extend_char_left(cx: &mut Context) {
698+fn extend_char_left(cx: &mut Context) {
563699 let count = cx.count();
564700 let (view, doc) = cx.current();
565701 let text = doc.text().slice(..);
@@ -569,7 +705,7 @@ pub fn extend_char_left(cx: &mut Context) {
569705 doc.set_selection(view.id, selection);
570706}
571707
572−pub fn extend_char_right(cx: &mut Context) {
708+fn extend_char_right(cx: &mut Context) {
573709 let count = cx.count();
574710 let (view, doc) = cx.current();
575711 let text = doc.text().slice(..);
@@ -579,7 +715,7 @@ pub fn extend_char_right(cx: &mut Context) {
579715 doc.set_selection(view.id, selection);
580716}
581717
582−pub fn extend_line_up(cx: &mut Context) {
718+fn extend_line_up(cx: &mut Context) {
583719 let count = cx.count();
584720 let (view, doc) = cx.current();
585721 let text = doc.text().slice(..);
@@ -589,7 +725,7 @@ pub fn extend_line_up(cx: &mut Context) {
589725 doc.set_selection(view.id, selection);
590726}
591727
592−pub fn extend_line_down(cx: &mut Context) {
728+fn extend_line_down(cx: &mut Context) {
593729 let count = cx.count();
594730 let (view, doc) = cx.current();
595731 let text = doc.text().slice(..);
@@ -599,7 +735,7 @@ pub fn extend_line_down(cx: &mut Context) {
599735 doc.set_selection(view.id, selection);
600736}
601737
602−pub fn extend_line_end(cx: &mut Context) {
738+fn extend_line_end(cx: &mut Context) {
603739 let (view, doc) = cx.current();
604740
605741 let selection = doc.selection(view.id).transform(|range| {
@@ -615,7 +751,7 @@ pub fn extend_line_end(cx: &mut Context) {
615751 doc.set_selection(view.id, selection);
616752}
617753
618−pub fn extend_line_start(cx: &mut Context) {
754+fn extend_line_start(cx: &mut Context) {
619755 let (view, doc) = cx.current();
620756
621757 let selection = doc.selection(view.id).transform(|range| {
@@ -630,14 +766,14 @@ pub fn extend_line_start(cx: &mut Context) {
630766 doc.set_selection(view.id, selection);
631767}
632768
633−pub fn select_all(cx: &mut Context) {
769+fn select_all(cx: &mut Context) {
634770 let (view, doc) = cx.current();
635771
636772 let end = doc.text().len_chars().saturating_sub(1);
637773 doc.set_selection(view.id, Selection::single(0, end))
638774}
639775
640−pub fn select_regex(cx: &mut Context) {
776+fn select_regex(cx: &mut Context) {
641777 let prompt = ui::regex_prompt(cx, "select:".to_string(), move |view, doc, _, regex| {
642778 let text = doc.text().slice(..);
643779 if let Some(selection) = selection::select_on_matches(text, doc.selection(view.id), &regex)
@@ -649,7 +785,7 @@ pub fn select_regex(cx: &mut Context) {
649785 cx.push_layer(Box::new(prompt));
650786}
651787
652−pub fn split_selection(cx: &mut Context) {
788+fn split_selection(cx: &mut Context) {
653789 let prompt = ui::regex_prompt(cx, "split:".to_string(), move |view, doc, _, regex| {
654790 let text = doc.text().slice(..);
655791 let selection = selection::split_on_matches(text, doc.selection(view.id), &regex);
@@ -659,7 +795,7 @@ pub fn split_selection(cx: &mut Context) {
659795 cx.push_layer(Box::new(prompt));
660796}
661797
662−pub fn split_selection_on_newline(cx: &mut Context) {
798+fn split_selection_on_newline(cx: &mut Context) {
663799 let (view, doc) = cx.current();
664800 let text = doc.text().slice(..);
665801 // only compile the regex once
@@ -711,7 +847,7 @@ fn search_impl(doc: &mut Document, view: &mut View, contents: &str, regex: &Rege
711847}
712848
713849// TODO: use one function for search vs extend
714−pub fn search(cx: &mut Context) {
850+fn search(cx: &mut Context) {
715851 let (view, doc) = cx.current();
716852
717853 // TODO: could probably share with select_on_matches?
@@ -733,9 +869,8 @@ pub fn search(cx: &mut Context) {
733869
734870 cx.push_layer(Box::new(prompt));
735871}
736−// can't search next for ""compose"" for some reason
737872
738−pub fn search_next_impl(cx: &mut Context, extend: bool) {
873+fn search_next_impl(cx: &mut Context, extend: bool) {
739874 let (view, doc, registers) = cx.current_with_registers();
740875 if let Some(query) = registers.read('\\') {
741876 let query = query.first().unwrap();
@@ -745,15 +880,15 @@ pub fn search_next_impl(cx: &mut Context, extend: bool) {
745880 }
746881}
747882
748−pub fn search_next(cx: &mut Context) {
883+fn search_next(cx: &mut Context) {
749884 search_next_impl(cx, false);
750885}
751886
752−pub fn extend_search_next(cx: &mut Context) {
887+fn extend_search_next(cx: &mut Context) {
753888 search_next_impl(cx, true);
754889}
755890
756−pub fn search_selection(cx: &mut Context) {
891+fn search_selection(cx: &mut Context) {
757892 let (view, doc) = cx.current();
758893 let contents = doc.text().slice(..);
759894 let query = doc.selection(view.id).primary().fragment(contents);
@@ -768,7 +903,7 @@ pub fn search_selection(cx: &mut Context) {
768903
769904//
770905
771−pub fn select_line(cx: &mut Context) {
906+fn select_line(cx: &mut Context) {
772907 let count = cx.count();
773908 let (view, doc) = cx.current();
774909
@@ -783,7 +918,7 @@ pub fn select_line(cx: &mut Context) {
783918
784919 doc.set_selection(view.id, Selection::single(start, end));
785920}
786−pub fn extend_line(cx: &mut Context) {
921+fn extend_line(cx: &mut Context) {
787922 let count = cx.count();
788923 let (view, doc) = cx.current();
789924
@@ -825,7 +960,7 @@ fn delete_selection_impl(reg: &mut Register, doc: &mut Document, view_id: ViewId
825960 doc.apply(&transaction, view_id);
826961}
827962
828−pub fn delete_selection(cx: &mut Context) {
963+fn delete_selection(cx: &mut Context) {
829964 let reg_name = cx.selected_register.name();
830965 let (view, doc, registers) = cx.current_with_registers();
831966 let reg = registers.get_or_insert(reg_name);
@@ -837,7 +972,7 @@ pub fn delete_selection(cx: &mut Context) {
837972 exit_select_mode(cx);
838973}
839974
840−pub fn change_selection(cx: &mut Context) {
975+fn change_selection(cx: &mut Context) {
841976 let reg_name = cx.selected_register.name();
842977 let (view, doc, registers) = cx.current_with_registers();
843978 let reg = registers.get_or_insert(reg_name);
@@ -845,7 +980,7 @@ pub fn change_selection(cx: &mut Context) {
845980 enter_insert_mode(doc);
846981}
847982
848−pub fn collapse_selection(cx: &mut Context) {
983+fn collapse_selection(cx: &mut Context) {
849984 let (view, doc) = cx.current();
850985 let selection = doc
851986 .selection(view.id)
@@ -854,7 +989,7 @@ pub fn collapse_selection(cx: &mut Context) {
854989 doc.set_selection(view.id, selection);
855990}
856991
857−pub fn flip_selections(cx: &mut Context) {
992+fn flip_selections(cx: &mut Context) {
858993 let (view, doc) = cx.current();
859994 let selection = doc
860995 .selection(view.id)
@@ -868,7 +1003,7 @@ fn enter_insert_mode(doc: &mut Document) {
8681003}
8691004
8701005// inserts at the start of each selection
871−pub fn insert_mode(cx: &mut Context) {
1006+fn insert_mode(cx: &mut Context) {
8721007 let (view, doc) = cx.current();
8731008 enter_insert_mode(doc);
8741009
@@ -879,7 +1014,7 @@ pub fn insert_mode(cx: &mut Context) {
8791014}
8801015
8811016// inserts at the end of each selection
882−pub fn append_mode(cx: &mut Context) {
1017+fn append_mode(cx: &mut Context) {
8831018 let (view, doc) = cx.current();
8841019 enter_insert_mode(doc);
8851020 doc.restore_cursor = true;
@@ -913,7 +1048,7 @@ mod cmd {
9131048 use ui::completers::{self, Completer};
9141049
9151050 #[derive(Clone)]
916− pub struct Command {
1051+ pub struct TypableCommand {
9171052 pub name: &'static str,
9181053 pub alias: Option<&'static str>,
9191054 pub doc: &'static str,
@@ -1152,113 +1287,113 @@ mod cmd {
11521287 quit_all_impl(editor, args, event, true)
11531288 }
11541289
1155− pub const COMMAND_LIST: &[Command] = &[
1156− Command {
1290+ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
1291+ TypableCommand {
11571292 name: "quit",
11581293 alias: Some("q"),
11591294 doc: "Close the current view.",
11601295 fun: quit,
11611296 completer: None,
11621297 },
1163− Command {
1298+ TypableCommand {
11641299 name: "quit!",
11651300 alias: Some("q!"),
11661301 doc: "Close the current view.",
11671302 fun: force_quit,
11681303 completer: None,
11691304 },
1170− Command {
1305+ TypableCommand {
11711306 name: "open",
11721307 alias: Some("o"),
11731308 doc: "Open a file from disk into the current view.",
11741309 fun: open,
11751310 completer: Some(completers::filename),
11761311 },
1177− Command {
1312+ TypableCommand {
11781313 name: "write",
11791314 alias: Some("w"),
11801315 doc: "Write changes to disk. Accepts an optional path (:write some/path.txt)",
11811316 fun: write,
11821317 completer: Some(completers::filename),
11831318 },
1184− Command {
1319+ TypableCommand {
11851320 name: "new",
11861321 alias: Some("n"),
11871322 doc: "Create a new scratch buffer.",
11881323 fun: new_file,
11891324 completer: Some(completers::filename),
11901325 },
1191− Command {
1326+ TypableCommand {
11921327 name: "format",
11931328 alias: Some("fmt"),
11941329 doc: "Format the file using a formatter.",
11951330 fun: format,
11961331 completer: None,
11971332 },
1198− Command {
1333+ TypableCommand {
11991334 name: "indent-style",
12001335 alias: None,
12011336 doc: "Set the indentation style for editing. ('t' for tabs or 1-8 for number of spaces.)",
12021337 fun: set_indent_style,
12031338 completer: None,
12041339 },
1205− Command {
1340+ TypableCommand {
12061341 name: "earlier",
12071342 alias: Some("ear"),
12081343 doc: "Jump back to an earlier point in edit history. Accepts a number of steps or a time span.",
12091344 fun: earlier,
12101345 completer: None,
12111346 },
1212− Command {
1347+ TypableCommand {
12131348 name: "later",
12141349 alias: Some("lat"),
12151350 doc: "Jump to a later point in edit history. Accepts a number of steps or a time span.",
12161351 fun: later,
12171352 completer: None,
12181353 },
1219− Command {
1354+ TypableCommand {
12201355 name: "write-quit",
12211356 alias: Some("wq"),
12221357 doc: "Writes changes to disk and closes the current view. Accepts an optional path (:wq some/path.txt)",
12231358 fun: write_quit,
12241359 completer: Some(completers::filename),
12251360 },
1226− Command {
1361+ TypableCommand {
12271362 name: "write-quit!",
12281363 alias: Some("wq!"),
12291364 doc: "Writes changes to disk and closes the current view forcefully. Accepts an optional path (:wq! some/path.txt)",
12301365 fun: force_write_quit,
12311366 completer: Some(completers::filename),
12321367 },
1233− Command {
1368+ TypableCommand {
12341369 name: "write-all",
12351370 alias: Some("wa"),
12361371 doc: "Writes changes from all views to disk.",
12371372 fun: write_all,
12381373 completer: None,
12391374 },
1240− Command {
1375+ TypableCommand {
12411376 name: "write-quit-all",
12421377 alias: Some("wqa"),
12431378 doc: "Writes changes from all views to disk and close all views.",
12441379 fun: write_all_quit,
12451380 completer: None,
12461381 },
1247− Command {
1382+ TypableCommand {
12481383 name: "write-quit-all!",
12491384 alias: Some("wqa!"),
12501385 doc: "Writes changes from all views to disk and close all views forcefully (ignoring unsaved changes).",
12511386 fun: force_write_all_quit,
12521387 completer: None,
12531388 },
1254− Command {
1389+ TypableCommand {
12551390 name: "quit-all",
12561391 alias: Some("qa"),
12571392 doc: "Close all views.",
12581393 fun: quit_all,
12591394 completer: None,
12601395 },
1261− Command {
1396+ TypableCommand {
12621397 name: "quit-all!",
12631398 alias: Some("qa!"),
12641399 doc: "Close all views forcefully (ignoring unsaved changes).",
@@ -1268,10 +1403,10 @@ mod cmd {
12681403
12691404 ];
12701405
1271− pub static COMMANDS: Lazy<HashMap<&'static str, &'static Command>> = Lazy::new(|| {
1406+ pub static COMMANDS: Lazy<HashMap<&'static str, &'static TypableCommand>> = Lazy::new(|| {
12721407 let mut map = HashMap::new();
12731408
1274− for cmd in COMMAND_LIST {
1409+ for cmd in TYPABLE_COMMAND_LIST {
12751410 map.insert(cmd.name, cmd);
12761411 if let Some(alias) = cmd.alias {
12771412 map.insert(alias, cmd);
@@ -1282,7 +1417,7 @@ mod cmd {
12821417 });
12831418}
12841419
1285−pub fn command_mode(cx: &mut Context) {
1420+fn command_mode(cx: &mut Context) {
12861421 // TODO: completion items should have a info section that would get displayed in
12871422 // a popup above the prompt when items are tabbed over
12881423
@@ -1297,7 +1432,7 @@ pub fn command_mode(cx: &mut Context) {
12971432 if parts.len() <= 1 {
12981433 use std::{borrow::Cow, ops::Range};
12991434 let end = 0..;
1300− cmd::COMMAND_LIST
1435+ cmd::TYPABLE_COMMAND_LIST
13011436 .iter()
13021437 .filter(|command| command.name.contains(input))
13031438 .map(|command| (end.clone(), Cow::Borrowed(command.name)))
@@ -1305,7 +1440,7 @@ pub fn command_mode(cx: &mut Context) {
13051440 } else {
13061441 let part = parts.last().unwrap();
13071442
1308− if let Some(cmd::Command {
1443+ if let Some(cmd::TypableCommand {
13091444 completer: Some(completer),
13101445 ..
13111446 }) = cmd::COMMANDS.get(parts[0])
@@ -1346,7 +1481,7 @@ pub fn command_mode(cx: &mut Context) {
13461481 prompt.doc_fn = Box::new(|input: &str| {
13471482 let part = input.split(' ').next().unwrap_or_default();
13481483
1349− if let Some(cmd::Command { doc, .. }) = cmd::COMMANDS.get(part) {
1484+ if let Some(cmd::TypableCommand { doc, .. }) = cmd::COMMANDS.get(part) {
13501485 return Some(doc);
13511486 }
13521487
@@ -1356,13 +1491,13 @@ pub fn command_mode(cx: &mut Context) {
13561491 cx.push_layer(Box::new(prompt));
13571492}
13581493
1359−pub fn file_picker(cx: &mut Context) {
1494+fn file_picker(cx: &mut Context) {
13601495 let root = find_root(None).unwrap_or_else(|| PathBuf::from("./"));
13611496 let picker = ui::file_picker(root);
13621497 cx.push_layer(Box::new(picker));
13631498}
13641499
1365−pub fn buffer_picker(cx: &mut Context) {
1500+fn buffer_picker(cx: &mut Context) {
13661501 use std::path::{Path, PathBuf};
13671502 let current = cx.editor.view().doc;
13681503
@@ -1393,7 +1528,7 @@ pub fn buffer_picker(cx: &mut Context) {
13931528 cx.push_layer(Box::new(picker));
13941529}
13951530
1396−pub fn symbol_picker(cx: &mut Context) {
1531+fn symbol_picker(cx: &mut Context) {
13971532 fn nested_to_flat(
13981533 list: &mut Vec<lsp::SymbolInformation>,
13991534 file: &lsp::TextDocumentIdentifier,
@@ -1464,14 +1599,14 @@ pub fn symbol_picker(cx: &mut Context) {
14641599}
14651600
14661601// I inserts at the first nonwhitespace character of each line with a selection
1467−pub fn prepend_to_line(cx: &mut Context) {
1602+fn prepend_to_line(cx: &mut Context) {
14681603 move_first_nonwhitespace(cx);
14691604 let doc = cx.doc();
14701605 enter_insert_mode(doc);
14711606}
14721607
14731608// A inserts at the end of each line with a selection
1474−pub fn append_to_line(cx: &mut Context) {
1609+fn append_to_line(cx: &mut Context) {
14751610 let (view, doc) = cx.current();
14761611 enter_insert_mode(doc);
14771612
@@ -1550,16 +1685,16 @@ fn open(cx: &mut Context, open: Open) {
15501685}
15511686
15521687// o inserts a new line after each line with a selection
1553−pub fn open_below(cx: &mut Context) {
1688+fn open_below(cx: &mut Context) {
15541689 open(cx, Open::Below)
15551690}
15561691
15571692// O inserts a new line before each line with a selection
1558−pub fn open_above(cx: &mut Context) {
1693+fn open_above(cx: &mut Context) {
15591694 open(cx, Open::Above)
15601695}
15611696
1562−pub fn normal_mode(cx: &mut Context) {
1697+fn normal_mode(cx: &mut Context) {
15631698 let (view, doc) = cx.current();
15641699
15651700 doc.mode = Mode::Normal;
@@ -1597,7 +1732,7 @@ fn switch_to_last_accessed_file(cx: &mut Context) {
15971732 }
15981733}
15991734
1600−pub fn goto_mode(cx: &mut Context) {
1735+fn goto_mode(cx: &mut Context) {
16011736 if let Some(count) = cx.count {
16021737 push_jump(cx.editor);
16031738
@@ -1658,11 +1793,11 @@ pub fn goto_mode(cx: &mut Context) {
16581793 })
16591794}
16601795
1661−pub fn select_mode(cx: &mut Context) {
1796+fn select_mode(cx: &mut Context) {
16621797 cx.doc().mode = Mode::Select;
16631798}
16641799
1665−pub fn exit_select_mode(cx: &mut Context) {
1800+fn exit_select_mode(cx: &mut Context) {
16661801 cx.doc().mode = Mode::Normal;
16671802}
16681803
@@ -1722,7 +1857,7 @@ fn goto_impl(
17221857 }
17231858}
17241859
1725−pub fn goto_definition(cx: &mut Context) {
1860+fn goto_definition(cx: &mut Context) {
17261861 let (view, doc) = cx.current();
17271862 let language_server = match doc.language_server() {
17281863 Some(language_server) => language_server,
@@ -1759,7 +1894,7 @@ pub fn goto_definition(cx: &mut Context) {
17591894 );
17601895}
17611896
1762−pub fn goto_type_definition(cx: &mut Context) {
1897+fn goto_type_definition(cx: &mut Context) {
17631898 let (view, doc) = cx.current();
17641899 let language_server = match doc.language_server() {
17651900 Some(language_server) => language_server,
@@ -1796,7 +1931,7 @@ pub fn goto_type_definition(cx: &mut Context) {
17961931 );
17971932}
17981933
1799−pub fn goto_implementation(cx: &mut Context) {
1934+fn goto_implementation(cx: &mut Context) {
18001935 let (view, doc) = cx.current();
18011936 let language_server = match doc.language_server() {
18021937 Some(language_server) => language_server,
@@ -1833,7 +1968,7 @@ pub fn goto_implementation(cx: &mut Context) {
18331968 );
18341969}
18351970
1836−pub fn goto_reference(cx: &mut Context) {
1971+fn goto_reference(cx: &mut Context) {
18371972 let (view, doc) = cx.current();
18381973 let language_server = match doc.language_server() {
18391974 Some(language_server) => language_server,
@@ -1871,7 +2006,7 @@ fn goto_pos(editor: &mut Editor, pos: usize) {
18712006 align_view(doc, view, Align::Center);
18722007}
18732008
1874−pub fn goto_first_diag(cx: &mut Context) {
2009+fn goto_first_diag(cx: &mut Context) {
18752010 let editor = &mut cx.editor;
18762011 let (view, doc) = editor.current();
18772012
@@ -1885,7 +2020,7 @@ pub fn goto_first_diag(cx: &mut Context) {
18852020 goto_pos(editor, diag);
18862021}
18872022
1888−pub fn goto_last_diag(cx: &mut Context) {
2023+fn goto_last_diag(cx: &mut Context) {
18892024 let editor = &mut cx.editor;
18902025 let (view, doc) = editor.current();
18912026
@@ -1899,7 +2034,7 @@ pub fn goto_last_diag(cx: &mut Context) {
18992034 goto_pos(editor, diag);
19002035}
19012036
1902−pub fn goto_next_diag(cx: &mut Context) {
2037+fn goto_next_diag(cx: &mut Context) {
19032038 let editor = &mut cx.editor;
19042039 let (view, doc) = editor.current();
19052040
@@ -1920,7 +2055,7 @@ pub fn goto_next_diag(cx: &mut Context) {
19202055 goto_pos(editor, diag);
19212056}
19222057
1923−pub fn goto_prev_diag(cx: &mut Context) {
2058+fn goto_prev_diag(cx: &mut Context) {
19242059 let editor = &mut cx.editor;
19252060 let (view, doc) = editor.current();
19262061
@@ -1942,7 +2077,7 @@ pub fn goto_prev_diag(cx: &mut Context) {
19422077 goto_pos(editor, diag);
19432078}
19442079
1945−pub fn signature_help(cx: &mut Context) {
2080+fn signature_help(cx: &mut Context) {
19462081 let (view, doc) = cx.current();
19472082
19482083 let language_server = match doc.language_server() {
@@ -2218,19 +2353,19 @@ pub mod insert {
22182353// TODO: each command could simply return a Option<transaction>, then the higher level handles
22192354// storing it?
22202355
2221−pub fn undo(cx: &mut Context) {
2356+fn undo(cx: &mut Context) {
22222357 let view_id = cx.view().id;
22232358 cx.doc().undo(view_id);
22242359}
22252360
2226−pub fn redo(cx: &mut Context) {
2361+fn redo(cx: &mut Context) {
22272362 let view_id = cx.view().id;
22282363 cx.doc().redo(view_id);
22292364}
22302365
22312366// Yank / Paste
22322367
2233−pub fn yank(cx: &mut Context) {
2368+fn yank(cx: &mut Context) {
22342369 // TODO: should selections be made end inclusive?
22352370 let (view, doc) = cx.current();
22362371 let values: Vec<String> = doc
@@ -2295,7 +2430,7 @@ fn paste_impl(
22952430 Some(transaction)
22962431}
22972432
2298−pub fn replace_with_yanked(cx: &mut Context) {
2433+fn replace_with_yanked(cx: &mut Context) {
22992434 let reg_name = cx.selected_register.name();
23002435 let (view, doc, registers) = cx.current_with_registers();
23012436
@@ -2324,7 +2459,7 @@ pub fn replace_with_yanked(cx: &mut Context) {
23242459// replace => replace
23252460// default insert
23262461
2327−pub fn paste_after(cx: &mut Context) {
2462+fn paste_after(cx: &mut Context) {
23282463 let reg_name = cx.selected_register.name();
23292464 let (view, doc, registers) = cx.current_with_registers();
23302465
@@ -2337,7 +2472,7 @@ pub fn paste_after(cx: &mut Context) {
23372472 }
23382473}
23392474
2340−pub fn paste_before(cx: &mut Context) {
2475+fn paste_before(cx: &mut Context) {
23412476 let reg_name = cx.selected_register.name();
23422477 let (view, doc, registers) = cx.current_with_registers();
23432478
@@ -2367,7 +2502,7 @@ fn get_lines(doc: &Document, view_id: ViewId) -> Vec<usize> {
23672502 lines
23682503}
23692504
2370−pub fn indent(cx: &mut Context) {
2505+fn indent(cx: &mut Context) {
23712506 let count = cx.count();
23722507 let (view, doc) = cx.current();
23732508 let lines = get_lines(doc, view.id);
@@ -2386,7 +2521,7 @@ pub fn indent(cx: &mut Context) {
23862521 doc.append_changes_to_history(view.id);
23872522}
23882523
2389−pub fn unindent(cx: &mut Context) {
2524+fn unindent(cx: &mut Context) {
23902525 let count = cx.count();
23912526 let (view, doc) = cx.current();
23922527 let lines = get_lines(doc, view.id);
@@ -2426,7 +2561,7 @@ pub fn unindent(cx: &mut Context) {
24262561 doc.append_changes_to_history(view.id);
24272562}
24282563
2429−pub fn format_selections(cx: &mut Context) {
2564+fn format_selections(cx: &mut Context) {
24302565 let (view, doc) = cx.current();
24312566
24322567 // via lsp if available
@@ -2472,7 +2607,7 @@ pub fn format_selections(cx: &mut Context) {
24722607 doc.append_changes_to_history(view.id);
24732608}
24742609
2475−pub fn join_selections(cx: &mut Context) {
2610+fn join_selections(cx: &mut Context) {
24762611 use movement::skip_while;
24772612 let (view, doc) = cx.current();
24782613 let text = doc.text();
@@ -2516,7 +2651,7 @@ pub fn join_selections(cx: &mut Context) {
25162651 doc.append_changes_to_history(view.id);
25172652}
25182653
2519−pub fn keep_selections(cx: &mut Context) {
2654+fn keep_selections(cx: &mut Context) {
25202655 // keep selections matching regex
25212656 let prompt = ui::regex_prompt(cx, "keep:".to_string(), move |view, doc, _, regex| {
25222657 let text = doc.text().slice(..);
@@ -2529,7 +2664,7 @@ pub fn keep_selections(cx: &mut Context) {
25292664 cx.push_layer(Box::new(prompt));
25302665}
25312666
2532−pub fn keep_primary_selection(cx: &mut Context) {
2667+fn keep_primary_selection(cx: &mut Context) {
25332668 let (view, doc) = cx.current();
25342669
25352670 let range = doc.selection(view.id).primary();
@@ -2539,14 +2674,14 @@ pub fn keep_primary_selection(cx: &mut Context) {
25392674
25402675//
25412676
2542−pub fn save(cx: &mut Context) {
2677+fn save(cx: &mut Context) {
25432678 // Spawns an async task to actually do the saving. This way we prevent blocking.
25442679
25452680 // TODO: handle save errors somehow?
25462681 tokio::spawn(cx.doc().save());
25472682}
25482683
2549−pub fn completion(cx: &mut Context) {
2684+fn completion(cx: &mut Context) {
25502685 // trigger on trigger char, or if user calls it
25512686 // (or on word char typing??)
25522687 // after it's triggered, if response marked is_incomplete, update on every subsequent keypress
@@ -2636,7 +2771,7 @@ pub fn completion(cx: &mut Context) {
26362771 //}
26372772}
26382773
2639−pub fn hover(cx: &mut Context) {
2774+fn hover(cx: &mut Context) {
26402775 let (view, doc) = cx.current();
26412776
26422777 let language_server = match doc.language_server() {
@@ -2686,7 +2821,7 @@ pub fn hover(cx: &mut Context) {
26862821}
26872822
26882823// comments
2689−pub fn toggle_comments(cx: &mut Context) {
2824+fn toggle_comments(cx: &mut Context) {
26902825 let (view, doc) = cx.current();
26912826 let transaction = comment::toggle_line_comments(doc.text(), doc.selection(view.id));
26922827
@@ -2696,7 +2831,7 @@ pub fn toggle_comments(cx: &mut Context) {
26962831
26972832// tree sitter node selection
26982833
2699−pub fn expand_selection(cx: &mut Context) {
2834+fn expand_selection(cx: &mut Context) {
27002835 let (view, doc) = cx.current();
27012836
27022837 if let Some(syntax) = doc.syntax() {
@@ -2706,7 +2841,7 @@ pub fn expand_selection(cx: &mut Context) {
27062841 }
27072842}
27082843
2709−pub fn match_brackets(cx: &mut Context) {
2844+fn match_brackets(cx: &mut Context) {
27102845 let (view, doc) = cx.current();
27112846
27122847 if let Some(syntax) = doc.syntax() {
@@ -2720,7 +2855,7 @@ pub fn match_brackets(cx: &mut Context) {
27202855
27212856//
27222857
2723−pub fn jump_forward(cx: &mut Context) {
2858+fn jump_forward(cx: &mut Context) {
27242859 let count = cx.count();
27252860 let (view, doc) = cx.current();
27262861
@@ -2734,7 +2869,7 @@ pub fn jump_forward(cx: &mut Context) {
27342869 };
27352870}
27362871
2737−pub fn jump_backward(cx: &mut Context) {
2872+fn jump_backward(cx: &mut Context) {
27382873 let count = cx.count();
27392874 let (view, doc) = cx.current();
27402875
@@ -2752,7 +2887,7 @@ pub fn jump_backward(cx: &mut Context) {
27522887 };
27532888}
27542889
2755−pub fn window_mode(cx: &mut Context) {
2890+fn window_mode(cx: &mut Context) {
27562891 cx.on_next_key(move |cx, event| {
27572892 if let KeyEvent {
27582893 code: KeyCode::Char(ch),
@@ -2770,12 +2905,14 @@ pub fn window_mode(cx: &mut Context) {
27702905 })
27712906}
27722907
2773−pub fn rotate_view(cx: &mut Context) {
2908+fn rotate_view(cx: &mut Context) {
27742909 cx.editor.focus_next()
27752910}
27762911
27772912// split helper, clear it later
27782913use helix_view::editor::Action;
2914+
2915+use self::cmd::TypableCommand;
27792916fn split(cx: &mut Context, action: Action) {
27802917 use helix_view::editor::Action;
27812918 let (view, doc) = cx.current();
@@ -2791,21 +2928,21 @@ fn split(cx: &mut Context, action: Action) {
27912928 doc.set_selection(view.id, selection);
27922929}
27932930
2794−pub fn hsplit(cx: &mut Context) {
2931+fn hsplit(cx: &mut Context) {
27952932 split(cx, Action::HorizontalSplit);
27962933}
27972934
2798−pub fn vsplit(cx: &mut Context) {
2935+fn vsplit(cx: &mut Context) {
27992936 split(cx, Action::VerticalSplit);
28002937}
28012938
2802−pub fn wclose(cx: &mut Context) {
2939+fn wclose(cx: &mut Context) {
28032940 let view_id = cx.view().id;
28042941 // close current split
28052942 cx.editor.close(view_id, /* close_buffer */ false);
28062943}
28072944
2808−pub fn select_register(cx: &mut Context) {
2945+fn select_register(cx: &mut Context) {
28092946 cx.on_next_key(move |cx, event| {
28102947 if let KeyEvent {
28112948 code: KeyCode::Char(ch),
@@ -2817,7 +2954,7 @@ pub fn select_register(cx: &mut Context) {
28172954 })
28182955}
28192956
2820−pub fn space_mode(cx: &mut Context) {
2957+fn space_mode(cx: &mut Context) {
28212958 cx.on_next_key(move |cx, event| {
28222959 if let KeyEvent {
28232960 code: KeyCode::Char(ch),
@@ -2839,7 +2976,7 @@ pub fn space_mode(cx: &mut Context) {
28392976 })
28402977}
28412978
2842−pub fn view_mode(cx: &mut Context) {
2979+fn view_mode(cx: &mut Context) {
28432980 cx.on_next_key(move |cx, event| {
28442981 if let KeyEvent {
28452982 code: KeyCode::Char(ch),
@@ -2882,7 +3019,7 @@ pub fn view_mode(cx: &mut Context) {
28823019 })
28833020}
28843021
2885−pub fn left_bracket_mode(cx: &mut Context) {
3022+fn left_bracket_mode(cx: &mut Context) {
28863023 cx.on_next_key(move |cx, event| {
28873024 if let KeyEvent {
28883025 code: KeyCode::Char(ch),
@@ -2898,7 +3035,7 @@ pub fn left_bracket_mode(cx: &mut Context) {
28983035 })
28993036}
29003037
2901−pub fn right_bracket_mode(cx: &mut Context) {
3038+fn right_bracket_mode(cx: &mut Context) {
29023039 cx.on_next_key(move |cx, event| {
29033040 if let KeyEvent {
29043041 code: KeyCode::Char(ch),
@@ -2913,3 +3050,29 @@ pub fn right_bracket_mode(cx: &mut Context) {
29133050 }
29143051 })
29153052}
3053+
3054+impl fmt::Display for Command {
3055+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3056+ let Command(name, _) = self;
3057+ f.write_str(name)
3058+ }
3059+}
3060+
3061+impl std::str::FromStr for Command {
3062+ type Err = anyhow::Error;
3063+
3064+ fn from_str(s: &str) -> Result<Self, Self::Err> {
3065+ Command::COMMAND_LIST
3066+ .iter()
3067+ .copied()
3068+ .find(|cmd| cmd.0 == s)
3069+ .ok_or_else(|| anyhow!("No command named '{}'", s))
3070+ }
3071+}
3072+
3073+impl fmt::Debug for Command {
3074+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3075+ let Command(name, _) = self;
3076+ f.debug_tuple("Command").field(name).finish()
3077+ }
3078+}
helix-term/src/config.rsnew file+33
@@ -0,0 +1,33 @@
1+use anyhow::{Error, Result};
2+use std::{collections::HashMap, str::FromStr};
3+
4+use serde::{de::Error as SerdeError, Deserialize, Serialize};
5+
6+use crate::keymap::{parse_keymaps, Keymaps};
7+
8+#[derive(Default)]
9+pub struct Config {
10+ pub keymaps: Keymaps,
11+}
12+
13+#[derive(Serialize, Deserialize)]
14+struct TomlConfig {
15+ keys: Option<HashMap<String, HashMap<String, String>>>,
16+}
17+
18+impl<'de> Deserialize<'de> for Config {
19+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
20+ where
21+ D: serde::Deserializer<'de>,
22+ {
23+ let config = TomlConfig::deserialize(deserializer)?;
24+ Ok(Self {
25+ keymaps: config
26+ .keys
27+ .map(|r| parse_keymaps(&r))
28+ .transpose()
29+ .map_err(|e| D::Error::custom(format!("Error deserializing keymap: {}", e)))?
30+ .unwrap_or_else(Keymaps::default),
31+ })
32+ }
33+}
helix-term/src/keymap.rs+500−188
@@ -1,7 +1,14 @@
1−use crate::commands::{self, Command};
1+use crate::commands;
2+pub use crate::commands::Command;
3+use anyhow::{anyhow, Error, Result};
24use helix_core::hashmap;
35use helix_view::document::Mode;
4−use std::collections::HashMap;
6+use std::{
7+ collections::HashMap,
8+ fmt::Display,
9+ ops::{Deref, DerefMut},
10+ str::FromStr,
11+};
512
613// Kakoune-inspired:
714// mode = {
@@ -95,8 +102,10 @@ use std::collections::HashMap;
95102// #[cfg(feature = "term")]
96103pub use crossterm::event::{KeyCode, KeyEvent, KeyModifiers};
97104
98−pub type Keymap = HashMap<KeyEvent, Command>;
99−pub type Keymaps = HashMap<Mode, Keymap>;
105+#[derive(Clone, Debug)]
106+pub struct Keymap(pub HashMap<KeyEvent, Command>);
107+#[derive(Clone, Debug)]
108+pub struct Keymaps(pub HashMap<Mode, Keymap>);
100109
101110#[macro_export]
102111macro_rules! key {
@@ -132,188 +141,491 @@ macro_rules! alt {
132141 };
133142}
134143
135−pub fn default() -> Keymaps {
136− let normal = hashmap!(
137− key!('h') => commands::move_char_left as Command,
138− key!('j') => commands::move_line_down,
139− key!('k') => commands::move_line_up,
140− key!('l') => commands::move_char_right,
141−
142− key!(Left) => commands::move_char_left,
143− key!(Down) => commands::move_line_down,
144− key!(Up) => commands::move_line_up,
145− key!(Right) => commands::move_char_right,
146−
147− key!('t') => commands::find_till_char,
148− key!('f') => commands::find_next_char,
149− key!('T') => commands::till_prev_char,
150− key!('F') => commands::find_prev_char,
151− // and matching set for select mode (extend)
152− //
153− key!('r') => commands::replace,
154− key!('R') => commands::replace_with_yanked,
155−
156− key!(Home) => commands::move_line_start,
157− key!(End) => commands::move_line_end,
158−
159− key!('w') => commands::move_next_word_start,
160− key!('b') => commands::move_prev_word_start,
161− key!('e') => commands::move_next_word_end,
162−
163− key!('v') => commands::select_mode,
164− key!('g') => commands::goto_mode,
165− key!(':') => commands::command_mode,
166−
167− key!('i') => commands::insert_mode,
168− key!('I') => commands::prepend_to_line,
169− key!('a') => commands::append_mode,
170− key!('A') => commands::append_to_line,
171− key!('o') => commands::open_below,
172− key!('O') => commands::open_above,
173− // [<space> ]<space> equivalents too (add blank new line, no edit)
174−
175−
176− key!('d') => commands::delete_selection,
177− // TODO: also delete without yanking
178− key!('c') => commands::change_selection,
179− // TODO: also change delete without yanking
180−
181− // key!('r') => commands::replace_with_char,
182−
183− key!('s') => commands::select_regex,
184− alt!('s') => commands::split_selection_on_newline,
185− key!('S') => commands::split_selection,
186− key!(';') => commands::collapse_selection,
187− alt!(';') => commands::flip_selections,
188− key!('%') => commands::select_all,
189− key!('x') => commands::select_line,
190− key!('X') => commands::extend_line,
191− // or select mode X?
192− // extend_to_whole_line, crop_to_whole_line
193−
194−
195− key!('m') => commands::match_brackets,
196− // TODO: refactor into
197− // key!('m') => commands::select_to_matching,
198− // key!('M') => commands::back_select_to_matching,
199− // select mode extend equivalents
200−
201− // key!('.') => commands::repeat_insert,
202− // repeat_select
203−
204− // TODO: figure out what key to use
205− // key!('[') => commands::expand_selection, ??
206− key!('[') => commands::left_bracket_mode,
207− key!(']') => commands::right_bracket_mode,
208−
209− key!('/') => commands::search,
210− // ? for search_reverse
211− key!('n') => commands::search_next,
212− key!('N') => commands::extend_search_next,
213− // N for search_prev
214− key!('*') => commands::search_selection,
215−
216− key!('u') => commands::undo,
217− key!('U') => commands::redo,
218−
219− key!('y') => commands::yank,
220− // yank_all
221− key!('p') => commands::paste_after,
222− // paste_all
223− key!('P') => commands::paste_before,
224−
225− key!('>') => commands::indent,
226− key!('<') => commands::unindent,
227− key!('=') => commands::format_selections,
228− key!('J') => commands::join_selections,
229− // TODO: conflicts hover/doc
230− key!('K') => commands::keep_selections,
231− // TODO: and another method for inverse
232−
233− // TODO: clashes with space mode
234− key!(' ') => commands::keep_primary_selection,
235−
236− // key!('q') => commands::record_macro,
237− // key!('Q') => commands::replay_macro,
238−
239− // ~ / apostrophe => change case
240− // & align selections
241− // _ trim selections
242−
243− // C / altC = copy (repeat) selections on prev/next lines
244−
245− key!(Esc) => commands::normal_mode,
246− key!(PageUp) => commands::page_up,
247− key!(PageDown) => commands::page_down,
248− ctrl!('b') => commands::page_up,
249− ctrl!('f') => commands::page_down,
250− ctrl!('u') => commands::half_page_up,
251− ctrl!('d') => commands::half_page_down,
252−
253− ctrl!('w') => commands::window_mode,
254−
255− // move under <space>c
256− ctrl!('c') => commands::toggle_comments,
257− key!('K') => commands::hover,
258−
259− // z family for save/restore/combine from/to sels from register
260−
261− // supposedly ctrl!('i') but did not work
262− key!(Tab) => commands::jump_forward,
263− ctrl!('o') => commands::jump_backward,
264− // ctrl!('s') => commands::save_selection,
265−
266− key!(' ') => commands::space_mode,
267− key!('z') => commands::view_mode,
268−
269− key!('"') => commands::select_register,
270− );
271− // TODO: decide whether we want normal mode to also be select mode (kakoune-like), or whether
272− // we keep this separate select mode. More keys can fit into normal mode then, but it's weird
273− // because some selection operations can now be done from normal mode, some from select mode.
274− let mut select = normal.clone();
275− select.extend(
276− hashmap!(
277− key!('h') => commands::extend_char_left as Command,
278− key!('j') => commands::extend_line_down,
279− key!('k') => commands::extend_line_up,
280− key!('l') => commands::extend_char_right,
281−
282− key!(Left) => commands::extend_char_left,
283− key!(Down) => commands::extend_line_down,
284− key!(Up) => commands::extend_line_up,
285− key!(Right) => commands::extend_char_right,
286−
287− key!('w') => commands::extend_next_word_start,
288− key!('b') => commands::extend_prev_word_start,
289− key!('e') => commands::extend_next_word_end,
290−
291− key!('t') => commands::extend_till_char,
292− key!('f') => commands::extend_next_char,
293−
294− key!('T') => commands::extend_till_prev_char,
295− key!('F') => commands::extend_prev_char,
296− key!(Home) => commands::extend_line_start,
297− key!(End) => commands::extend_line_end,
298− key!(Esc) => commands::exit_select_mode,
299− )
300− .into_iter(),
301− );
302−
303− hashmap!(
304− // as long as you cast the first item, rust is able to infer the other cases
305− // TODO: select could be normal mode with some bindings merged over
306− Mode::Normal => normal,
307− Mode::Select => select,
308− Mode::Insert => hashmap!(
309− key!(Esc) => commands::normal_mode as Command,
310− key!(Backspace) => commands::insert::delete_char_backward,
311− key!(Delete) => commands::insert::delete_char_forward,
312− key!(Enter) => commands::insert::insert_newline,
313− key!(Tab) => commands::insert::insert_tab,
314−
315− ctrl!('x') => commands::completion,
316− ctrl!('w') => commands::insert::delete_word_backward,
317− ),
318− )
144+impl Default for Keymaps {
145+ fn default() -> Self {
146+ let normal = Keymap(hashmap!(
147+ key!('h') => Command::move_char_left,
148+ key!('j') => Command::move_line_down,
149+ key!('k') => Command::move_line_up,
150+ key!('l') => Command::move_char_right,
151+
152+ key!(Left) => Command::move_char_left,
153+ key!(Down) => Command::move_line_down,
154+ key!(Up) => Command::move_line_up,
155+ key!(Right) => Command::move_char_right,
156+
157+ key!('t') => Command::find_till_char,
158+ key!('f') => Command::find_next_char,
159+ key!('T') => Command::till_prev_char,
160+ key!('F') => Command::find_prev_char,
161+ // and matching set for select mode (extend)
162+ //
163+ key!('r') => Command::replace,
164+ key!('R') => Command::replace_with_yanked,
165+
166+ key!(Home) => Command::move_line_start,
167+ key!(End) => Command::move_line_end,
168+
169+ key!('w') => Command::move_next_word_start,
170+ key!('b') => Command::move_prev_word_start,
171+ key!('e') => Command::move_next_word_end,
172+
173+ key!('v') => Command::select_mode,
174+ key!('g') => Command::goto_mode,
175+ key!(':') => Command::command_mode,
176+
177+ key!('i') => Command::insert_mode,
178+ key!('I') => Command::prepend_to_line,
179+ key!('a') => Command::append_mode,
180+ key!('A') => Command::append_to_line,
181+ key!('o') => Command::open_below,
182+ key!('O') => Command::open_above,
183+ // [<space> ]<space> equivalents too (add blank new line, no edit)
184+
185+
186+ key!('d') => Command::delete_selection,
187+ // TODO: also delete without yanking
188+ key!('c') => Command::change_selection,
189+ // TODO: also change delete without yanking
190+
191+ // key!('r') => Command::replace_with_char,
192+
193+ key!('s') => Command::select_regex,
194+ alt!('s') => Command::split_selection_on_newline,
195+ key!('S') => Command::split_selection,
196+ key!(';') => Command::collapse_selection,
197+ alt!(';') => Command::flip_selections,
198+ key!('%') => Command::select_all,
199+ key!('x') => Command::select_line,
200+ key!('X') => Command::extend_line,
201+ // or select mode X?
202+ // extend_to_whole_line, crop_to_whole_line
203+
204+
205+ key!('m') => Command::match_brackets,
206+ // TODO: refactor into
207+ // key!('m') => commands::select_to_matching,
208+ // key!('M') => commands::back_select_to_matching,
209+ // select mode extend equivalents
210+
211+ // key!('.') => commands::repeat_insert,
212+ // repeat_select
213+
214+ // TODO: figure out what key to use
215+ // key!('[') => Command::expand_selection, ??
216+ key!('[') => Command::left_bracket_mode,
217+ key!(']') => Command::right_bracket_mode,
218+
219+ key!('/') => Command::search,
220+ // ? for search_reverse
221+ key!('n') => Command::search_next,
222+ key!('N') => Command::extend_search_next,
223+ // N for search_prev
224+ key!('*') => Command::search_selection,
225+
226+ key!('u') => Command::undo,
227+ key!('U') => Command::redo,
228+
229+ key!('y') => Command::yank,
230+ // yank_all
231+ key!('p') => Command::paste_after,
232+ // paste_all
233+ key!('P') => Command::paste_before,
234+
235+ key!('>') => Command::indent,
236+ key!('<') => Command::unindent,
237+ key!('=') => Command::format_selections,
238+ key!('J') => Command::join_selections,
239+ // TODO: conflicts hover/doc
240+ key!('K') => Command::keep_selections,
241+ // TODO: and another method for inverse
242+
243+ // TODO: clashes with space mode
244+ key!(' ') => Command::keep_primary_selection,
245+
246+ // key!('q') => Command::record_macro,
247+ // key!('Q') => Command::replay_macro,
248+
249+ // ~ / apostrophe => change case
250+ // & align selections
251+ // _ trim selections
252+
253+ // C / altC = copy (repeat) selections on prev/next lines
254+
255+ key!(Esc) => Command::normal_mode,
256+ key!(PageUp) => Command::page_up,
257+ key!(PageDown) => Command::page_down,
258+ ctrl!('b') => Command::page_up,
259+ ctrl!('f') => Command::page_down,
260+ ctrl!('u') => Command::half_page_up,
261+ ctrl!('d') => Command::half_page_down,
262+
263+ ctrl!('w') => Command::window_mode,
264+
265+ // move under <space>c
266+ ctrl!('c') => Command::toggle_comments,
267+ key!('K') => Command::hover,
268+
269+ // z family for save/restore/combine from/to sels from register
270+
271+ // supposedly ctrl!('i') but did not work
272+ key!(Tab) => Command::jump_forward,
273+ ctrl!('o') => Command::jump_backward,
274+ // ctrl!('s') => Command::save_selection,
275+
276+ key!(' ') => Command::space_mode,
277+ key!('z') => Command::view_mode,
278+
279+ key!('"') => Command::select_register,
280+ ));
281+ // TODO: decide whether we want normal mode to also be select mode (kakoune-like), or whether
282+ // we keep this separate select mode. More keys can fit into normal mode then, but it's weird
283+ // because some selection operations can now be done from normal mode, some from select mode.
284+ let mut select = normal.clone();
285+ select.0.extend(
286+ hashmap!(
287+ key!('h') => Command::extend_char_left,
288+ key!('j') => Command::extend_line_down,
289+ key!('k') => Command::extend_line_up,
290+ key!('l') => Command::extend_char_right,
291+
292+ key!(Left) => Command::extend_char_left,
293+ key!(Down) => Command::extend_line_down,
294+ key!(Up) => Command::extend_line_up,
295+ key!(Right) => Command::extend_char_right,
296+
297+ key!('w') => Command::extend_next_word_start,
298+ key!('b') => Command::extend_prev_word_start,
299+ key!('e') => Command::extend_next_word_end,
300+
301+ key!('t') => Command::extend_till_char,
302+ key!('f') => Command::extend_next_char,
303+
304+ key!('T') => Command::extend_till_prev_char,
305+ key!('F') => Command::extend_prev_char,
306+ key!(Home) => Command::extend_line_start,
307+ key!(End) => Command::extend_line_end,
308+ key!(Esc) => Command::exit_select_mode,
309+ )
310+ .into_iter(),
311+ );
312+
313+ Keymaps(hashmap!(
314+ // as long as you cast the first item, rust is able to infer the other cases
315+ // TODO: select could be normal mode with some bindings merged over
316+ Mode::Normal => normal,
317+ Mode::Select => select,
318+ Mode::Insert => Keymap(hashmap!(
319+ key!(Esc) => Command::normal_mode as Command,
320+ key!(Backspace) => Command::delete_char_backward,
321+ key!(Delete) => Command::delete_char_forward,
322+ key!(Enter) => Command::insert_newline,
323+ key!(Tab) => Command::insert_tab,
324+ ctrl!('x') => Command::completion,
325+ ctrl!('w') => Command::delete_word_backward,
326+ )),
327+ ))
328+ }
329+}
330+
331+// Newtype wrapper over keys to allow toml serialization/parsing
332+#[derive(Debug, PartialEq, PartialOrd, Clone, Copy, Hash)]
333+pub struct RepresentableKeyEvent(pub KeyEvent);
334+impl Display for RepresentableKeyEvent {
335+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
336+ let Self(key) = self;
337+ f.write_fmt(format_args!(
338+ "{}{}{}",
339+ if key.modifiers.contains(KeyModifiers::SHIFT) {
340+ "S-"
341+ } else {
342+ ""
343+ },
344+ if key.modifiers.contains(KeyModifiers::ALT) {
345+ "A-"
346+ } else {
347+ ""
348+ },
349+ if key.modifiers.contains(KeyModifiers::CONTROL) {
350+ "C-"
351+ } else {
352+ ""
353+ },
354+ ))?;
355+ match key.code {
356+ KeyCode::Backspace => f.write_str("backspace")?,
357+ KeyCode::Enter => f.write_str("ret")?,
358+ KeyCode::Left => f.write_str("left")?,
359+ KeyCode::Right => f.write_str("right")?,
360+ KeyCode::Up => f.write_str("up")?,
361+ KeyCode::Down => f.write_str("down")?,
362+ KeyCode::Home => f.write_str("home")?,
363+ KeyCode::End => f.write_str("end")?,
364+ KeyCode::PageUp => f.write_str("pageup")?,
365+ KeyCode::PageDown => f.write_str("pagedown")?,
366+ KeyCode::Tab => f.write_str("tab")?,
367+ KeyCode::BackTab => f.write_str("backtab")?,
368+ KeyCode::Delete => f.write_str("del")?,
369+ KeyCode::Insert => f.write_str("ins")?,
370+ KeyCode::Null => f.write_str("null")?,
371+ KeyCode::Esc => f.write_str("esc")?,
372+ KeyCode::Char('<') => f.write_str("lt")?,
373+ KeyCode::Char('>') => f.write_str("gt")?,
374+ KeyCode::Char('+') => f.write_str("plus")?,
375+ KeyCode::Char('-') => f.write_str("minus")?,
376+ KeyCode::Char(';') => f.write_str("semicolon")?,
377+ KeyCode::Char('%') => f.write_str("percent")?,
378+ KeyCode::F(i) => f.write_fmt(format_args!("F{}", i))?,
379+ KeyCode::Char(c) => f.write_fmt(format_args!("{}", c))?,
380+ };
381+ Ok(())
382+ }
383+}
384+
385+impl FromStr for RepresentableKeyEvent {
386+ type Err = Error;
387+
388+ fn from_str(s: &str) -> Result<Self, Self::Err> {
389+ let mut tokens: Vec<_> = s.split('-').collect();
390+ let code = match tokens.pop().ok_or_else(|| anyhow!("Missing key code"))? {
391+ "backspace" => KeyCode::Backspace,
392+ "space" => KeyCode::Char(' '),
393+ "ret" => KeyCode::Enter,
394+ "lt" => KeyCode::Char('<'),
395+ "gt" => KeyCode::Char('>'),
396+ "plus" => KeyCode::Char('+'),
397+ "minus" => KeyCode::Char('-'),
398+ "semicolon" => KeyCode::Char(';'),
399+ "percent" => KeyCode::Char('%'),
400+ "left" => KeyCode::Left,
401+ "right" => KeyCode::Right,
402+ "up" => KeyCode::Down,
403+ "home" => KeyCode::Home,
404+ "end" => KeyCode::End,
405+ "pageup" => KeyCode::PageUp,
406+ "pagedown" => KeyCode::PageDown,
407+ "tab" => KeyCode::Tab,
408+ "backtab" => KeyCode::BackTab,
409+ "del" => KeyCode::Delete,
410+ "ins" => KeyCode::Insert,
411+ "null" => KeyCode::Null,
412+ "esc" => KeyCode::Esc,
413+ single if single.len() == 1 => KeyCode::Char(single.chars().next().unwrap()),
414+ function if function.len() > 1 && function.starts_with('F') => {
415+ let function: String = function.chars().skip(1).collect();
416+ let function = str::parse::<u8>(&function)?;
417+ (function > 0 && function < 13)
418+ .then(|| KeyCode::F(function))
419+ .ok_or_else(|| anyhow!("Invalid function key '{}'", function))?
420+ }
421+ invalid => return Err(anyhow!("Invalid key code '{}'", invalid)),
422+ };
423+
424+ let mut modifiers = KeyModifiers::empty();
425+ for token in tokens {
426+ let flag = match token {
427+ "S" => KeyModifiers::SHIFT,
428+ "A" => KeyModifiers::ALT,
429+ "C" => KeyModifiers::CONTROL,
430+ _ => return Err(anyhow!("Invalid key modifier '{}-'", token)),
431+ };
432+
433+ if modifiers.contains(flag) {
434+ return Err(anyhow!("Repeated key modifier '{}-'", token));
435+ }
436+ modifiers.insert(flag);
437+ }
438+
439+ Ok(RepresentableKeyEvent(KeyEvent { code, modifiers }))
440+ }
441+}
442+
443+pub fn parse_keymaps(toml_keymaps: &HashMap<String, HashMap<String, String>>) -> Result<Keymaps> {
444+ let mut keymaps = Keymaps::default();
445+
446+ for (mode, map) in toml_keymaps {
447+ let mode = Mode::from_str(&mode)?;
448+ for (key, command) in map {
449+ let key = str::parse::<RepresentableKeyEvent>(&key)?;
450+ let command = str::parse::<Command>(&command)?;
451+ keymaps.0.get_mut(&mode).unwrap().0.insert(key.0, command);
452+ }
453+ }
454+ Ok(keymaps)
455+}
456+
457+impl Deref for Keymap {
458+ type Target = HashMap<KeyEvent, Command>;
459+ fn deref(&self) -> &Self::Target {
460+ &self.0
461+ }
462+}
463+
464+impl Deref for Keymaps {
465+ type Target = HashMap<Mode, Keymap>;
466+ fn deref(&self) -> &Self::Target {
467+ &self.0
468+ }
469+}
470+
471+impl DerefMut for Keymap {
472+ fn deref_mut(&mut self) -> &mut Self::Target {
473+ &mut self.0
474+ }
475+}
476+
477+impl DerefMut for Keymaps {
478+ fn deref_mut(&mut self) -> &mut Self::Target {
479+ &mut self.0
480+ }
481+}
482+
483+#[cfg(test)]
484+mod test {
485+ use crate::config::Config;
486+
487+ use super::*;
488+
489+ impl PartialEq for Command {
490+ fn eq(&self, other: &Self) -> bool {
491+ self.name() == other.name()
492+ }
493+ }
494+
495+ #[test]
496+ fn parsing_keymaps_config_file() {
497+ let sample_keymaps = r#"
498+ [keys.insert]
499+ y = "move_line_down"
500+ S-C-a = "delete_selection"
501+
502+ [keys.normal]
503+ A-F12 = "move_next_word_end"
504+ "#;
505+
506+ let config: Config = toml::from_str(sample_keymaps).unwrap();
507+ assert_eq!(
508+ *config
509+ .keymaps
510+ .0
511+ .get(&Mode::Insert)
512+ .unwrap()
513+ .0
514+ .get(&KeyEvent {
515+ code: KeyCode::Char('y'),
516+ modifiers: KeyModifiers::NONE
517+ })
518+ .unwrap(),
519+ Command::move_line_down
520+ );
521+ assert_eq!(
522+ *config
523+ .keymaps
524+ .0
525+ .get(&Mode::Insert)
526+ .unwrap()
527+ .0
528+ .get(&KeyEvent {
529+ code: KeyCode::Char('a'),
530+ modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL
531+ })
532+ .unwrap(),
533+ Command::delete_selection
534+ );
535+ assert_eq!(
536+ *config
537+ .keymaps
538+ .0
539+ .get(&Mode::Normal)
540+ .unwrap()
541+ .0
542+ .get(&KeyEvent {
543+ code: KeyCode::F(12),
544+ modifiers: KeyModifiers::ALT
545+ })
546+ .unwrap(),
547+ Command::move_next_word_end
548+ );
549+ }
550+
551+ #[test]
552+ fn parsing_unmodified_keys() {
553+ assert_eq!(
554+ str::parse::<RepresentableKeyEvent>("backspace").unwrap(),
555+ RepresentableKeyEvent(KeyEvent {
556+ code: KeyCode::Backspace,
557+ modifiers: KeyModifiers::NONE
558+ })
559+ );
560+
561+ assert_eq!(
562+ str::parse::<RepresentableKeyEvent>("left").unwrap(),
563+ RepresentableKeyEvent(KeyEvent {
564+ code: KeyCode::Left,
565+ modifiers: KeyModifiers::NONE
566+ })
567+ );
568+
569+ assert_eq!(
570+ str::parse::<RepresentableKeyEvent>(",").unwrap(),
571+ RepresentableKeyEvent(KeyEvent {
572+ code: KeyCode::Char(','),
573+ modifiers: KeyModifiers::NONE
574+ })
575+ );
576+
577+ assert_eq!(
578+ str::parse::<RepresentableKeyEvent>("w").unwrap(),
579+ RepresentableKeyEvent(KeyEvent {
580+ code: KeyCode::Char('w'),
581+ modifiers: KeyModifiers::NONE
582+ })
583+ );
584+
585+ assert_eq!(
586+ str::parse::<RepresentableKeyEvent>("F12").unwrap(),
587+ RepresentableKeyEvent(KeyEvent {
588+ code: KeyCode::F(12),
589+ modifiers: KeyModifiers::NONE
590+ })
591+ );
592+ }
593+
594+ fn parsing_modified_keys() {
595+ assert_eq!(
596+ str::parse::<RepresentableKeyEvent>("S-minus").unwrap(),
597+ RepresentableKeyEvent(KeyEvent {
598+ code: KeyCode::Char('-'),
599+ modifiers: KeyModifiers::SHIFT
600+ })
601+ );
602+
603+ assert_eq!(
604+ str::parse::<RepresentableKeyEvent>("C-A-S-F12").unwrap(),
605+ RepresentableKeyEvent(KeyEvent {
606+ code: KeyCode::F(12),
607+ modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL | KeyModifiers::ALT
608+ })
609+ );
610+
611+ assert_eq!(
612+ str::parse::<RepresentableKeyEvent>("S-C-2").unwrap(),
613+ RepresentableKeyEvent(KeyEvent {
614+ code: KeyCode::F(2),
615+ modifiers: KeyModifiers::SHIFT | KeyModifiers::CONTROL
616+ })
617+ );
618+ }
619+
620+ #[test]
621+ fn parsing_nonsensical_keys_fails() {
622+ assert!(str::parse::<RepresentableKeyEvent>("F13").is_err());
623+ assert!(str::parse::<RepresentableKeyEvent>("F0").is_err());
624+ assert!(str::parse::<RepresentableKeyEvent>("aaa").is_err());
625+ assert!(str::parse::<RepresentableKeyEvent>("S-S-a").is_err());
626+ assert!(str::parse::<RepresentableKeyEvent>("C-A-S-C-1").is_err());
627+ assert!(str::parse::<RepresentableKeyEvent>("FU").is_err());
628+ assert!(str::parse::<RepresentableKeyEvent>("123").is_err());
629+ assert!(str::parse::<RepresentableKeyEvent>("S--").is_err());
630+ }
319631}
helix-term/src/lib.rs+1
@@ -4,5 +4,6 @@ pub mod application;
44pub mod args;
55pub mod commands;
66pub mod compositor;
7+pub mod config;
78pub mod keymap;
89pub mod ui;
helix-term/src/main.rs+9−2
@@ -1,6 +1,6 @@
11use helix_term::application::Application;
22use helix_term::args::Args;
3−
3+use helix_term::config::Config;
44use std::path::PathBuf;
55
66use anyhow::{Context, Result};
@@ -89,10 +89,17 @@ FLAGS:
8989 std::fs::create_dir_all(&conf_dir).ok();
9090 }
9191
92+ let config = std::fs::read_to_string(conf_dir.join("config.toml"))
93+ .ok()
94+ .map(|s| toml::from_str(&s))
95+ .transpose()?
96+ .or_else(|| Some(Config::default()))
97+ .unwrap();
98+
9299 setup_logging(logpath, args.verbosity).context("failed to initialize logging")?;
93100
94101 // TODO: use the thread local executor to spawn the application task separately from the work pool
95− let mut app = Application::new(args).context("unable to create new appliction")?;
102+ let mut app = Application::new(args, config).context("unable to create new application")?;
96103 app.run().await.unwrap();
97104
98105 Ok(())
helix-term/src/ui/editor.rs+12−15
@@ -11,10 +11,7 @@ use helix_core::{
1111 syntax::{self, HighlightEvent},
1212 Position, Range,
1313};
14−use helix_view::{
15− document::{IndentStyle, Mode},
16− Document, Editor, Theme, View,
17−};
14+use helix_view::{document::Mode, Document, Editor, Theme, View};
1815use std::borrow::Cow;
1916
2017use crossterm::{
@@ -30,7 +27,7 @@ use tui::{
3027};
3128
3229pub struct EditorView {
33− keymap: Keymaps,
30+ keymaps: Keymaps,
3431 on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>,
3532 last_insert: (commands::Command, Vec<KeyEvent>),
3633 completion: Option<Completion>,
@@ -40,16 +37,16 @@ const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter
4037
4138impl Default for EditorView {
4239 fn default() -> Self {
43− Self::new()
40+ Self::new(Keymaps::default())
4441 }
4542}
4643
4744impl EditorView {
48− pub fn new() -> Self {
45+ pub fn new(keymaps: Keymaps) -> Self {
4946 Self {
50− keymap: keymap::default(),
47+ keymaps,
5148 on_next_key: None,
52− last_insert: (commands::normal_mode, Vec::new()),
49+ last_insert: (commands::Command::normal_mode, Vec::new()),
5350 completion: None,
5451 }
5552 }
@@ -546,8 +543,8 @@ impl EditorView {
546543 }
547544
548545 fn insert_mode(&self, cx: &mut commands::Context, event: KeyEvent) {
549− if let Some(command) = self.keymap[&Mode::Insert].get(&event) {
550− command(cx);
546+ if let Some(command) = self.keymaps[&Mode::Insert].get(&event) {
547+ command.execute(cx);
551548 } else if let KeyEvent {
552549 code: KeyCode::Char(ch),
553550 ..
@@ -568,7 +565,7 @@ impl EditorView {
568565 // special handling for repeat operator
569566 key!('.') => {
570567 // first execute whatever put us into insert mode
571− (self.last_insert.0)(cxt);
568+ self.last_insert.0.execute(cxt);
572569 // then replay the inputs
573570 for key in &self.last_insert.1 {
574571 self.insert_mode(cxt, *key)
@@ -584,8 +581,8 @@ impl EditorView {
584581 // set the register
585582 cxt.selected_register = cxt.editor.selected_register.take();
586583
587− if let Some(command) = self.keymap[&mode].get(&event) {
588− command(cxt);
584+ if let Some(command) = self.keymaps[&mode].get(&event) {
585+ command.execute(cxt);
589586 }
590587 }
591588 }
@@ -699,7 +696,7 @@ impl Component for EditorView {
699696 // how we entered insert mode is important, and we should track that so
700697 // we can repeat the side effect.
701698
702− self.last_insert.0 = self.keymap[&mode][&key];
699+ self.last_insert.0 = self.keymaps[&mode][&key];
703700 self.last_insert.1.clear();
704701 }
705702 (Mode::Insert, Mode::Normal) => {
helix-view/src/document.rs+26−1
@@ -1,7 +1,9 @@
1−use anyhow::{Context, Error};
1+use anyhow::{anyhow, Context, Error};
22use std::cell::Cell;
3+use std::fmt::Display;
34use std::future::Future;
45use std::path::{Component, Path, PathBuf};
6+use std::str::FromStr;
57use std::sync::Arc;
68
79use helix_core::{
@@ -86,6 +88,29 @@ impl fmt::Debug for Document {
8688 }
8789}
8890
91+impl Display for Mode {
92+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93+ match self {
94+ Mode::Normal => f.write_str("normal"),
95+ Mode::Select => f.write_str("select"),
96+ Mode::Insert => f.write_str("insert"),
97+ }
98+ }
99+}
100+
101+impl FromStr for Mode {
102+ type Err = Error;
103+
104+ fn from_str(s: &str) -> Result<Self, Self::Err> {
105+ match s {
106+ "normal" => Ok(Mode::Normal),
107+ "select" => Ok(Mode::Select),
108+ "insert" => Ok(Mode::Insert),
109+ _ => Err(anyhow!("Invalid mode '{}'", s)),
110+ }
111+ }
112+}
113+
89114/// Like std::mem::replace() except it allows the replacement value to be mapped from the
90115/// original value.
91116fn take_with<T, F>(mut_ref: &mut T, closure: F)