aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--helix-term/src/application.rs16
-rw-r--r--helix-term/src/commands/typed.rs472
-rw-r--r--helix-term/src/ui/mod.rs1
-rw-r--r--helix-term/src/ui/picker.rs2
-rw-r--r--helix-view/src/editor.rs41
-rw-r--r--helix-view/src/theme.rs8
6 files changed, 422 insertions, 118 deletions
diff --git a/helix-term/src/application.rs b/helix-term/src/application.rs
index 805f660f..df14f5e3 100644
--- a/helix-term/src/application.rs
+++ b/helix-term/src/application.rs
@@ -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)),
);
}
diff --git a/helix-term/src/commands/typed.rs b/helix-term/src/commands/typed.rs
index 70f5fa9f..4e1ac0da 100644
--- a/helix-term/src/commands/typed.rs
+++ b/helix-term/src/commands/typed.rs
@@ -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| {
diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs
index 8d2bd325..ca4cedb5 100644
--- a/helix-term/src/ui/mod.rs
+++ b/helix-term/src/ui/mod.rs
@@ -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()
diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs
index f4dd234a..375723e5 100644
--- a/helix-term/src/ui/picker.rs
+++ b/helix-term/src/ui/picker.rs
@@ -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();
})));
diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs
index 1ed27e99..a2943af9 100644
--- a/helix-view/src/editor.rs
+++ b/helix-view/src/editor.rs
@@ -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();
}
diff --git a/helix-view/src/theme.rs b/helix-view/src/theme.rs
index 3f45aac6..fa5fa702 100644
--- a/helix-view/src/theme.rs
+++ b/helix-view/src/theme.rs
@@ -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()