on master help sign in
Add live preview to theme picker (#1798)

* Add theme picker with live preview * Add live theme preview to :theme command * cargo fmt * Fix clippy warnings * Remove picker variant * Remove unused import * Cleanup * Change current_theme to last_theme * Fix accidental comment flash deletion * Typo * Remove theme cache * Add some comments * Refactor some theme handling TIL flatmap on Option is called and_then * Remove unnecessary renames * Constrain last_theme theme preview lifecycle * Switch to bitflag implementation * Better handling of last_theme * Sort theme names * Better memory juggling * Missed a branch * Remove name from theme, switch bitand to & * cargo fmt * Update helix-view/src/editor.rs * Switch boolean to enum * Remove bitflag impl * cargo fmt * Remove un-needed type arg * cargo fmt

commitb26e7e2e8fc900b5637d9772ecb74874e8794ecc authorJoe <Josephharrisonlim@gmail.com> dateTue 5 Jul 2022, 06:44 (4 years ago) parent85411be
6 files changed+422−118
helix-term/src/application.rs+2−14 helix-term/src/commands/typed.rs+371−101 helix-term/src/ui/mod.rs+1 helix-term/src/ui/picker.rs+1−1 helix-view/src/editor.rs+39−2 helix-view/src/theme.rs+8
helix-term/src/application.rs+2−14
@@ -108,13 +108,7 @@ impl Application {
108108 .ok()
109109 .filter(|theme| (true_color || theme.is_16_color()))
110110 })
111− .unwrap_or_else(|| {
112− if true_color {
113− theme_loader.default()
114− } else {
115− theme_loader.base16_default()
116− }
117− });
111+ .unwrap_or_else(|| theme_loader.default_theme(true_color));
118112
119113 let syn_loader_conf = user_syntax_loader().unwrap_or_else(|err| {
120114 eprintln!("Bad language config: {}", err);
@@ -373,13 +367,7 @@ impl Application {
373367 })
374368 .ok()
375369 .filter(|theme| (true_color || theme.is_16_color()))
376− .unwrap_or_else(|| {
377− if true_color {
378− self.theme_loader.default()
379− } else {
380− self.theme_loader.base16_default()
381− }
382− }),
370+ .unwrap_or_else(|| self.theme_loader.default_theme(true_color)),
383371 );
384372 }
385373
helix-term/src/commands/typed.rs+371−101
@@ -15,11 +15,11 @@ pub struct TypableCommand {
1515 pub completer: Option<Completer>,
1616}
1717
18−fn quit(
19− cx: &mut compositor::Context,
20− args: &[Cow<str>],
21− _event: PromptEvent,
22−) -> anyhow::Result<()> {
18+fn quit(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) -> anyhow::Result<()> {
19+ if event != PromptEvent::Validate {
20+ return Ok(());
21+ }
22+
2323 ensure!(args.is_empty(), ":quit takes no arguments");
2424
2525 // last view and we have unsaved changes
@@ -35,8 +35,12 @@ fn quit(
3535fn force_quit(
3636 cx: &mut compositor::Context,
3737 args: &[Cow<str>],
38− _event: PromptEvent,
38+ event: PromptEvent,
3939) -> anyhow::Result<()> {
40+ if event != PromptEvent::Validate {
41+ return Ok(());
42+ }
43+
4044 ensure!(args.is_empty(), ":quit! takes no arguments");
4145
4246 cx.editor.close(view!(cx.editor).id);
@@ -44,11 +48,11 @@ fn force_quit(
4448 Ok(())
4549}
4650
47−fn open(
48− cx: &mut compositor::Context,
49− args: &[Cow<str>],
50− _event: PromptEvent,
51−) -> anyhow::Result<()> {
51+fn open(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) -> anyhow::Result<()> {
52+ if event != PromptEvent::Validate {
53+ return Ok(());
54+ }
55+
5256 ensure!(!args.is_empty(), "wrong argument count");
5357 for arg in args {
5458 let (path, pos) = args::parse_file(arg);
@@ -114,8 +118,12 @@ fn buffer_gather_paths_impl(editor: &mut Editor, args: &[Cow<str>]) -> Vec<Docum
114118fn buffer_close(
115119 cx: &mut compositor::Context,
116120 args: &[Cow<str>],
117− _event: PromptEvent,
121+ event: PromptEvent,
118122) -> anyhow::Result<()> {
123+ if event != PromptEvent::Validate {
124+ return Ok(());
125+ }
126+
119127 let document_ids = buffer_gather_paths_impl(cx.editor, args);
120128 buffer_close_by_ids_impl(cx.editor, &document_ids, false)
121129}
@@ -123,8 +131,12 @@ fn buffer_close(
123131fn force_buffer_close(
124132 cx: &mut compositor::Context,
125133 args: &[Cow<str>],
126− _event: PromptEvent,
134+ event: PromptEvent,
127135) -> anyhow::Result<()> {
136+ if event != PromptEvent::Validate {
137+ return Ok(());
138+ }
139+
128140 let document_ids = buffer_gather_paths_impl(cx.editor, args);
129141 buffer_close_by_ids_impl(cx.editor, &document_ids, true)
130142}
@@ -141,8 +153,12 @@ fn buffer_gather_others_impl(editor: &mut Editor) -> Vec<DocumentId> {
141153fn buffer_close_others(
142154 cx: &mut compositor::Context,
143155 _args: &[Cow<str>],
144− _event: PromptEvent,
156+ event: PromptEvent,
145157) -> anyhow::Result<()> {
158+ if event != PromptEvent::Validate {
159+ return Ok(());
160+ }
161+
146162 let document_ids = buffer_gather_others_impl(cx.editor);
147163 buffer_close_by_ids_impl(cx.editor, &document_ids, false)
148164}
@@ -150,8 +166,12 @@ fn buffer_close_others(
150166fn force_buffer_close_others(
151167 cx: &mut compositor::Context,
152168 _args: &[Cow<str>],
153− _event: PromptEvent,
169+ event: PromptEvent,
154170) -> anyhow::Result<()> {
171+ if event != PromptEvent::Validate {
172+ return Ok(());
173+ }
174+
155175 let document_ids = buffer_gather_others_impl(cx.editor);
156176 buffer_close_by_ids_impl(cx.editor, &document_ids, true)
157177}
@@ -163,8 +183,12 @@ fn buffer_gather_all_impl(editor: &mut Editor) -> Vec<DocumentId> {
163183fn buffer_close_all(
164184 cx: &mut compositor::Context,
165185 _args: &[Cow<str>],
166− _event: PromptEvent,
186+ event: PromptEvent,
167187) -> anyhow::Result<()> {
188+ if event != PromptEvent::Validate {
189+ return Ok(());
190+ }
191+
168192 let document_ids = buffer_gather_all_impl(cx.editor);
169193 buffer_close_by_ids_impl(cx.editor, &document_ids, false)
170194}
@@ -172,8 +196,12 @@ fn buffer_close_all(
172196fn force_buffer_close_all(
173197 cx: &mut compositor::Context,
174198 _args: &[Cow<str>],
175− _event: PromptEvent,
199+ event: PromptEvent,
176200) -> anyhow::Result<()> {
201+ if event != PromptEvent::Validate {
202+ return Ok(());
203+ }
204+
177205 let document_ids = buffer_gather_all_impl(cx.editor);
178206 buffer_close_by_ids_impl(cx.editor, &document_ids, true)
179207}
@@ -181,8 +209,12 @@ fn force_buffer_close_all(
181209fn buffer_next(
182210 cx: &mut compositor::Context,
183211 _args: &[Cow<str>],
184− _event: PromptEvent,
212+ event: PromptEvent,
185213) -> anyhow::Result<()> {
214+ if event != PromptEvent::Validate {
215+ return Ok(());
216+ }
217+
186218 goto_buffer(cx.editor, Direction::Forward);
187219 Ok(())
188220}
@@ -190,8 +222,12 @@ fn buffer_next(
190222fn buffer_previous(
191223 cx: &mut compositor::Context,
192224 _args: &[Cow<str>],
193− _event: PromptEvent,
225+ event: PromptEvent,
194226) -> anyhow::Result<()> {
227+ if event != PromptEvent::Validate {
228+ return Ok(());
229+ }
230+
195231 goto_buffer(cx.editor, Direction::Backward);
196232 Ok(())
197233}
@@ -242,24 +278,36 @@ fn write_impl(
242278fn write(
243279 cx: &mut compositor::Context,
244280 args: &[Cow<str>],
245− _event: PromptEvent,
281+ event: PromptEvent,
246282) -> anyhow::Result<()> {
283+ if event != PromptEvent::Validate {
284+ return Ok(());
285+ }
286+
247287 write_impl(cx, args.first(), false)
248288}
249289
250290fn force_write(
251291 cx: &mut compositor::Context,
252292 args: &[Cow<str>],
253− _event: PromptEvent,
293+ event: PromptEvent,
254294) -> anyhow::Result<()> {
295+ if event != PromptEvent::Validate {
296+ return Ok(());
297+ }
298+
255299 write_impl(cx, args.first(), true)
256300}
257301
258302fn new_file(
259303 cx: &mut compositor::Context,
260304 _args: &[Cow<str>],
261− _event: PromptEvent,
305+ event: PromptEvent,
262306) -> anyhow::Result<()> {
307+ if event != PromptEvent::Validate {
308+ return Ok(());
309+ }
310+
263311 cx.editor.new_file(Action::Replace);
264312
265313 Ok(())
@@ -268,8 +316,12 @@ fn new_file(
268316fn format(
269317 cx: &mut compositor::Context,
270318 _args: &[Cow<str>],
271− _event: PromptEvent,
319+ event: PromptEvent,
272320) -> anyhow::Result<()> {
321+ if event != PromptEvent::Validate {
322+ return Ok(());
323+ }
324+
273325 let doc = doc!(cx.editor);
274326 if let Some(format) = doc.format() {
275327 let callback =
@@ -282,8 +334,12 @@ fn format(
282334fn set_indent_style(
283335 cx: &mut compositor::Context,
284336 args: &[Cow<str>],
285− _event: PromptEvent,
337+ event: PromptEvent,
286338) -> anyhow::Result<()> {
339+ if event != PromptEvent::Validate {
340+ return Ok(());
341+ }
342+
287343 use IndentStyle::*;
288344
289345 // If no argument, report current indent style.
@@ -321,8 +377,12 @@ fn set_indent_style(
321377fn set_line_ending(
322378 cx: &mut compositor::Context,
323379 args: &[Cow<str>],
324− _event: PromptEvent,
380+ event: PromptEvent,
325381) -> anyhow::Result<()> {
382+ if event != PromptEvent::Validate {
383+ return Ok(());
384+ }
385+
326386 use LineEnding::*;
327387
328388 // If no argument, report current line ending setting.
@@ -391,8 +451,12 @@ fn set_line_ending(
391451fn earlier(
392452 cx: &mut compositor::Context,
393453 args: &[Cow<str>],
394− _event: PromptEvent,
454+ event: PromptEvent,
395455) -> anyhow::Result<()> {
456+ if event != PromptEvent::Validate {
457+ return Ok(());
458+ }
459+
396460 let uk = args.join(" ").parse::<UndoKind>().map_err(|s| anyhow!(s))?;
397461
398462 let (view, doc) = current!(cx.editor);
@@ -407,8 +471,12 @@ fn earlier(
407471fn later(
408472 cx: &mut compositor::Context,
409473 args: &[Cow<str>],
410− _event: PromptEvent,
474+ event: PromptEvent,
411475) -> anyhow::Result<()> {
476+ if event != PromptEvent::Validate {
477+ return Ok(());
478+ }
479+
412480 let uk = args.join(" ").parse::<UndoKind>().map_err(|s| anyhow!(s))?;
413481 let (view, doc) = current!(cx.editor);
414482 let success = doc.later(view.id, uk);
@@ -424,6 +492,10 @@ fn write_quit(
424492 args: &[Cow<str>],
425493 event: PromptEvent,
426494) -> anyhow::Result<()> {
495+ if event != PromptEvent::Validate {
496+ return Ok(());
497+ }
498+
427499 write_impl(cx, args.first(), false)?;
428500 helix_lsp::block_on(cx.jobs.finish())?;
429501 quit(cx, &[], event)
@@ -434,6 +506,10 @@ fn force_write_quit(
434506 args: &[Cow<str>],
435507 event: PromptEvent,
436508) -> anyhow::Result<()> {
509+ if event != PromptEvent::Validate {
510+ return Ok(());
511+ }
512+
437513 write_impl(cx, args.first(), true)?;
438514 force_quit(cx, &[], event)
439515}
@@ -463,10 +539,14 @@ pub(super) fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()>
463539fn write_all_impl(
464540 cx: &mut compositor::Context,
465541 _args: &[Cow<str>],
466− _event: PromptEvent,
542+ event: PromptEvent,
467543 quit: bool,
468544 force: bool,
469545) -> anyhow::Result<()> {
546+ if event != PromptEvent::Validate {
547+ return Ok(());
548+ }
549+
470550 let mut errors = String::new();
471551 let auto_format = cx.editor.config().auto_format;
472552 let jobs = &mut cx.jobs;
@@ -520,6 +600,10 @@ fn write_all(
520600 args: &[Cow<str>],
521601 event: PromptEvent,
522602) -> anyhow::Result<()> {
603+ if event != PromptEvent::Validate {
604+ return Ok(());
605+ }
606+
523607 write_all_impl(cx, args, event, false, false)
524608}
525609
@@ -528,6 +612,10 @@ fn write_all_quit(
528612 args: &[Cow<str>],
529613 event: PromptEvent,
530614) -> anyhow::Result<()> {
615+ if event != PromptEvent::Validate {
616+ return Ok(());
617+ }
618+
531619 write_all_impl(cx, args, event, true, false)
532620}
533621
@@ -536,6 +624,10 @@ fn force_write_all_quit(
536624 args: &[Cow<str>],
537625 event: PromptEvent,
538626) -> anyhow::Result<()> {
627+ if event != PromptEvent::Validate {
628+ return Ok(());
629+ }
630+
539631 write_all_impl(cx, args, event, true, true)
540632}
541633
@@ -556,24 +648,36 @@ fn quit_all_impl(editor: &mut Editor, force: bool) -> anyhow::Result<()> {
556648fn quit_all(
557649 cx: &mut compositor::Context,
558650 _args: &[Cow<str>],
559− _event: PromptEvent,
651+ event: PromptEvent,
560652) -> anyhow::Result<()> {
653+ if event != PromptEvent::Validate {
654+ return Ok(());
655+ }
656+
561657 quit_all_impl(cx.editor, false)
562658}
563659
564660fn force_quit_all(
565661 cx: &mut compositor::Context,
566662 _args: &[Cow<str>],
567− _event: PromptEvent,
663+ event: PromptEvent,
568664) -> anyhow::Result<()> {
665+ if event != PromptEvent::Validate {
666+ return Ok(());
667+ }
668+
569669 quit_all_impl(cx.editor, true)
570670}
571671
572672fn cquit(
573673 cx: &mut compositor::Context,
574674 args: &[Cow<str>],
575− _event: PromptEvent,
675+ event: PromptEvent,
576676) -> anyhow::Result<()> {
677+ if event != PromptEvent::Validate {
678+ return Ok(());
679+ }
680+
577681 let exit_code = args
578682 .first()
579683 .and_then(|code| code.parse::<i32>().ok())
@@ -586,8 +690,12 @@ fn cquit(
586690fn force_cquit(
587691 cx: &mut compositor::Context,
588692 args: &[Cow<str>],
589− _event: PromptEvent,
693+ event: PromptEvent,
590694) -> anyhow::Result<()> {
695+ if event != PromptEvent::Validate {
696+ return Ok(());
697+ }
698+
591699 let exit_code = args
592700 .first()
593701 .and_then(|code| code.parse::<i32>().ok())
@@ -600,35 +708,61 @@ fn force_cquit(
600708fn theme(
601709 cx: &mut compositor::Context,
602710 args: &[Cow<str>],
603− _event: PromptEvent,
604−) -> anyhow::Result<()> {
605− let theme = args.first().context("Theme not provided")?;
606− let theme = cx
607− .editor
608− .theme_loader
609− .load(theme)
610− .with_context(|| format!("Failed setting theme {}", theme))?;
611− let true_color = cx.editor.config().true_color || crate::true_color();
612− if !(true_color || theme.is_16_color()) {
613− bail!("Unsupported theme: theme requires true color support");
614− }
615− cx.editor.set_theme(theme);
711+ event: PromptEvent,
712+) -> anyhow::Result<()> {
713+ let true_color = cx.editor.config.load().true_color || crate::true_color();
714+ match event {
715+ PromptEvent::Abort => {
716+ cx.editor.unset_theme_preview();
717+ }
718+ PromptEvent::Update => {
719+ if let Some(theme_name) = args.first() {
720+ if let Ok(theme) = cx.editor.theme_loader.load(theme_name) {
721+ if !(true_color || theme.is_16_color()) {
722+ bail!("Unsupported theme: theme requires true color support");
723+ }
724+ cx.editor.set_theme_preview(theme);
725+ };
726+ };
727+ }
728+ PromptEvent::Validate => {
729+ let theme_name = args.first().with_context(|| "Theme name not provided")?;
730+ let theme = cx
731+ .editor
732+ .theme_loader
733+ .load(theme_name)
734+ .with_context(|| "Theme does not exist")?;
735+ if !(true_color || theme.is_16_color()) {
736+ bail!("Unsupported theme: theme requires true color support");
737+ }
738+ cx.editor.set_theme(theme);
739+ }
740+ };
741+
616742 Ok(())
617743}
618744
619745fn yank_main_selection_to_clipboard(
620746 cx: &mut compositor::Context,
621747 _args: &[Cow<str>],
622− _event: PromptEvent,
748+ event: PromptEvent,
623749) -> anyhow::Result<()> {
750+ if event != PromptEvent::Validate {
751+ return Ok(());
752+ }
753+
624754 yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Clipboard)
625755}
626756
627757fn yank_joined_to_clipboard(
628758 cx: &mut compositor::Context,
629759 args: &[Cow<str>],
630− _event: PromptEvent,
760+ event: PromptEvent,
631761) -> anyhow::Result<()> {
762+ if event != PromptEvent::Validate {
763+ return Ok(());
764+ }
765+
632766 let doc = doc!(cx.editor);
633767 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
634768 let separator = args.first().unwrap_or(&default_sep);
@@ -638,16 +772,24 @@ fn yank_joined_to_clipboard(
638772fn yank_main_selection_to_primary_clipboard(
639773 cx: &mut compositor::Context,
640774 _args: &[Cow<str>],
641− _event: PromptEvent,
775+ event: PromptEvent,
642776) -> anyhow::Result<()> {
777+ if event != PromptEvent::Validate {
778+ return Ok(());
779+ }
780+
643781 yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Selection)
644782}
645783
646784fn yank_joined_to_primary_clipboard(
647785 cx: &mut compositor::Context,
648786 args: &[Cow<str>],
649− _event: PromptEvent,
787+ event: PromptEvent,
650788) -> anyhow::Result<()> {
789+ if event != PromptEvent::Validate {
790+ return Ok(());
791+ }
792+
651793 let doc = doc!(cx.editor);
652794 let default_sep = Cow::Borrowed(doc.line_ending.as_str());
653795 let separator = args.first().unwrap_or(&default_sep);
@@ -657,32 +799,48 @@ fn yank_joined_to_primary_clipboard(
657799fn paste_clipboard_after(
658800 cx: &mut compositor::Context,
659801 _args: &[Cow<str>],
660− _event: PromptEvent,
802+ event: PromptEvent,
661803) -> anyhow::Result<()> {
804+ if event != PromptEvent::Validate {
805+ return Ok(());
806+ }
807+
662808 paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Clipboard, 1)
663809}
664810
665811fn paste_clipboard_before(
666812 cx: &mut compositor::Context,
667813 _args: &[Cow<str>],
668− _event: PromptEvent,
814+ event: PromptEvent,
669815) -> anyhow::Result<()> {
816+ if event != PromptEvent::Validate {
817+ return Ok(());
818+ }
819+
670820 paste_clipboard_impl(cx.editor, Paste::Before, ClipboardType::Clipboard, 1)
671821}
672822
673823fn paste_primary_clipboard_after(
674824 cx: &mut compositor::Context,
675825 _args: &[Cow<str>],
676− _event: PromptEvent,
826+ event: PromptEvent,
677827) -> anyhow::Result<()> {
828+ if event != PromptEvent::Validate {
829+ return Ok(());
830+ }
831+
678832 paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Selection, 1)
679833}
680834
681835fn paste_primary_clipboard_before(
682836 cx: &mut compositor::Context,
683837 _args: &[Cow<str>],
684− _event: PromptEvent,
838+ event: PromptEvent,
685839) -> anyhow::Result<()> {
840+ if event != PromptEvent::Validate {
841+ return Ok(());
842+ }
843+
686844 paste_clipboard_impl(cx.editor, Paste::Before, ClipboardType::Selection, 1)
687845}
688846
@@ -710,24 +868,36 @@ fn replace_selections_with_clipboard_impl(
710868fn replace_selections_with_clipboard(
711869 cx: &mut compositor::Context,
712870 _args: &[Cow<str>],
713− _event: PromptEvent,
871+ event: PromptEvent,
714872) -> anyhow::Result<()> {
873+ if event != PromptEvent::Validate {
874+ return Ok(());
875+ }
876+
715877 replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard)
716878}
717879
718880fn replace_selections_with_primary_clipboard(
719881 cx: &mut compositor::Context,
720882 _args: &[Cow<str>],
721− _event: PromptEvent,
883+ event: PromptEvent,
722884) -> anyhow::Result<()> {
885+ if event != PromptEvent::Validate {
886+ return Ok(());
887+ }
888+
723889 replace_selections_with_clipboard_impl(cx, ClipboardType::Selection)
724890}
725891
726892fn show_clipboard_provider(
727893 cx: &mut compositor::Context,
728894 _args: &[Cow<str>],
729− _event: PromptEvent,
895+ event: PromptEvent,
730896) -> anyhow::Result<()> {
897+ if event != PromptEvent::Validate {
898+ return Ok(());
899+ }
900+
731901 cx.editor
732902 .set_status(cx.editor.clipboard_provider.name().to_string());
733903 Ok(())
@@ -736,8 +906,12 @@ fn show_clipboard_provider(
736906fn change_current_directory(
737907 cx: &mut compositor::Context,
738908 args: &[Cow<str>],
739− _event: PromptEvent,
909+ event: PromptEvent,
740910) -> anyhow::Result<()> {
911+ if event != PromptEvent::Validate {
912+ return Ok(());
913+ }
914+
741915 let dir = helix_core::path::expand_tilde(
742916 args.first()
743917 .context("target directory not provided")?
@@ -760,8 +934,12 @@ fn change_current_directory(
760934fn show_current_directory(
761935 cx: &mut compositor::Context,
762936 _args: &[Cow<str>],
763− _event: PromptEvent,
937+ event: PromptEvent,
764938) -> anyhow::Result<()> {
939+ if event != PromptEvent::Validate {
940+ return Ok(());
941+ }
942+
765943 let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
766944 cx.editor
767945 .set_status(format!("Current working directory is {}", cwd.display()));
@@ -772,8 +950,12 @@ fn show_current_directory(
772950fn set_encoding(
773951 cx: &mut compositor::Context,
774952 args: &[Cow<str>],
775− _event: PromptEvent,
953+ event: PromptEvent,
776954) -> anyhow::Result<()> {
955+ if event != PromptEvent::Validate {
956+ return Ok(());
957+ }
958+
777959 let doc = doc_mut!(cx.editor);
778960 if let Some(label) = args.first() {
779961 doc.set_encoding(label)
@@ -788,8 +970,12 @@ fn set_encoding(
788970fn reload(
789971 cx: &mut compositor::Context,
790972 _args: &[Cow<str>],
791− _event: PromptEvent,
973+ event: PromptEvent,
792974) -> anyhow::Result<()> {
975+ if event != PromptEvent::Validate {
976+ return Ok(());
977+ }
978+
793979 let scrolloff = cx.editor.config().scrolloff;
794980 let (view, doc) = current!(cx.editor);
795981 doc.reload(view.id).map(|_| {
@@ -800,8 +986,12 @@ fn reload(
800986fn tree_sitter_scopes(
801987 cx: &mut compositor::Context,
802988 _args: &[Cow<str>],
803− _event: PromptEvent,
989+ event: PromptEvent,
804990) -> anyhow::Result<()> {
991+ if event != PromptEvent::Validate {
992+ return Ok(());
993+ }
994+
805995 let (view, doc) = current!(cx.editor);
806996 let text = doc.text().slice(..);
807997
@@ -814,8 +1004,12 @@ fn tree_sitter_scopes(
8141004fn vsplit(
8151005 cx: &mut compositor::Context,
8161006 args: &[Cow<str>],
817− _event: PromptEvent,
1007+ event: PromptEvent,
8181008) -> anyhow::Result<()> {
1009+ if event != PromptEvent::Validate {
1010+ return Ok(());
1011+ }
1012+
8191013 let id = view!(cx.editor).doc;
8201014
8211015 if args.is_empty() {
@@ -833,8 +1027,12 @@ fn vsplit(
8331027fn hsplit(
8341028 cx: &mut compositor::Context,
8351029 args: &[Cow<str>],
836− _event: PromptEvent,
1030+ event: PromptEvent,
8371031) -> anyhow::Result<()> {
1032+ if event != PromptEvent::Validate {
1033+ return Ok(());
1034+ }
1035+
8381036 let id = view!(cx.editor).doc;
8391037
8401038 if args.is_empty() {
@@ -852,8 +1050,12 @@ fn hsplit(
8521050fn vsplit_new(
8531051 cx: &mut compositor::Context,
8541052 _args: &[Cow<str>],
855− _event: PromptEvent,
1053+ event: PromptEvent,
8561054) -> anyhow::Result<()> {
1055+ if event != PromptEvent::Validate {
1056+ return Ok(());
1057+ }
1058+
8571059 cx.editor.new_file(Action::VerticalSplit);
8581060
8591061 Ok(())
@@ -862,8 +1064,12 @@ fn vsplit_new(
8621064fn hsplit_new(
8631065 cx: &mut compositor::Context,
8641066 _args: &[Cow<str>],
865− _event: PromptEvent,
1067+ event: PromptEvent,
8661068) -> anyhow::Result<()> {
1069+ if event != PromptEvent::Validate {
1070+ return Ok(());
1071+ }
1072+
8671073 cx.editor.new_file(Action::HorizontalSplit);
8681074
8691075 Ok(())
@@ -872,8 +1078,12 @@ fn hsplit_new(
8721078fn debug_eval(
8731079 cx: &mut compositor::Context,
8741080 args: &[Cow<str>],
875− _event: PromptEvent,
1081+ event: PromptEvent,
8761082) -> anyhow::Result<()> {
1083+ if event != PromptEvent::Validate {
1084+ return Ok(());
1085+ }
1086+
8771087 if let Some(debugger) = cx.editor.debugger.as_mut() {
8781088 let (frame, thread_id) = match (debugger.active_frame, debugger.thread_id) {
8791089 (Some(frame), Some(thread_id)) => (frame, thread_id),
@@ -894,8 +1104,12 @@ fn debug_eval(
8941104fn debug_start(
8951105 cx: &mut compositor::Context,
8961106 args: &[Cow<str>],
897− _event: PromptEvent,
1107+ event: PromptEvent,
8981108) -> anyhow::Result<()> {
1109+ if event != PromptEvent::Validate {
1110+ return Ok(());
1111+ }
1112+
8991113 let mut args = args.to_owned();
9001114 let name = match args.len() {
9011115 0 => None,
@@ -907,8 +1121,12 @@ fn debug_start(
9071121fn debug_remote(
9081122 cx: &mut compositor::Context,
9091123 args: &[Cow<str>],
910− _event: PromptEvent,
1124+ event: PromptEvent,
9111125) -> anyhow::Result<()> {
1126+ if event != PromptEvent::Validate {
1127+ return Ok(());
1128+ }
1129+
9121130 let mut args = args.to_owned();
9131131 let address = match args.len() {
9141132 0 => None,
@@ -924,8 +1142,12 @@ fn debug_remote(
9241142fn tutor(
9251143 cx: &mut compositor::Context,
9261144 _args: &[Cow<str>],
927− _event: PromptEvent,
1145+ event: PromptEvent,
9281146) -> anyhow::Result<()> {
1147+ if event != PromptEvent::Validate {
1148+ return Ok(());
1149+ }
1150+
9291151 let path = helix_loader::runtime_dir().join("tutor.txt");
9301152 cx.editor.open(&path, Action::Replace)?;
9311153 // Unset path to prevent accidentally saving to the original tutor file.
@@ -936,8 +1158,12 @@ fn tutor(
9361158pub(super) fn goto_line_number(
9371159 cx: &mut compositor::Context,
9381160 args: &[Cow<str>],
939− _event: PromptEvent,
1161+ event: PromptEvent,
9401162) -> anyhow::Result<()> {
1163+ if event != PromptEvent::Validate {
1164+ return Ok(());
1165+ }
1166+
9411167 ensure!(!args.is_empty(), "Line number required");
9421168
9431169 let line = args[0].parse::<usize>()?;
@@ -954,8 +1180,12 @@ pub(super) fn goto_line_number(
9541180fn get_option(
9551181 cx: &mut compositor::Context,
9561182 args: &[Cow<str>],
957− _event: PromptEvent,
1183+ event: PromptEvent,
9581184) -> anyhow::Result<()> {
1185+ if event != PromptEvent::Validate {
1186+ return Ok(());
1187+ }
1188+
9591189 if args.len() != 1 {
9601190 anyhow::bail!("Bad arguments. Usage: `:get key`");
9611191 }
@@ -976,8 +1206,12 @@ fn get_option(
9761206fn set_option(
9771207 cx: &mut compositor::Context,
9781208 args: &[Cow<str>],
979− _event: PromptEvent,
1209+ event: PromptEvent,
9801210) -> anyhow::Result<()> {
1211+ if event != PromptEvent::Validate {
1212+ return Ok(());
1213+ }
1214+
9811215 if args.len() != 2 {
9821216 anyhow::bail!("Bad arguments. Usage: `:set key field`");
9831217 }
@@ -1009,8 +1243,12 @@ fn set_option(
10091243fn language(
10101244 cx: &mut compositor::Context,
10111245 args: &[Cow<str>],
1012− _event: PromptEvent,
1246+ event: PromptEvent,
10131247) -> anyhow::Result<()> {
1248+ if event != PromptEvent::Validate {
1249+ return Ok(());
1250+ }
1251+
10141252 if args.len() != 1 {
10151253 anyhow::bail!("Bad arguments. Usage: `:set-language language`");
10161254 }
@@ -1023,19 +1261,23 @@ fn language(
10231261 Ok(())
10241262}
10251263
1026−fn sort(
1027− cx: &mut compositor::Context,
1028− args: &[Cow<str>],
1029− _event: PromptEvent,
1030−) -> anyhow::Result<()> {
1264+fn sort(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) -> anyhow::Result<()> {
1265+ if event != PromptEvent::Validate {
1266+ return Ok(());
1267+ }
1268+
10311269 sort_impl(cx, args, false)
10321270}
10331271
10341272fn sort_reverse(
10351273 cx: &mut compositor::Context,
10361274 args: &[Cow<str>],
1037− _event: PromptEvent,
1275+ event: PromptEvent,
10381276) -> anyhow::Result<()> {
1277+ if event != PromptEvent::Validate {
1278+ return Ok(());
1279+ }
1280+
10391281 sort_impl(cx, args, true)
10401282}
10411283
@@ -1076,8 +1318,12 @@ fn sort_impl(
10761318fn reflow(
10771319 cx: &mut compositor::Context,
10781320 args: &[Cow<str>],
1079− _event: PromptEvent,
1321+ event: PromptEvent,
10801322) -> anyhow::Result<()> {
1323+ if event != PromptEvent::Validate {
1324+ return Ok(());
1325+ }
1326+
10811327 let (view, doc) = current!(cx.editor);
10821328
10831329 const DEFAULT_MAX_LEN: usize = 79;
@@ -1115,8 +1361,12 @@ fn reflow(
11151361fn tree_sitter_subtree(
11161362 cx: &mut compositor::Context,
11171363 _args: &[Cow<str>],
1118− _event: PromptEvent,
1364+ event: PromptEvent,
11191365) -> anyhow::Result<()> {
1366+ if event != PromptEvent::Validate {
1367+ return Ok(());
1368+ }
1369+
11201370 let (view, doc) = current!(cx.editor);
11211371
11221372 if let Some(syntax) = doc.syntax() {
@@ -1151,8 +1401,12 @@ fn tree_sitter_subtree(
11511401fn open_config(
11521402 cx: &mut compositor::Context,
11531403 _args: &[Cow<str>],
1154− _event: PromptEvent,
1404+ event: PromptEvent,
11551405) -> anyhow::Result<()> {
1406+ if event != PromptEvent::Validate {
1407+ return Ok(());
1408+ }
1409+
11561410 cx.editor
11571411 .open(&helix_loader::config_file(), Action::Replace)?;
11581412 Ok(())
@@ -1161,8 +1415,12 @@ fn open_config(
11611415fn open_log(
11621416 cx: &mut compositor::Context,
11631417 _args: &[Cow<str>],
1164− _event: PromptEvent,
1418+ event: PromptEvent,
11651419) -> anyhow::Result<()> {
1420+ if event != PromptEvent::Validate {
1421+ return Ok(());
1422+ }
1423+
11661424 cx.editor.open(&helix_loader::log_file(), Action::Replace)?;
11671425 Ok(())
11681426}
@@ -1170,8 +1428,12 @@ fn open_log(
11701428fn refresh_config(
11711429 cx: &mut compositor::Context,
11721430 _args: &[Cow<str>],
1173− _event: PromptEvent,
1431+ event: PromptEvent,
11741432) -> anyhow::Result<()> {
1433+ if event != PromptEvent::Validate {
1434+ return Ok(());
1435+ }
1436+
11751437 cx.editor.config_events.0.send(ConfigEvent::Refresh)?;
11761438 Ok(())
11771439}
@@ -1179,8 +1441,12 @@ fn refresh_config(
11791441fn append_output(
11801442 cx: &mut compositor::Context,
11811443 args: &[Cow<str>],
1182− _event: PromptEvent,
1444+ event: PromptEvent,
11831445) -> anyhow::Result<()> {
1446+ if event != PromptEvent::Validate {
1447+ return Ok(());
1448+ }
1449+
11841450 ensure!(!args.is_empty(), "Shell command required");
11851451 shell(cx, &args.join(" "), &ShellBehavior::Append);
11861452 Ok(())
@@ -1189,18 +1455,22 @@ fn append_output(
11891455fn insert_output(
11901456 cx: &mut compositor::Context,
11911457 args: &[Cow<str>],
1192− _event: PromptEvent,
1458+ event: PromptEvent,
11931459) -> anyhow::Result<()> {
1460+ if event != PromptEvent::Validate {
1461+ return Ok(());
1462+ }
1463+
11941464 ensure!(!args.is_empty(), "Shell command required");
11951465 shell(cx, &args.join(" "), &ShellBehavior::Insert);
11961466 Ok(())
11971467}
11981468
1199−fn pipe(
1200− cx: &mut compositor::Context,
1201− args: &[Cow<str>],
1202− _event: PromptEvent,
1203−) -> anyhow::Result<()> {
1469+fn pipe(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) -> anyhow::Result<()> {
1470+ if event != PromptEvent::Validate {
1471+ return Ok(());
1472+ }
1473+
12041474 ensure!(!args.is_empty(), "Shell command required");
12051475 shell(cx, &args.join(" "), &ShellBehavior::Replace);
12061476 Ok(())
@@ -1209,8 +1479,12 @@ fn pipe(
12091479fn run_shell_command(
12101480 cx: &mut compositor::Context,
12111481 args: &[Cow<str>],
1212− _event: PromptEvent,
1482+ event: PromptEvent,
12131483) -> anyhow::Result<()> {
1484+ if event != PromptEvent::Validate {
1485+ return Ok(());
1486+ }
1487+
12141488 let shell = &cx.editor.config().shell;
12151489 let (output, success) = shell_impl(shell, &args.join(" "), None)?;
12161490 if success {
@@ -1270,14 +1544,14 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
12701544 aliases: &["bc", "bclose"],
12711545 doc: "Close the current buffer.",
12721546 fun: buffer_close,
1273− completer: Some(completers::buffer),
1547+ completer: Some(completers::buffer),
12741548 },
12751549 TypableCommand {
12761550 name: "buffer-close!",
12771551 aliases: &["bc!", "bclose!"],
12781552 doc: "Close the current buffer forcefully (ignoring unsaved changes).",
12791553 fun: force_buffer_close,
1280− completer: Some(completers::buffer),
1554+ completer: Some(completers::buffer),
12811555 },
12821556 TypableCommand {
12831557 name: "buffer-close-others",
@@ -1561,7 +1835,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
15611835 doc: "Display tree sitter scopes, primarily for theming and development.",
15621836 fun: tree_sitter_scopes,
15631837 completer: None,
1564− },
1838+ },
15651839 TypableCommand {
15661840 name: "debug-start",
15671841 aliases: &["dbg"],
@@ -1787,10 +2061,6 @@ pub fn command_mode(cx: &mut Context) {
17872061 }
17882062 }, // completion
17892063 move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
1790− if event != PromptEvent::Validate {
1791− return;
1792− }
1793−
17942064 let parts = input.split_whitespace().collect::<Vec<&str>>();
17952065 if parts.is_empty() {
17962066 return;
@@ -1811,10 +2081,10 @@ pub fn command_mode(cx: &mut Context) {
18112081 if let Err(e) = (cmd.fun)(cx, &args[1..], event) {
18122082 cx.editor.set_error(format!("{}", e));
18132083 }
1814− } else {
2084+ } else if event == PromptEvent::Validate {
18152085 cx.editor
18162086 .set_error(format!("no such command: '{}'", parts[0]));
1817− };
2087+ }
18182088 },
18192089 );
18202090 prompt.doc_fn = Box::new(|input: &str| {
helix-term/src/ui/mod.rs+1
@@ -237,6 +237,7 @@ pub mod completers {
237237 ));
238238 names.push("default".into());
239239 names.push("base16_default".into());
240+ names.sort();
240241
241242 let mut names: Vec<_> = names
242243 .into_iter()
helix-term/src/ui/picker.rs+1−1
@@ -490,7 +490,7 @@ impl<T: Item + 'static> Component for Picker<T> {
490490 _ => return EventResult::Ignored(None),
491491 };
492492
493− let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _| {
493+ let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _cx| {
494494 // remove the layer
495495 compositor.last_picker = compositor.pop();
496496 })));
helix-view/src/editor.rs+39−2
@@ -464,7 +464,6 @@ pub struct Editor {
464464 pub registers: Registers,
465465 pub macro_recording: Option<(char, Vec<KeyEvent>)>,
466466 pub macro_replaying: Vec<char>,
467− pub theme: Theme,
468467 pub language_servers: helix_lsp::Registry,
469468 pub diagnostics: BTreeMap<lsp::Url, Vec<lsp::Diagnostic>>,
470469
@@ -476,6 +475,12 @@ pub struct Editor {
476475
477476 pub syn_loader: Arc<syntax::Loader>,
478477 pub theme_loader: Arc<theme::Loader>,
478+ /// last_theme is used for theme previews. We store the current theme here,
479+ /// and if previewing is cancelled, we can return to it.
480+ pub last_theme: Option<Theme>,
481+ /// The currently applied editor theme. While previewing a theme, the previewed theme
482+ /// is set here.
483+ pub theme: Theme,
479484
480485 pub status_msg: Option<(Cow<'static, str>, Severity)>,
481486 pub autoinfo: Option<Info>,
@@ -500,6 +505,11 @@ pub enum ConfigEvent {
500505 Update(Box<Config>),
501506}
502507
508+enum ThemeAction {
509+ Set,
510+ Preview,
511+}
512+
503513#[derive(Debug, Clone)]
504514pub struct CompleteAction {
505515 pub trigger_offset: usize,
@@ -544,6 +554,7 @@ impl Editor {
544554 breakpoints: HashMap::new(),
545555 syn_loader,
546556 theme_loader,
557+ last_theme: None,
547558 registers: Registers::default(),
548559 clipboard_provider: get_clipboard_provider(),
549560 status_msg: None,
@@ -613,7 +624,22 @@ impl Editor {
613624 .unwrap_or(false)
614625 }
615626
627+ pub fn unset_theme_preview(&mut self) {
628+ if let Some(last_theme) = self.last_theme.take() {
629+ self.set_theme(last_theme);
630+ }
631+ // None likely occurs when the user types ":theme" and then exits before previewing
632+ }
633+
634+ pub fn set_theme_preview(&mut self, theme: Theme) {
635+ self.set_theme_impl(theme, ThemeAction::Preview);
636+ }
637+
616638 pub fn set_theme(&mut self, theme: Theme) {
639+ self.set_theme_impl(theme, ThemeAction::Set);
640+ }
641+
642+ fn set_theme_impl(&mut self, theme: Theme, preview: ThemeAction) {
617643 // `ui.selection` is the only scope required to be able to render a theme.
618644 if theme.find_scope_index("ui.selection").is_none() {
619645 self.set_error("Invalid theme: `ui.selection` required");
@@ -623,7 +649,18 @@ impl Editor {
623649 let scopes = theme.scopes();
624650 self.syn_loader.set_scopes(scopes.to_vec());
625651
626− self.theme = theme;
652+ match preview {
653+ ThemeAction::Preview => {
654+ let last_theme = std::mem::replace(&mut self.theme, theme);
655+ // only insert on first preview: this will be the last theme the user has saved
656+ self.last_theme.get_or_insert(last_theme);
657+ }
658+ ThemeAction::Set => {
659+ self.last_theme = None;
660+ self.theme = theme;
661+ }
662+ }
663+
627664 self._refresh();
628665 }
629666
helix-view/src/theme.rs+8
@@ -77,6 +77,14 @@ impl Loader {
7777 names
7878 }
7979
80+ pub fn default_theme(&self, true_color: bool) -> Theme {
81+ if true_color {
82+ self.default()
83+ } else {
84+ self.base16_default()
85+ }
86+ }
87+
8088 /// Returns the default theme
8189 pub fn default(&self) -> Theme {
8290 DEFAULT_THEME.clone()