From 40eb1268c72b60f1249389c58892e7c45d268cff Mon Sep 17 00:00:00 2001 From: Bram Date: Wed, 23 Feb 2022 04:46:12 +0100 Subject: [PATCH] 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 formatting --- helix-term/src/commands.rs | 2 +- helix-term/src/commands/lsp.rs | 2 +- helix-term/src/compositor.rs | 29 ++++++++++++++++++++++------- helix-term/src/ui/completion.rs | 2 +- helix-term/src/ui/editor.rs | 24 +++++++++++------------- helix-term/src/ui/menu.rs | 4 ++-- helix-term/src/ui/picker.rs | 2 +- helix-term/src/ui/popup.rs | 31 ++++++++++++++++++++++++------- helix-term/src/ui/prompt.rs | 2 +- 9 files changed, 64 insertions(+), 34 deletions(-) diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 4cded7ef..1272cc8a 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -2921,7 +2921,7 @@ pub mod cmd { let call: job::Callback = Box::new(move |editor: &mut Editor, compositor: &mut Compositor| { 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)); }); Ok(call) diff --git a/helix-term/src/commands/lsp.rs b/helix-term/src/commands/lsp.rs index af11cd4e..798dedc6 100644 --- a/helix-term/src/commands/lsp.rs +++ b/helix-term/src/commands/lsp.rs @@ -633,7 +633,7 @@ pub fn hover(cx: &mut Context) { // skip if contents empty 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)); } }, diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs index dd7ebe1d..2f80de00 100644 --- a/helix-term/src/compositor.rs +++ b/helix-term/src/compositor.rs @@ -19,7 +19,7 @@ pub type Callback = Box; // Cursive-inspired pub enum EventResult { - Ignored, + Ignored(Option), Consumed(Option), } @@ -36,7 +36,7 @@ pub struct Context<'a> { pub trait Component: Any + AnyComponent { /// Process input events, return true if handled. fn handle_event(&mut self, _event: Event, _ctx: &mut Context) -> EventResult { - EventResult::Ignored + EventResult::Ignored(None) } // , args: () @@ -146,19 +146,34 @@ impl Compositor { 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 // run out of layers (event bubbling) for layer in self.layers.iter_mut().rev() { match layer.handle_event(event, cx) { EventResult::Consumed(Some(callback)) => { - callback(self, cx); - return true; + callbacks.push(callback); + consumed = true; + break; + } + EventResult::Consumed(None) => { + consumed = true; + break; + } + EventResult::Ignored(Some(callback)) => { + callbacks.push(callback); } - EventResult::Consumed(None) => return true, - EventResult::Ignored => false, + EventResult::Ignored(None) => {} }; } - false + + for callback in callbacks { + callback(self, cx) + } + + consumed } pub fn render(&mut self, cx: &mut Context) { diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index d3b618a9..adaac1a7 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -276,7 +276,7 @@ impl Component for Completion { code: KeyCode::Esc, .. }) = event { - return EventResult::Ignored; + return EventResult::Ignored(None); } self.popup.handle_event(event, cx) } diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index fc749ebb..29552b67 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -872,9 +872,7 @@ impl EditorView { let path = match doc.path() { Some(path) => path.clone(), - None => { - return EventResult::Ignored; - } + None => return EventResult::Ignored(None), }; let line = coords.row + view.offset.row; @@ -884,7 +882,7 @@ impl EditorView { } } - EventResult::Ignored + EventResult::Ignored(None) } MouseEvent { @@ -897,7 +895,7 @@ impl EditorView { let pos = match view.pos_at_screen_coords(doc, row, column) { Some(pos) => pos, - None => return EventResult::Ignored, + None => return EventResult::Ignored(None), }; let mut selection = doc.selection(view.id).clone(); @@ -928,7 +926,7 @@ impl EditorView { match result { 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; @@ -944,14 +942,14 @@ impl EditorView { .. } => { if !cxt.editor.config.middle_click_paste { - return EventResult::Ignored; + return EventResult::Ignored(None); } let (view, doc) = current!(cxt.editor); let range = doc.selection(view.id).primary(); if range.to() - range.from() <= 1 { - return EventResult::Ignored; + return EventResult::Ignored(None); } commands::MappableCommand::yank_main_selection_to_primary_clipboard.execute(cxt); @@ -988,7 +986,7 @@ impl EditorView { return EventResult::Consumed(None); } } - EventResult::Ignored + EventResult::Ignored(None) } MouseEvent { @@ -1000,7 +998,7 @@ impl EditorView { } => { let editor = &mut cxt.editor; if !editor.config.middle_click_paste { - return EventResult::Ignored; + return EventResult::Ignored(None); } if modifiers == crossterm::event::KeyModifiers::ALT { @@ -1023,10 +1021,10 @@ impl EditorView { 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. // on the next loop cycle the Application will then terminate. if cx.editor.should_close() { - return EventResult::Ignored; + return EventResult::Ignored(None); } let (view, doc) = current!(cx.editor); diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index f9a0438c..1839da47 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -202,7 +202,7 @@ impl Component for Menu { fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { let event = match event { Event::Key(event) => event, - _ => return EventResult::Ignored, + _ => return EventResult::Ignored(None), }; let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _| { @@ -252,7 +252,7 @@ impl Component for Menu { // 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. // EventResult::Consumed(None) - EventResult::Ignored + EventResult::Ignored(None) } fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> { diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 9e236510..34709e8b 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -419,7 +419,7 @@ impl Component for Picker { let key_event = match event { Event::Key(event) => event, Event::Resize(..) => return EventResult::Consumed(None), - _ => return EventResult::Ignored, + _ => return EventResult::Ignored(None), }; let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _| { diff --git a/helix-term/src/ui/popup.rs b/helix-term/src/ui/popup.rs index 4d319423..45527482 100644 --- a/helix-term/src/ui/popup.rs +++ b/helix-term/src/ui/popup.rs @@ -1,5 +1,5 @@ use crate::{ - compositor::{Component, Compositor, Context, EventResult}, + compositor::{Callback, Component, Context, EventResult}, ctrl, key, }; use crossterm::event::Event; @@ -18,6 +18,7 @@ pub struct Popup { size: (u16, u16), child_size: (u16, u16), scroll: usize, + auto_close: bool, id: &'static str, } @@ -33,6 +34,7 @@ impl Popup { size: (0, 0), child_size: (0, 0), scroll: 0, + auto_close: false, id, } } @@ -46,6 +48,11 @@ impl Popup { 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) { let position = self .position @@ -105,19 +112,19 @@ impl Component for Popup { Event::Key(event) => event, Event::Resize(_, _) => { // 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 compositor.pop(); - }))); + }); match key.into() { // 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') => { self.scroll(self.size.1 as usize / 2, true); EventResult::Consumed(None) @@ -126,7 +133,17 @@ impl Component for Popup { self.scroll(self.size.1 as usize / 2, false); 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 // tab/enter/ctrl-k or whatever will confirm the selection/ ctrl-n/ctrl-p for scroll. diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index cd8e14ee..c3402f02 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -438,7 +438,7 @@ impl Component for Prompt { let event = match event { Event::Key(event) => event, Event::Resize(..) => return EventResult::Consumed(None), - _ => return EventResult::Ignored, + _ => return EventResult::Ignored(None), }; let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _| {