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
pull/2982/head
Joe 2 years ago committed by GitHub
parent 85411bed83
commit b26e7e2e8f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -108,13 +108,7 @@ impl Application {
.ok()
.filter(|theme| (true_color || theme.is_16_color()))
})
.unwrap_or_else(|| {
if true_color {
theme_loader.default()
} else {
theme_loader.base16_default()
}
});
.unwrap_or_else(|| theme_loader.default_theme(true_color));
let syn_loader_conf = user_syntax_loader().unwrap_or_else(|err| {
eprintln!("Bad language config: {}", err);
@ -373,13 +367,7 @@ impl Application {
})
.ok()
.filter(|theme| (true_color || theme.is_16_color()))
.unwrap_or_else(|| {
if true_color {
self.theme_loader.default()
} else {
self.theme_loader.base16_default()
}
}),
.unwrap_or_else(|| self.theme_loader.default_theme(true_color)),
);
}

@ -15,11 +15,11 @@ pub struct TypableCommand {
pub completer: Option<Completer>,
}
fn quit(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
) -> anyhow::Result<()> {
fn quit(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
ensure!(args.is_empty(), ":quit takes no arguments");
// last view and we have unsaved changes
@ -35,8 +35,12 @@ fn quit(
fn force_quit(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
ensure!(args.is_empty(), ":quit! takes no arguments");
cx.editor.close(view!(cx.editor).id);
@ -44,11 +48,11 @@ fn force_quit(
Ok(())
}
fn open(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
) -> anyhow::Result<()> {
fn open(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
ensure!(!args.is_empty(), "wrong argument count");
for arg in args {
let (path, pos) = args::parse_file(arg);
@ -114,8 +118,12 @@ fn buffer_gather_paths_impl(editor: &mut Editor, args: &[Cow<str>]) -> Vec<Docum
fn buffer_close(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let document_ids = buffer_gather_paths_impl(cx.editor, args);
buffer_close_by_ids_impl(cx.editor, &document_ids, false)
}
@ -123,8 +131,12 @@ fn buffer_close(
fn force_buffer_close(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let document_ids = buffer_gather_paths_impl(cx.editor, args);
buffer_close_by_ids_impl(cx.editor, &document_ids, true)
}
@ -141,8 +153,12 @@ fn buffer_gather_others_impl(editor: &mut Editor) -> Vec<DocumentId> {
fn buffer_close_others(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let document_ids = buffer_gather_others_impl(cx.editor);
buffer_close_by_ids_impl(cx.editor, &document_ids, false)
}
@ -150,8 +166,12 @@ fn buffer_close_others(
fn force_buffer_close_others(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let document_ids = buffer_gather_others_impl(cx.editor);
buffer_close_by_ids_impl(cx.editor, &document_ids, true)
}
@ -163,8 +183,12 @@ fn buffer_gather_all_impl(editor: &mut Editor) -> Vec<DocumentId> {
fn buffer_close_all(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let document_ids = buffer_gather_all_impl(cx.editor);
buffer_close_by_ids_impl(cx.editor, &document_ids, false)
}
@ -172,8 +196,12 @@ fn buffer_close_all(
fn force_buffer_close_all(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let document_ids = buffer_gather_all_impl(cx.editor);
buffer_close_by_ids_impl(cx.editor, &document_ids, true)
}
@ -181,8 +209,12 @@ fn force_buffer_close_all(
fn buffer_next(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
goto_buffer(cx.editor, Direction::Forward);
Ok(())
}
@ -190,8 +222,12 @@ fn buffer_next(
fn buffer_previous(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
goto_buffer(cx.editor, Direction::Backward);
Ok(())
}
@ -242,24 +278,36 @@ fn write_impl(
fn write(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
write_impl(cx, args.first(), false)
}
fn force_write(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
write_impl(cx, args.first(), true)
}
fn new_file(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
cx.editor.new_file(Action::Replace);
Ok(())
@ -268,8 +316,12 @@ fn new_file(
fn format(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let doc = doc!(cx.editor);
if let Some(format) = doc.format() {
let callback =
@ -282,8 +334,12 @@ fn format(
fn set_indent_style(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
use IndentStyle::*;
// If no argument, report current indent style.
@ -321,8 +377,12 @@ fn set_indent_style(
fn set_line_ending(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
use LineEnding::*;
// If no argument, report current line ending setting.
@ -391,8 +451,12 @@ fn set_line_ending(
fn earlier(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let uk = args.join(" ").parse::<UndoKind>().map_err(|s| anyhow!(s))?;
let (view, doc) = current!(cx.editor);
@ -407,8 +471,12 @@ fn earlier(
fn later(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let uk = args.join(" ").parse::<UndoKind>().map_err(|s| anyhow!(s))?;
let (view, doc) = current!(cx.editor);
let success = doc.later(view.id, uk);
@ -424,6 +492,10 @@ fn write_quit(
args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
write_impl(cx, args.first(), false)?;
helix_lsp::block_on(cx.jobs.finish())?;
quit(cx, &[], event)
@ -434,6 +506,10 @@ fn force_write_quit(
args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
write_impl(cx, args.first(), true)?;
force_quit(cx, &[], event)
}
@ -463,10 +539,14 @@ pub(super) fn buffers_remaining_impl(editor: &mut Editor) -> anyhow::Result<()>
fn write_all_impl(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
quit: bool,
force: bool,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let mut errors = String::new();
let auto_format = cx.editor.config().auto_format;
let jobs = &mut cx.jobs;
@ -520,6 +600,10 @@ fn write_all(
args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
write_all_impl(cx, args, event, false, false)
}
@ -528,6 +612,10 @@ fn write_all_quit(
args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
write_all_impl(cx, args, event, true, false)
}
@ -536,6 +624,10 @@ fn force_write_all_quit(
args: &[Cow<str>],
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
write_all_impl(cx, args, event, true, true)
}
@ -556,24 +648,36 @@ fn quit_all_impl(editor: &mut Editor, force: bool) -> anyhow::Result<()> {
fn quit_all(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
quit_all_impl(cx.editor, false)
}
fn force_quit_all(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
quit_all_impl(cx.editor, true)
}
fn cquit(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let exit_code = args
.first()
.and_then(|code| code.parse::<i32>().ok())
@ -586,8 +690,12 @@ fn cquit(
fn force_cquit(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let exit_code = args
.first()
.and_then(|code| code.parse::<i32>().ok())
@ -600,35 +708,61 @@ fn force_cquit(
fn theme(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
) -> anyhow::Result<()> {
let theme = args.first().context("Theme not provided")?;
let theme = cx
.editor
.theme_loader
.load(theme)
.with_context(|| format!("Failed setting theme {}", theme))?;
let true_color = cx.editor.config().true_color || crate::true_color();
if !(true_color || theme.is_16_color()) {
bail!("Unsupported theme: theme requires true color support");
}
cx.editor.set_theme(theme);
event: PromptEvent,
) -> anyhow::Result<()> {
let true_color = cx.editor.config.load().true_color || crate::true_color();
match event {
PromptEvent::Abort => {
cx.editor.unset_theme_preview();
}
PromptEvent::Update => {
if let Some(theme_name) = args.first() {
if let Ok(theme) = cx.editor.theme_loader.load(theme_name) {
if !(true_color || theme.is_16_color()) {
bail!("Unsupported theme: theme requires true color support");
}
cx.editor.set_theme_preview(theme);
};
};
}
PromptEvent::Validate => {
let theme_name = args.first().with_context(|| "Theme name not provided")?;
let theme = cx
.editor
.theme_loader
.load(theme_name)
.with_context(|| "Theme does not exist")?;
if !(true_color || theme.is_16_color()) {
bail!("Unsupported theme: theme requires true color support");
}
cx.editor.set_theme(theme);
}
};
Ok(())
}
fn yank_main_selection_to_clipboard(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Clipboard)
}
fn yank_joined_to_clipboard(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let doc = doc!(cx.editor);
let default_sep = Cow::Borrowed(doc.line_ending.as_str());
let separator = args.first().unwrap_or(&default_sep);
@ -638,16 +772,24 @@ fn yank_joined_to_clipboard(
fn yank_main_selection_to_primary_clipboard(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
yank_main_selection_to_clipboard_impl(cx.editor, ClipboardType::Selection)
}
fn yank_joined_to_primary_clipboard(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let doc = doc!(cx.editor);
let default_sep = Cow::Borrowed(doc.line_ending.as_str());
let separator = args.first().unwrap_or(&default_sep);
@ -657,32 +799,48 @@ fn yank_joined_to_primary_clipboard(
fn paste_clipboard_after(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Clipboard, 1)
}
fn paste_clipboard_before(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
paste_clipboard_impl(cx.editor, Paste::Before, ClipboardType::Clipboard, 1)
}
fn paste_primary_clipboard_after(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
paste_clipboard_impl(cx.editor, Paste::After, ClipboardType::Selection, 1)
}
fn paste_primary_clipboard_before(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
paste_clipboard_impl(cx.editor, Paste::Before, ClipboardType::Selection, 1)
}
@ -710,24 +868,36 @@ fn replace_selections_with_clipboard_impl(
fn replace_selections_with_clipboard(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
replace_selections_with_clipboard_impl(cx, ClipboardType::Clipboard)
}
fn replace_selections_with_primary_clipboard(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
replace_selections_with_clipboard_impl(cx, ClipboardType::Selection)
}
fn show_clipboard_provider(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
cx.editor
.set_status(cx.editor.clipboard_provider.name().to_string());
Ok(())
@ -736,8 +906,12 @@ fn show_clipboard_provider(
fn change_current_directory(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let dir = helix_core::path::expand_tilde(
args.first()
.context("target directory not provided")?
@ -760,8 +934,12 @@ fn change_current_directory(
fn show_current_directory(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let cwd = std::env::current_dir().context("Couldn't get the new working directory")?;
cx.editor
.set_status(format!("Current working directory is {}", cwd.display()));
@ -772,8 +950,12 @@ fn show_current_directory(
fn set_encoding(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let doc = doc_mut!(cx.editor);
if let Some(label) = args.first() {
doc.set_encoding(label)
@ -788,8 +970,12 @@ fn set_encoding(
fn reload(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let scrolloff = cx.editor.config().scrolloff;
let (view, doc) = current!(cx.editor);
doc.reload(view.id).map(|_| {
@ -800,8 +986,12 @@ fn reload(
fn tree_sitter_scopes(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let (view, doc) = current!(cx.editor);
let text = doc.text().slice(..);
@ -814,8 +1004,12 @@ fn tree_sitter_scopes(
fn vsplit(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let id = view!(cx.editor).doc;
if args.is_empty() {
@ -833,8 +1027,12 @@ fn vsplit(
fn hsplit(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let id = view!(cx.editor).doc;
if args.is_empty() {
@ -852,8 +1050,12 @@ fn hsplit(
fn vsplit_new(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
cx.editor.new_file(Action::VerticalSplit);
Ok(())
@ -862,8 +1064,12 @@ fn vsplit_new(
fn hsplit_new(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
cx.editor.new_file(Action::HorizontalSplit);
Ok(())
@ -872,8 +1078,12 @@ fn hsplit_new(
fn debug_eval(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
if let Some(debugger) = cx.editor.debugger.as_mut() {
let (frame, thread_id) = match (debugger.active_frame, debugger.thread_id) {
(Some(frame), Some(thread_id)) => (frame, thread_id),
@ -894,8 +1104,12 @@ fn debug_eval(
fn debug_start(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let mut args = args.to_owned();
let name = match args.len() {
0 => None,
@ -907,8 +1121,12 @@ fn debug_start(
fn debug_remote(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let mut args = args.to_owned();
let address = match args.len() {
0 => None,
@ -924,8 +1142,12 @@ fn debug_remote(
fn tutor(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let path = helix_loader::runtime_dir().join("tutor.txt");
cx.editor.open(&path, Action::Replace)?;
// Unset path to prevent accidentally saving to the original tutor file.
@ -936,8 +1158,12 @@ fn tutor(
pub(super) fn goto_line_number(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
ensure!(!args.is_empty(), "Line number required");
let line = args[0].parse::<usize>()?;
@ -954,8 +1180,12 @@ pub(super) fn goto_line_number(
fn get_option(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
if args.len() != 1 {
anyhow::bail!("Bad arguments. Usage: `:get key`");
}
@ -976,8 +1206,12 @@ fn get_option(
fn set_option(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
if args.len() != 2 {
anyhow::bail!("Bad arguments. Usage: `:set key field`");
}
@ -1009,8 +1243,12 @@ fn set_option(
fn language(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
if args.len() != 1 {
anyhow::bail!("Bad arguments. Usage: `:set-language language`");
}
@ -1023,19 +1261,23 @@ fn language(
Ok(())
}
fn sort(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
) -> anyhow::Result<()> {
fn sort(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
sort_impl(cx, args, false)
}
fn sort_reverse(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
sort_impl(cx, args, true)
}
@ -1076,8 +1318,12 @@ fn sort_impl(
fn reflow(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let (view, doc) = current!(cx.editor);
const DEFAULT_MAX_LEN: usize = 79;
@ -1115,8 +1361,12 @@ fn reflow(
fn tree_sitter_subtree(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let (view, doc) = current!(cx.editor);
if let Some(syntax) = doc.syntax() {
@ -1151,8 +1401,12 @@ fn tree_sitter_subtree(
fn open_config(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
cx.editor
.open(&helix_loader::config_file(), Action::Replace)?;
Ok(())
@ -1161,8 +1415,12 @@ fn open_config(
fn open_log(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
cx.editor.open(&helix_loader::log_file(), Action::Replace)?;
Ok(())
}
@ -1170,8 +1428,12 @@ fn open_log(
fn refresh_config(
cx: &mut compositor::Context,
_args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
cx.editor.config_events.0.send(ConfigEvent::Refresh)?;
Ok(())
}
@ -1179,8 +1441,12 @@ fn refresh_config(
fn append_output(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
ensure!(!args.is_empty(), "Shell command required");
shell(cx, &args.join(" "), &ShellBehavior::Append);
Ok(())
@ -1189,18 +1455,22 @@ fn append_output(
fn insert_output(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
ensure!(!args.is_empty(), "Shell command required");
shell(cx, &args.join(" "), &ShellBehavior::Insert);
Ok(())
}
fn pipe(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
) -> anyhow::Result<()> {
fn pipe(cx: &mut compositor::Context, args: &[Cow<str>], event: PromptEvent) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
ensure!(!args.is_empty(), "Shell command required");
shell(cx, &args.join(" "), &ShellBehavior::Replace);
Ok(())
@ -1209,8 +1479,12 @@ fn pipe(
fn run_shell_command(
cx: &mut compositor::Context,
args: &[Cow<str>],
_event: PromptEvent,
event: PromptEvent,
) -> anyhow::Result<()> {
if event != PromptEvent::Validate {
return Ok(());
}
let shell = &cx.editor.config().shell;
let (output, success) = shell_impl(shell, &args.join(" "), None)?;
if success {
@ -1270,14 +1544,14 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
aliases: &["bc", "bclose"],
doc: "Close the current buffer.",
fun: buffer_close,
completer: Some(completers::buffer),
completer: Some(completers::buffer),
},
TypableCommand {
name: "buffer-close!",
aliases: &["bc!", "bclose!"],
doc: "Close the current buffer forcefully (ignoring unsaved changes).",
fun: force_buffer_close,
completer: Some(completers::buffer),
completer: Some(completers::buffer),
},
TypableCommand {
name: "buffer-close-others",
@ -1561,7 +1835,7 @@ pub const TYPABLE_COMMAND_LIST: &[TypableCommand] = &[
doc: "Display tree sitter scopes, primarily for theming and development.",
fun: tree_sitter_scopes,
completer: None,
},
},
TypableCommand {
name: "debug-start",
aliases: &["dbg"],
@ -1787,10 +2061,6 @@ pub fn command_mode(cx: &mut Context) {
}
}, // completion
move |cx: &mut compositor::Context, input: &str, event: PromptEvent| {
if event != PromptEvent::Validate {
return;
}
let parts = input.split_whitespace().collect::<Vec<&str>>();
if parts.is_empty() {
return;
@ -1811,10 +2081,10 @@ pub fn command_mode(cx: &mut Context) {
if let Err(e) = (cmd.fun)(cx, &args[1..], event) {
cx.editor.set_error(format!("{}", e));
}
} else {
} else if event == PromptEvent::Validate {
cx.editor
.set_error(format!("no such command: '{}'", parts[0]));
};
}
},
);
prompt.doc_fn = Box::new(|input: &str| {

@ -237,6 +237,7 @@ pub mod completers {
));
names.push("default".into());
names.push("base16_default".into());
names.sort();
let mut names: Vec<_> = names
.into_iter()

@ -490,7 +490,7 @@ impl<T: Item + 'static> Component for Picker<T> {
_ => return EventResult::Ignored(None),
};
let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _| {
let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _cx| {
// remove the layer
compositor.last_picker = compositor.pop();
})));

@ -464,7 +464,6 @@ pub struct Editor {
pub registers: Registers,
pub macro_recording: Option<(char, Vec<KeyEvent>)>,
pub macro_replaying: Vec<char>,
pub theme: Theme,
pub language_servers: helix_lsp::Registry,
pub diagnostics: BTreeMap<lsp::Url, Vec<lsp::Diagnostic>>,
@ -476,6 +475,12 @@ pub struct Editor {
pub syn_loader: Arc<syntax::Loader>,
pub theme_loader: Arc<theme::Loader>,
/// last_theme is used for theme previews. We store the current theme here,
/// and if previewing is cancelled, we can return to it.
pub last_theme: Option<Theme>,
/// The currently applied editor theme. While previewing a theme, the previewed theme
/// is set here.
pub theme: Theme,
pub status_msg: Option<(Cow<'static, str>, Severity)>,
pub autoinfo: Option<Info>,
@ -500,6 +505,11 @@ pub enum ConfigEvent {
Update(Box<Config>),
}
enum ThemeAction {
Set,
Preview,
}
#[derive(Debug, Clone)]
pub struct CompleteAction {
pub trigger_offset: usize,
@ -544,6 +554,7 @@ impl Editor {
breakpoints: HashMap::new(),
syn_loader,
theme_loader,
last_theme: None,
registers: Registers::default(),
clipboard_provider: get_clipboard_provider(),
status_msg: None,
@ -613,7 +624,22 @@ impl Editor {
.unwrap_or(false)
}
pub fn unset_theme_preview(&mut self) {
if let Some(last_theme) = self.last_theme.take() {
self.set_theme(last_theme);
}
// None likely occurs when the user types ":theme" and then exits before previewing
}
pub fn set_theme_preview(&mut self, theme: Theme) {
self.set_theme_impl(theme, ThemeAction::Preview);
}
pub fn set_theme(&mut self, theme: Theme) {
self.set_theme_impl(theme, ThemeAction::Set);
}
fn set_theme_impl(&mut self, theme: Theme, preview: ThemeAction) {
// `ui.selection` is the only scope required to be able to render a theme.
if theme.find_scope_index("ui.selection").is_none() {
self.set_error("Invalid theme: `ui.selection` required");
@ -623,7 +649,18 @@ impl Editor {
let scopes = theme.scopes();
self.syn_loader.set_scopes(scopes.to_vec());
self.theme = theme;
match preview {
ThemeAction::Preview => {
let last_theme = std::mem::replace(&mut self.theme, theme);
// only insert on first preview: this will be the last theme the user has saved
self.last_theme.get_or_insert(last_theme);
}
ThemeAction::Set => {
self.last_theme = None;
self.theme = theme;
}
}
self._refresh();
}

@ -77,6 +77,14 @@ impl Loader {
names
}
pub fn default_theme(&self, true_color: bool) -> Theme {
if true_color {
self.default()
} else {
self.base16_default()
}
}
/// Returns the default theme
pub fn default(&self) -> Theme {
DEFAULT_THEME.clone()

Loading…
Cancel
Save