mirror of https://github.com/helix-editor/helix
Close some popups automatically (#1285)
* Add Event::Used to use event callback without consuming * Close popup if contents ignored event * collect event results before executing callbacks * don't add new result variant, use Ignored(..) instead * break in match cases * Make auto_close configurable * fix merge * auto close hover popups * fix formattingpull/1703/head
parent
e1a92fd399
commit
40eb1268c7
|
@ -2921,7 +2921,7 @@ pub mod cmd {
|
||||||
let call: job::Callback =
|
let call: job::Callback =
|
||||||
Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
|
Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
|
||||||
let contents = ui::Markdown::new(contents, editor.syn_loader.clone());
|
let contents = ui::Markdown::new(contents, editor.syn_loader.clone());
|
||||||
let popup = Popup::new("hover", contents);
|
let popup = Popup::new("hover", contents).auto_close(true);
|
||||||
compositor.replace_or_push("hover", Box::new(popup));
|
compositor.replace_or_push("hover", Box::new(popup));
|
||||||
});
|
});
|
||||||
Ok(call)
|
Ok(call)
|
||||||
|
|
|
@ -633,7 +633,7 @@ pub fn hover(cx: &mut Context) {
|
||||||
// skip if contents empty
|
// skip if contents empty
|
||||||
|
|
||||||
let contents = ui::Markdown::new(contents, editor.syn_loader.clone());
|
let contents = ui::Markdown::new(contents, editor.syn_loader.clone());
|
||||||
let popup = Popup::new("hover", contents);
|
let popup = Popup::new("hover", contents).auto_close(true);
|
||||||
compositor.replace_or_push("hover", Box::new(popup));
|
compositor.replace_or_push("hover", Box::new(popup));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -19,7 +19,7 @@ pub type Callback = Box<dyn FnOnce(&mut Compositor, &mut Context)>;
|
||||||
|
|
||||||
// Cursive-inspired
|
// Cursive-inspired
|
||||||
pub enum EventResult {
|
pub enum EventResult {
|
||||||
Ignored,
|
Ignored(Option<Callback>),
|
||||||
Consumed(Option<Callback>),
|
Consumed(Option<Callback>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ pub struct Context<'a> {
|
||||||
pub trait Component: Any + AnyComponent {
|
pub trait Component: Any + AnyComponent {
|
||||||
/// Process input events, return true if handled.
|
/// Process input events, return true if handled.
|
||||||
fn handle_event(&mut self, _event: Event, _ctx: &mut Context) -> EventResult {
|
fn handle_event(&mut self, _event: Event, _ctx: &mut Context) -> EventResult {
|
||||||
EventResult::Ignored
|
EventResult::Ignored(None)
|
||||||
}
|
}
|
||||||
// , args: ()
|
// , args: ()
|
||||||
|
|
||||||
|
@ -146,19 +146,34 @@ impl Compositor {
|
||||||
keys.push(key.into());
|
keys.push(key.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut callbacks = Vec::new();
|
||||||
|
let mut consumed = false;
|
||||||
|
|
||||||
// propagate events through the layers until we either find a layer that consumes it or we
|
// propagate events through the layers until we either find a layer that consumes it or we
|
||||||
// run out of layers (event bubbling)
|
// run out of layers (event bubbling)
|
||||||
for layer in self.layers.iter_mut().rev() {
|
for layer in self.layers.iter_mut().rev() {
|
||||||
match layer.handle_event(event, cx) {
|
match layer.handle_event(event, cx) {
|
||||||
EventResult::Consumed(Some(callback)) => {
|
EventResult::Consumed(Some(callback)) => {
|
||||||
callback(self, cx);
|
callbacks.push(callback);
|
||||||
return true;
|
consumed = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
EventResult::Consumed(None) => return true,
|
EventResult::Consumed(None) => {
|
||||||
EventResult::Ignored => false,
|
consumed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
EventResult::Ignored(Some(callback)) => {
|
||||||
|
callbacks.push(callback);
|
||||||
|
}
|
||||||
|
EventResult::Ignored(None) => {}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
false
|
|
||||||
|
for callback in callbacks {
|
||||||
|
callback(self, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
consumed
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn render(&mut self, cx: &mut Context) {
|
pub fn render(&mut self, cx: &mut Context) {
|
||||||
|
|
|
@ -276,7 +276,7 @@ impl Component for Completion {
|
||||||
code: KeyCode::Esc, ..
|
code: KeyCode::Esc, ..
|
||||||
}) = event
|
}) = event
|
||||||
{
|
{
|
||||||
return EventResult::Ignored;
|
return EventResult::Ignored(None);
|
||||||
}
|
}
|
||||||
self.popup.handle_event(event, cx)
|
self.popup.handle_event(event, cx)
|
||||||
}
|
}
|
||||||
|
|
|
@ -872,9 +872,7 @@ impl EditorView {
|
||||||
|
|
||||||
let path = match doc.path() {
|
let path = match doc.path() {
|
||||||
Some(path) => path.clone(),
|
Some(path) => path.clone(),
|
||||||
None => {
|
None => return EventResult::Ignored(None),
|
||||||
return EventResult::Ignored;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let line = coords.row + view.offset.row;
|
let line = coords.row + view.offset.row;
|
||||||
|
@ -884,7 +882,7 @@ impl EditorView {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
EventResult::Ignored
|
EventResult::Ignored(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseEvent {
|
MouseEvent {
|
||||||
|
@ -897,7 +895,7 @@ impl EditorView {
|
||||||
|
|
||||||
let pos = match view.pos_at_screen_coords(doc, row, column) {
|
let pos = match view.pos_at_screen_coords(doc, row, column) {
|
||||||
Some(pos) => pos,
|
Some(pos) => pos,
|
||||||
None => return EventResult::Ignored,
|
None => return EventResult::Ignored(None),
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut selection = doc.selection(view.id).clone();
|
let mut selection = doc.selection(view.id).clone();
|
||||||
|
@ -928,7 +926,7 @@ impl EditorView {
|
||||||
|
|
||||||
match result {
|
match result {
|
||||||
Some(view_id) => cxt.editor.tree.focus = view_id,
|
Some(view_id) => cxt.editor.tree.focus = view_id,
|
||||||
None => return EventResult::Ignored,
|
None => return EventResult::Ignored(None),
|
||||||
}
|
}
|
||||||
|
|
||||||
let offset = cxt.editor.config.scroll_lines.abs() as usize;
|
let offset = cxt.editor.config.scroll_lines.abs() as usize;
|
||||||
|
@ -944,14 +942,14 @@ impl EditorView {
|
||||||
..
|
..
|
||||||
} => {
|
} => {
|
||||||
if !cxt.editor.config.middle_click_paste {
|
if !cxt.editor.config.middle_click_paste {
|
||||||
return EventResult::Ignored;
|
return EventResult::Ignored(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (view, doc) = current!(cxt.editor);
|
let (view, doc) = current!(cxt.editor);
|
||||||
let range = doc.selection(view.id).primary();
|
let range = doc.selection(view.id).primary();
|
||||||
|
|
||||||
if range.to() - range.from() <= 1 {
|
if range.to() - range.from() <= 1 {
|
||||||
return EventResult::Ignored;
|
return EventResult::Ignored(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
commands::MappableCommand::yank_main_selection_to_primary_clipboard.execute(cxt);
|
commands::MappableCommand::yank_main_selection_to_primary_clipboard.execute(cxt);
|
||||||
|
@ -988,7 +986,7 @@ impl EditorView {
|
||||||
return EventResult::Consumed(None);
|
return EventResult::Consumed(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
EventResult::Ignored
|
EventResult::Ignored(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseEvent {
|
MouseEvent {
|
||||||
|
@ -1000,7 +998,7 @@ impl EditorView {
|
||||||
} => {
|
} => {
|
||||||
let editor = &mut cxt.editor;
|
let editor = &mut cxt.editor;
|
||||||
if !editor.config.middle_click_paste {
|
if !editor.config.middle_click_paste {
|
||||||
return EventResult::Ignored;
|
return EventResult::Ignored(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
if modifiers == crossterm::event::KeyModifiers::ALT {
|
if modifiers == crossterm::event::KeyModifiers::ALT {
|
||||||
|
@ -1023,10 +1021,10 @@ impl EditorView {
|
||||||
return EventResult::Consumed(None);
|
return EventResult::Consumed(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
EventResult::Ignored
|
EventResult::Ignored(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => EventResult::Ignored,
|
_ => EventResult::Ignored(None),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1117,7 +1115,7 @@ impl Component for EditorView {
|
||||||
// if the command consumed the last view, skip the render.
|
// if the command consumed the last view, skip the render.
|
||||||
// on the next loop cycle the Application will then terminate.
|
// on the next loop cycle the Application will then terminate.
|
||||||
if cx.editor.should_close() {
|
if cx.editor.should_close() {
|
||||||
return EventResult::Ignored;
|
return EventResult::Ignored(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
let (view, doc) = current!(cx.editor);
|
let (view, doc) = current!(cx.editor);
|
||||||
|
|
|
@ -202,7 +202,7 @@ impl<T: Item + 'static> Component for Menu<T> {
|
||||||
fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
|
fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
|
||||||
let event = match event {
|
let event = match event {
|
||||||
Event::Key(event) => event,
|
Event::Key(event) => event,
|
||||||
_ => return EventResult::Ignored,
|
_ => 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, _| {
|
||||||
|
@ -252,7 +252,7 @@ impl<T: Item + 'static> Component for Menu<T> {
|
||||||
// for some events, we want to process them but send ignore, specifically all input except
|
// for some events, we want to process them but send ignore, specifically all input except
|
||||||
// tab/enter/ctrl-k or whatever will confirm the selection/ ctrl-n/ctrl-p for scroll.
|
// tab/enter/ctrl-k or whatever will confirm the selection/ ctrl-n/ctrl-p for scroll.
|
||||||
// EventResult::Consumed(None)
|
// EventResult::Consumed(None)
|
||||||
EventResult::Ignored
|
EventResult::Ignored(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
|
fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
|
||||||
|
|
|
@ -419,7 +419,7 @@ impl<T: 'static> Component for Picker<T> {
|
||||||
let key_event = match event {
|
let key_event = match event {
|
||||||
Event::Key(event) => event,
|
Event::Key(event) => event,
|
||||||
Event::Resize(..) => return EventResult::Consumed(None),
|
Event::Resize(..) => return EventResult::Consumed(None),
|
||||||
_ => return EventResult::Ignored,
|
_ => 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, _| {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
compositor::{Component, Compositor, Context, EventResult},
|
compositor::{Callback, Component, Context, EventResult},
|
||||||
ctrl, key,
|
ctrl, key,
|
||||||
};
|
};
|
||||||
use crossterm::event::Event;
|
use crossterm::event::Event;
|
||||||
|
@ -18,6 +18,7 @@ pub struct Popup<T: Component> {
|
||||||
size: (u16, u16),
|
size: (u16, u16),
|
||||||
child_size: (u16, u16),
|
child_size: (u16, u16),
|
||||||
scroll: usize,
|
scroll: usize,
|
||||||
|
auto_close: bool,
|
||||||
id: &'static str,
|
id: &'static str,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +34,7 @@ impl<T: Component> Popup<T> {
|
||||||
size: (0, 0),
|
size: (0, 0),
|
||||||
child_size: (0, 0),
|
child_size: (0, 0),
|
||||||
scroll: 0,
|
scroll: 0,
|
||||||
|
auto_close: false,
|
||||||
id,
|
id,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -46,6 +48,11 @@ impl<T: Component> Popup<T> {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn auto_close(mut self, auto_close: bool) -> Self {
|
||||||
|
self.auto_close = auto_close;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_rel_position(&mut self, viewport: Rect, cx: &Context) -> (u16, u16) {
|
pub fn get_rel_position(&mut self, viewport: Rect, cx: &Context) -> (u16, u16) {
|
||||||
let position = self
|
let position = self
|
||||||
.position
|
.position
|
||||||
|
@ -105,19 +112,19 @@ impl<T: Component> Component for Popup<T> {
|
||||||
Event::Key(event) => event,
|
Event::Key(event) => event,
|
||||||
Event::Resize(_, _) => {
|
Event::Resize(_, _) => {
|
||||||
// TODO: calculate inner area, call component's handle_event with that area
|
// TODO: calculate inner area, call component's handle_event with that area
|
||||||
return EventResult::Ignored;
|
return EventResult::Ignored(None);
|
||||||
}
|
}
|
||||||
_ => return EventResult::Ignored,
|
_ => return EventResult::Ignored(None),
|
||||||
};
|
};
|
||||||
|
|
||||||
let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _| {
|
let close_fn: Callback = Box::new(|compositor, _| {
|
||||||
// remove the layer
|
// remove the layer
|
||||||
compositor.pop();
|
compositor.pop();
|
||||||
})));
|
});
|
||||||
|
|
||||||
match key.into() {
|
match key.into() {
|
||||||
// esc or ctrl-c aborts the completion and closes the menu
|
// esc or ctrl-c aborts the completion and closes the menu
|
||||||
key!(Esc) | ctrl!('c') => close_fn,
|
key!(Esc) | ctrl!('c') => EventResult::Consumed(Some(close_fn)),
|
||||||
ctrl!('d') => {
|
ctrl!('d') => {
|
||||||
self.scroll(self.size.1 as usize / 2, true);
|
self.scroll(self.size.1 as usize / 2, true);
|
||||||
EventResult::Consumed(None)
|
EventResult::Consumed(None)
|
||||||
|
@ -126,7 +133,17 @@ impl<T: Component> Component for Popup<T> {
|
||||||
self.scroll(self.size.1 as usize / 2, false);
|
self.scroll(self.size.1 as usize / 2, false);
|
||||||
EventResult::Consumed(None)
|
EventResult::Consumed(None)
|
||||||
}
|
}
|
||||||
_ => self.contents.handle_event(event, cx),
|
_ => {
|
||||||
|
let contents_event_result = self.contents.handle_event(event, cx);
|
||||||
|
|
||||||
|
if self.auto_close {
|
||||||
|
if let EventResult::Ignored(None) = contents_event_result {
|
||||||
|
return EventResult::Ignored(Some(close_fn));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
contents_event_result
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// for some events, we want to process them but send ignore, specifically all input except
|
// for some events, we want to process them but send ignore, specifically all input except
|
||||||
// tab/enter/ctrl-k or whatever will confirm the selection/ ctrl-n/ctrl-p for scroll.
|
// tab/enter/ctrl-k or whatever will confirm the selection/ ctrl-n/ctrl-p for scroll.
|
||||||
|
|
|
@ -438,7 +438,7 @@ impl Component for Prompt {
|
||||||
let event = match event {
|
let event = match event {
|
||||||
Event::Key(event) => event,
|
Event::Key(event) => event,
|
||||||
Event::Resize(..) => return EventResult::Consumed(None),
|
Event::Resize(..) => return EventResult::Consumed(None),
|
||||||
_ => return EventResult::Ignored,
|
_ => 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, _| {
|
||||||
|
|
Loading…
Reference in New Issue