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?
md-compositor-key-remapping-idea
Michael Davis 12 months ago
parent 5ca3ed3ef8
commit 71b6cc4d17
No known key found for this signature in database

@ -50,7 +50,7 @@ use movement::Movement;
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 @@ impl MappableCommand {
}
}
Self::Static { fun, .. } => (fun)(cx),
Self::Component { .. } => unimplemented!(),
}
}
@ -216,6 +222,7 @@ impl MappableCommand {
match &self {
Self::Typable { name, .. } => name,
Self::Static { name, .. } => name,
Self::Component { .. } => unimplemented!(),
}
}
@ -223,9 +230,18 @@ impl MappableCommand {
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 @@ impl fmt::Debug for MappableCommand {
.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 @@ impl ui::menu::Item for MappableCommand {
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 @@ impl<T: Item + 'static> Component for Picker<T> {
_ => 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 @@ impl<T: Item + Send + 'static> Component for DynamicPicker<T> {
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…
Cancel
Save