on master help sign in
Close some popups automatically (#1285)
40eb126Bram
helix-term/src/commands.rs+1−1
@@ -2921,7 +2921,7 @@ pub mod cmd {
2921 let call: job::Callback =
2921 let call: job::Callback =
2922 Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
2922 Box::new(move |editor: &mut Editor, compositor: &mut Compositor| {
2923 let contents = ui::Markdown::new(contents, editor.syn_loader.clone());
2923 let contents = ui::Markdown::new(contents, editor.syn_loader.clone());
2924− let popup = Popup::new("hover", contents);
2924+ let popup = Popup::new("hover", contents).auto_close(true);
2925 compositor.replace_or_push("hover", Box::new(popup));
2925 compositor.replace_or_push("hover", Box::new(popup));
2926 });
2926 });
2927 Ok(call)
2927 Ok(call)
helix-term/src/commands/lsp.rs+1−1
@@ -633,7 +633,7 @@ pub fn hover(cx: &mut Context) {
633 // skip if contents empty
633 // skip if contents empty
634
634
635 let contents = ui::Markdown::new(contents, editor.syn_loader.clone());
635 let contents = ui::Markdown::new(contents, editor.syn_loader.clone());
636− let popup = Popup::new("hover", contents);
636+ let popup = Popup::new("hover", contents).auto_close(true);
637 compositor.replace_or_push("hover", Box::new(popup));
637 compositor.replace_or_push("hover", Box::new(popup));
638 }
638 }
639 },
639 },
helix-term/src/compositor.rs+22−7
@@ -19,7 +19,7 @@ pub type Callback = Box<dyn FnOnce(&mut Compositor, &mut Context)>;
19
19
20// Cursive-inspired
20// Cursive-inspired
21pub enum EventResult {
21pub enum EventResult {
22− Ignored,
22+ Ignored(Option<Callback>),
23 Consumed(Option<Callback>),
23 Consumed(Option<Callback>),
24}
24}
25
25
@@ -36,7 +36,7 @@ pub struct Context<'a> {
36pub trait Component: Any + AnyComponent {
36pub trait Component: Any + AnyComponent {
37 /// Process input events, return true if handled.
37 /// Process input events, return true if handled.
38 fn handle_event(&mut self, _event: Event, _ctx: &mut Context) -> EventResult {
38 fn handle_event(&mut self, _event: Event, _ctx: &mut Context) -> EventResult {
39− EventResult::Ignored
39+ EventResult::Ignored(None)
40 }
40 }
41 // , args: ()
41 // , args: ()
42
42
@@ -146,19 +146,34 @@ impl Compositor {
146 keys.push(key.into());
146 keys.push(key.into());
147 }
147 }
148
148
149+ let mut callbacks = Vec::new();
150+ let mut consumed = false;
151+
149 // propagate events through the layers until we either find a layer that consumes it or we
152 // propagate events through the layers until we either find a layer that consumes it or we
150 // run out of layers (event bubbling)
153 // run out of layers (event bubbling)
151 for layer in self.layers.iter_mut().rev() {
154 for layer in self.layers.iter_mut().rev() {
152 match layer.handle_event(event, cx) {
155 match layer.handle_event(event, cx) {
153 EventResult::Consumed(Some(callback)) => {
156 EventResult::Consumed(Some(callback)) => {
154− callback(self, cx);
157+ callbacks.push(callback);
155− return true;
158+ consumed = true;
159+ break;
160+ }
161+ EventResult::Consumed(None) => {
162+ consumed = true;
163+ break;
164+ }
165+ EventResult::Ignored(Some(callback)) => {
166+ callbacks.push(callback);
156 }
167 }
157− EventResult::Consumed(None) => return true,
168+ EventResult::Ignored(None) => {}
158− EventResult::Ignored => false,
159 };
169 };
160 }
170 }
161− false
171+
172+ for callback in callbacks {
173+ callback(self, cx)
174+ }
175+
176+ consumed
162 }
177 }
163
178
164 pub fn render(&mut self, cx: &mut Context) {
179 pub fn render(&mut self, cx: &mut Context) {
helix-term/src/ui/completion.rs+1−1
@@ -276,7 +276,7 @@ impl Component for Completion {
276 code: KeyCode::Esc, ..
276 code: KeyCode::Esc, ..
277 }) = event
277 }) = event
278 {
278 {
279− return EventResult::Ignored;
279+ return EventResult::Ignored(None);
280 }
280 }
281 self.popup.handle_event(event, cx)
281 self.popup.handle_event(event, cx)
282 }
282 }
helix-term/src/ui/editor.rs+11−13
@@ -872,9 +872,7 @@ impl EditorView {
872
872
873 let path = match doc.path() {
873 let path = match doc.path() {
874 Some(path) => path.clone(),
874 Some(path) => path.clone(),
875− None => {
876− return EventResult::Ignored;
875+ None => return EventResult::Ignored(None),
877− }
878 };
876 };
879
877
880 let line = coords.row + view.offset.row;
878 let line = coords.row + view.offset.row;
@@ -884,7 +882,7 @@ impl EditorView {
884 }
882 }
885 }
883 }
886
884
887− EventResult::Ignored
885+ EventResult::Ignored(None)
888 }
886 }
889
887
890 MouseEvent {
888 MouseEvent {
@@ -897,7 +895,7 @@ impl EditorView {
897
895
898 let pos = match view.pos_at_screen_coords(doc, row, column) {
896 let pos = match view.pos_at_screen_coords(doc, row, column) {
899 Some(pos) => pos,
897 Some(pos) => pos,
900− None => return EventResult::Ignored,
898+ None => return EventResult::Ignored(None),
901 };
899 };
902
900
903 let mut selection = doc.selection(view.id).clone();
901 let mut selection = doc.selection(view.id).clone();
@@ -928,7 +926,7 @@ impl EditorView {
928
926
929 match result {
927 match result {
930 Some(view_id) => cxt.editor.tree.focus = view_id,
928 Some(view_id) => cxt.editor.tree.focus = view_id,
931− None => return EventResult::Ignored,
929+ None => return EventResult::Ignored(None),
932 }
930 }
933
931
934 let offset = cxt.editor.config.scroll_lines.abs() as usize;
932 let offset = cxt.editor.config.scroll_lines.abs() as usize;
@@ -944,14 +942,14 @@ impl EditorView {
944 ..
942 ..
945 } => {
943 } => {
946 if !cxt.editor.config.middle_click_paste {
944 if !cxt.editor.config.middle_click_paste {
947− return EventResult::Ignored;
945+ return EventResult::Ignored(None);
948 }
946 }
949
947
950 let (view, doc) = current!(cxt.editor);
948 let (view, doc) = current!(cxt.editor);
951 let range = doc.selection(view.id).primary();
949 let range = doc.selection(view.id).primary();
952
950
953 if range.to() - range.from() <= 1 {
951 if range.to() - range.from() <= 1 {
954− return EventResult::Ignored;
952+ return EventResult::Ignored(None);
955 }
953 }
956
954
957 commands::MappableCommand::yank_main_selection_to_primary_clipboard.execute(cxt);
955 commands::MappableCommand::yank_main_selection_to_primary_clipboard.execute(cxt);
@@ -988,7 +986,7 @@ impl EditorView {
988 return EventResult::Consumed(None);
986 return EventResult::Consumed(None);
989 }
987 }
990 }
988 }
991− EventResult::Ignored
989+ EventResult::Ignored(None)
992 }
990 }
993
991
994 MouseEvent {
992 MouseEvent {
@@ -1000,7 +998,7 @@ impl EditorView {
1000 } => {
998 } => {
1001 let editor = &mut cxt.editor;
999 let editor = &mut cxt.editor;
1002 if !editor.config.middle_click_paste {
1000 if !editor.config.middle_click_paste {
1003− return EventResult::Ignored;
1001+ return EventResult::Ignored(None);
1004 }
1002 }
1005
1003
1006 if modifiers == crossterm::event::KeyModifiers::ALT {
1004 if modifiers == crossterm::event::KeyModifiers::ALT {
@@ -1023,10 +1021,10 @@ impl EditorView {
1023 return EventResult::Consumed(None);
1021 return EventResult::Consumed(None);
1024 }
1022 }
1025
1023
1026− EventResult::Ignored
1024+ EventResult::Ignored(None)
1027 }
1025 }
1028
1026
1029− _ => EventResult::Ignored,
1027+ _ => EventResult::Ignored(None),
1030 }
1028 }
1031 }
1029 }
1032}
1030}
@@ -1117,7 +1115,7 @@ impl Component for EditorView {
1117 // if the command consumed the last view, skip the render.
1115 // if the command consumed the last view, skip the render.
1118 // on the next loop cycle the Application will then terminate.
1116 // on the next loop cycle the Application will then terminate.
1119 if cx.editor.should_close() {
1117 if cx.editor.should_close() {
1120− return EventResult::Ignored;
1118+ return EventResult::Ignored(None);
1121 }
1119 }
1122
1120
1123 let (view, doc) = current!(cx.editor);
1121 let (view, doc) = current!(cx.editor);
helix-term/src/ui/menu.rs+2−2
@@ -202,7 +202,7 @@ impl<T: Item + 'static> Component for Menu<T> {
202 fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
202 fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
203 let event = match event {
203 let event = match event {
204 Event::Key(event) => event,
204 Event::Key(event) => event,
205− _ => return EventResult::Ignored,
205+ _ => return EventResult::Ignored(None),
206 };
206 };
207
207
208 let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _| {
208 let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _| {
@@ -252,7 +252,7 @@ impl<T: Item + 'static> Component for Menu<T> {
252 // for some events, we want to process them but send ignore, specifically all input except
252 // for some events, we want to process them but send ignore, specifically all input except
253 // tab/enter/ctrl-k or whatever will confirm the selection/ ctrl-n/ctrl-p for scroll.
253 // tab/enter/ctrl-k or whatever will confirm the selection/ ctrl-n/ctrl-p for scroll.
254 // EventResult::Consumed(None)
254 // EventResult::Consumed(None)
255− EventResult::Ignored
255+ EventResult::Ignored(None)
256 }
256 }
257
257
258 fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
258 fn required_size(&mut self, viewport: (u16, u16)) -> Option<(u16, u16)> {
helix-term/src/ui/picker.rs+1−1
@@ -419,7 +419,7 @@ impl<T: 'static> Component for Picker<T> {
419 let key_event = match event {
419 let key_event = match event {
420 Event::Key(event) => event,
420 Event::Key(event) => event,
421 Event::Resize(..) => return EventResult::Consumed(None),
421 Event::Resize(..) => return EventResult::Consumed(None),
422− _ => return EventResult::Ignored,
422+ _ => return EventResult::Ignored(None),
423 };
423 };
424
424
425 let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _| {
425 let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _| {
helix-term/src/ui/popup.rs+24−7
@@ -1,5 +1,5 @@
1use crate::{
1use crate::{
2− compositor::{Component, Compositor, Context, EventResult},
2+ compositor::{Callback, Component, Context, EventResult},
3 ctrl, key,
3 ctrl, key,
4};
4};
5use crossterm::event::Event;
5use crossterm::event::Event;
@@ -18,6 +18,7 @@ pub struct Popup<T: Component> {
18 size: (u16, u16),
18 size: (u16, u16),
19 child_size: (u16, u16),
19 child_size: (u16, u16),
20 scroll: usize,
20 scroll: usize,
21+ auto_close: bool,
21 id: &'static str,
22 id: &'static str,
22}
23}
23
24
@@ -33,6 +34,7 @@ impl<T: Component> Popup<T> {
33 size: (0, 0),
34 size: (0, 0),
34 child_size: (0, 0),
35 child_size: (0, 0),
35 scroll: 0,
36 scroll: 0,
37+ auto_close: false,
36 id,
38 id,
37 }
39 }
38 }
40 }
@@ -46,6 +48,11 @@ impl<T: Component> Popup<T> {
46 self
48 self
47 }
49 }
48
50
51+ pub fn auto_close(mut self, auto_close: bool) -> Self {
52+ self.auto_close = auto_close;
53+ self
54+ }
55+
49 pub fn get_rel_position(&mut self, viewport: Rect, cx: &Context) -> (u16, u16) {
56 pub fn get_rel_position(&mut self, viewport: Rect, cx: &Context) -> (u16, u16) {
50 let position = self
57 let position = self
51 .position
58 .position
@@ -105,19 +112,19 @@ impl<T: Component> Component for Popup<T> {
105 Event::Key(event) => event,
112 Event::Key(event) => event,
106 Event::Resize(_, _) => {
113 Event::Resize(_, _) => {
107 // TODO: calculate inner area, call component's handle_event with that area
114 // TODO: calculate inner area, call component's handle_event with that area
108− return EventResult::Ignored;
115+ return EventResult::Ignored(None);
109 }
116 }
110− _ => return EventResult::Ignored,
117+ _ => return EventResult::Ignored(None),
111 };
118 };
112
119
113− let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _| {
120+ let close_fn: Callback = Box::new(|compositor, _| {
114 // remove the layer
121 // remove the layer
115 compositor.pop();
122 compositor.pop();
116− })));
123+ });
117
124
118 match key.into() {
125 match key.into() {
119 // esc or ctrl-c aborts the completion and closes the menu
126 // esc or ctrl-c aborts the completion and closes the menu
120− key!(Esc) | ctrl!('c') => close_fn,
127+ key!(Esc) | ctrl!('c') => EventResult::Consumed(Some(close_fn)),
121 ctrl!('d') => {
128 ctrl!('d') => {
122 self.scroll(self.size.1 as usize / 2, true);
129 self.scroll(self.size.1 as usize / 2, true);
123 EventResult::Consumed(None)
130 EventResult::Consumed(None)
@@ -126,7 +133,17 @@ impl<T: Component> Component for Popup<T> {
126 self.scroll(self.size.1 as usize / 2, false);
133 self.scroll(self.size.1 as usize / 2, false);
127 EventResult::Consumed(None)
134 EventResult::Consumed(None)
128 }
135 }
136+ _ => {
129− _ => self.contents.handle_event(event, cx),
137+ let contents_event_result = self.contents.handle_event(event, cx);
138+
139+ if self.auto_close {
140+ if let EventResult::Ignored(None) = contents_event_result {
141+ return EventResult::Ignored(Some(close_fn));
142+ }
143+ }
144+
145+ contents_event_result
146+ }
130 }
147 }
131 // for some events, we want to process them but send ignore, specifically all input except
148 // for some events, we want to process them but send ignore, specifically all input except
132 // tab/enter/ctrl-k or whatever will confirm the selection/ ctrl-n/ctrl-p for scroll.
149 // tab/enter/ctrl-k or whatever will confirm the selection/ ctrl-n/ctrl-p for scroll.
helix-term/src/ui/prompt.rs+1−1
@@ -438,7 +438,7 @@ impl Component for Prompt {
438 let event = match event {
438 let event = match event {
439 Event::Key(event) => event,
439 Event::Key(event) => event,
440 Event::Resize(..) => return EventResult::Consumed(None),
440 Event::Resize(..) => return EventResult::Consumed(None),
441− _ => return EventResult::Ignored,
441+ _ => return EventResult::Ignored(None),
442 };
442 };
443
443
444 let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _| {
444 let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _| {