@@ -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), ®ex)
@@ -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), ®ex);
@@ -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+}