mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-22 01:16:18 +04:00
Implement a Component command for closing a buffer picker buffer
This is a basic example of a remappable command specific to a single Component. It could be remapped like so: ```toml [keys.buffer-picker] C-d = "close_buffer_in_buffer_picker" ``` This has some rough edges: * Can we namespace the commands so they don't all have to be very long and specific about which component they work for? * How can we make this work for generics? * We can't define commands that operate on a `Picker<_>` for example. This example only works because we're using a `Picker<BufferMeta>`. * For Pickers and Menus we could use a `Vec<Box<dyn Item>>` and drop the generics but that would lose static dispatch. * Could we separate the part that needs generics into a different struct and have the functions operate on that?
This commit is contained in:
parent
5ca3ed3ef8
commit
71b6cc4d17
@ -50,7 +50,7 @@
|
||||
|
||||
use crate::{
|
||||
args,
|
||||
compositor::{self, Component, Compositor},
|
||||
compositor::{self, Component, Compositor, EventResult},
|
||||
filter_picker_entry,
|
||||
job::Callback,
|
||||
keymap::{Keymaps, ReverseKeymap},
|
||||
@ -172,6 +172,11 @@ pub enum MappableCommand {
|
||||
fun: fn(cx: &mut Context),
|
||||
doc: &'static str,
|
||||
},
|
||||
Component {
|
||||
name: &'static str,
|
||||
fun: fn(&mut dyn crate::compositor::Component, &mut compositor::Context) -> EventResult,
|
||||
doc: &'static str,
|
||||
},
|
||||
}
|
||||
|
||||
macro_rules! static_commands {
|
||||
@ -209,6 +214,7 @@ pub fn execute(&self, cx: &mut Context) {
|
||||
}
|
||||
}
|
||||
Self::Static { fun, .. } => (fun)(cx),
|
||||
Self::Component { .. } => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -216,6 +222,7 @@ pub fn name(&self) -> &str {
|
||||
match &self {
|
||||
Self::Typable { name, .. } => name,
|
||||
Self::Static { name, .. } => name,
|
||||
Self::Component { .. } => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -223,9 +230,18 @@ pub fn doc(&self) -> &str {
|
||||
match &self {
|
||||
Self::Typable { doc, .. } => doc,
|
||||
Self::Static { doc, .. } => doc,
|
||||
Self::Component { .. } => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: macro for this...
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const close_buffer_in_buffer_picker: Self = Self::Component {
|
||||
name: "close_buffer_in_buffer_picker",
|
||||
fun: crate::ui::picker::close_buffer_in_buffer_picker,
|
||||
doc: "Closes the currently focused buffer",
|
||||
};
|
||||
|
||||
#[rustfmt::skip]
|
||||
static_commands!(
|
||||
no_op, "Do nothing",
|
||||
@ -503,6 +519,7 @@ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
.field(name)
|
||||
.field(args)
|
||||
.finish(),
|
||||
Self::Component { .. } => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2526,17 +2543,19 @@ fn file_picker_in_current_directory(cx: &mut Context) {
|
||||
cx.push_layer(Box::new(overlaid(picker)));
|
||||
}
|
||||
|
||||
pub struct BufferMeta {
|
||||
pub id: DocumentId,
|
||||
path: Option<PathBuf>,
|
||||
is_modified: bool,
|
||||
is_current: bool,
|
||||
focused_at: std::time::Instant,
|
||||
}
|
||||
|
||||
pub type BufferPicker = Picker<BufferMeta>;
|
||||
|
||||
fn buffer_picker(cx: &mut Context) {
|
||||
let current = view!(cx.editor).doc;
|
||||
|
||||
struct BufferMeta {
|
||||
id: DocumentId,
|
||||
path: Option<PathBuf>,
|
||||
is_modified: bool,
|
||||
is_current: bool,
|
||||
focused_at: std::time::Instant,
|
||||
}
|
||||
|
||||
impl ui::menu::Item for BufferMeta {
|
||||
type Data = ();
|
||||
|
||||
@ -2710,6 +2729,7 @@ fn format(&self, keymap: &Self::Data) -> Row {
|
||||
Some(bindings) => format!("{} ({}) [{}]", doc, fmt_binding(bindings), name).into(),
|
||||
None => format!("{} [{}]", doc, name).into(),
|
||||
},
|
||||
MappableCommand::Component { .. } => unimplemented!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -379,9 +379,15 @@ pub fn default() -> HashMap<Domain, KeyTrie> {
|
||||
"home" => goto_line_start,
|
||||
"end" => goto_line_end_newline,
|
||||
});
|
||||
|
||||
let buffer_picker = keymap!({ "Buffer picker"
|
||||
"C-x" => close_buffer_in_buffer_picker,
|
||||
});
|
||||
|
||||
hashmap!(
|
||||
Domain::Mode(Mode::Normal) => normal,
|
||||
Domain::Mode(Mode::Select) => select,
|
||||
Domain::Mode(Mode::Insert) => insert,
|
||||
Domain::Component("buffer-picker") => buffer_picker,
|
||||
)
|
||||
}
|
||||
|
@ -802,6 +802,18 @@ fn handle_event(&mut self, event: &Event, ctx: &mut Context) -> EventResult {
|
||||
_ => return EventResult::Ignored(None),
|
||||
};
|
||||
|
||||
match ctx.keymaps.get_by_component_id(self.id, key_event) {
|
||||
crate::keymap::KeymapResult::Matched(crate::keymap::MappableCommand::Component {
|
||||
fun,
|
||||
..
|
||||
}) => {
|
||||
if let EventResult::Consumed(callback) = fun(self, ctx) {
|
||||
return EventResult::Consumed(callback);
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
|
||||
let close_fn =
|
||||
EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _ctx| {
|
||||
// remove the layer
|
||||
@ -987,3 +999,39 @@ fn id(&self) -> Option<&'static str> {
|
||||
Some(DYNAMIC_PICKER_ID)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn close_buffer_in_buffer_picker(
|
||||
component: &mut dyn Component,
|
||||
cx: &mut compositor::Context,
|
||||
) -> EventResult {
|
||||
let Some(picker) = component
|
||||
.as_any_mut()
|
||||
.downcast_mut::<crate::commands::BufferPicker>()
|
||||
else {
|
||||
return EventResult::Ignored(None);
|
||||
};
|
||||
let Some(id) = picker.selection().map(|meta| meta.id) else {
|
||||
return EventResult::Ignored(None);
|
||||
};
|
||||
match cx.editor.close_document(id, false) {
|
||||
Ok(_) => {
|
||||
picker.options.retain(|item| item.id != id);
|
||||
if picker.options.is_empty() {
|
||||
return close_fn();
|
||||
}
|
||||
picker.cursor = picker.cursor.saturating_sub(1);
|
||||
picker.force_score();
|
||||
}
|
||||
// TODO: impl From<CloseError> for anyhow::Error
|
||||
Err(_err) => cx.editor.set_error("Failed to close buffer"),
|
||||
}
|
||||
|
||||
EventResult::Consumed(None)
|
||||
}
|
||||
|
||||
fn close_fn() -> EventResult {
|
||||
EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _ctx| {
|
||||
// remove the layer
|
||||
compositor.last_picker = compositor.pop();
|
||||
})))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user