aboutsummaryrefslogtreecommitdiff
path: root/helix-term/src
diff options
context:
space:
mode:
authorBlaž Hrastnik2021-03-02 09:24:24 +0000
committerBlaž Hrastnik2021-03-02 09:24:24 +0000
commit0b85c16be91d9326876b2e4a1ae6bdc1381d700d (patch)
tree4bbb758097d6b3aecb8791b061bd49a59dc5ad7a /helix-term/src
parent11c4e0b05325d8fd8046a764ec4b9e655486313d (diff)
ui: Share popup code with menu.
Menu is now just wrapped in a popup.
Diffstat (limited to 'helix-term/src')
-rw-r--r--helix-term/src/commands.rs31
-rw-r--r--helix-term/src/compositor.rs8
-rw-r--r--helix-term/src/ui/menu.rs34
-rw-r--r--helix-term/src/ui/mod.rs2
-rw-r--r--helix-term/src/ui/popup.rs47
-rw-r--r--helix-term/src/ui/text.rs41
6 files changed, 86 insertions, 77 deletions
diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs
index 7ffa28a3..bbd78092 100644
--- a/helix-term/src/commands.rs
+++ b/helix-term/src/commands.rs
@@ -1117,16 +1117,8 @@ pub fn completion(cx: &mut Context) {
},
);
- cx.callback = Some(Box::new(
- move |compositor: &mut Compositor, editor: &mut Editor| {
- if let Some(mut pos) = editor.cursor_position() {
- pos.row += 1; // shift down by one row
- menu.set_position(pos);
- };
-
- compositor.push(Box::new(menu));
- },
- ));
+ let popup = Popup::new(Box::new(menu));
+ cx.push_layer(Box::new(popup));
// TODO!: when iterating over items, show the docs in popup
@@ -1171,22 +1163,9 @@ pub fn hover(cx: &mut Context) {
// skip if contents empty
- // Popup: box frame + Box<Component> for internal content.
- // it will use the contents.size_hint/required size to figure out sizing & positioning
- // can also use render_buffer to render the content.
- // render_buffer(highlights/scopes, text, surface, theme)
- //
- let mut popup = Popup::new(contents);
-
- cx.callback = Some(Box::new(
- move |compositor: &mut Compositor, editor: &mut Editor| {
- if let Some(mut pos) = editor.cursor_position() {
- popup.set_position(pos);
- };
-
- compositor.push(Box::new(popup));
- },
- ));
+ let contents = ui::Text::new(contents);
+ let mut popup = Popup::new(Box::new(contents));
+ cx.push_layer(Box::new(popup));
}
}
diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs
index 59e93e03..3c90b76a 100644
--- a/helix-term/src/compositor.rs
+++ b/helix-term/src/compositor.rs
@@ -44,7 +44,9 @@ pub struct Context<'a> {
pub trait Component {
/// 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
+ }
// , args: ()
/// Should redraw? Useful for saving redraw cycles if we know component didn't change.
@@ -57,6 +59,10 @@ pub trait Component {
fn cursor_position(&self, area: Rect, ctx: &Editor) -> Option<Position> {
None
}
+
+ fn size_hint(&self, area: Rect) -> Option<(usize, usize)> {
+ None
+ }
}
// For v1:
diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs
index 7053a179..3fd5ed63 100644
--- a/helix-term/src/ui/menu.rs
+++ b/helix-term/src/ui/menu.rs
@@ -20,8 +20,6 @@ pub struct Menu<T> {
cursor: usize,
- position: Position,
-
format_fn: Box<dyn Fn(&T) -> Cow<str>>,
callback_fn: Box<dyn Fn(&mut Editor, Option<&T>, MenuEvent)>,
}
@@ -37,16 +35,11 @@ impl<T> Menu<T> {
Self {
options,
cursor: 0,
- position: Position::default(),
format_fn: Box::new(format_fn),
callback_fn: Box::new(callback_fn),
}
}
- pub fn set_position(&mut self, pos: Position) {
- self.position = pos;
- }
-
pub fn move_up(&mut self) {
self.cursor = self.cursor.saturating_sub(1);
}
@@ -151,31 +144,18 @@ impl<T> Component for Menu<T> {
// EventResult::Consumed(None)
EventResult::Ignored
}
- fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
- // render a box at x, y. Width equal to max width of item.
- // initially limit to n items, add support for scrolling
- //
- const MAX: usize = 5;
- let rows = std::cmp::min(self.options.len(), MAX) as u16;
- let area = Rect::new(self.position.col as u16, self.position.row as u16, 30, rows);
-
- // clear area
- let background = cx.editor.theme.get("ui.popup");
- for y in area.top()..area.bottom() {
- for x in area.left()..area.right() {
- let cell = surface.get_mut(x, y);
- cell.reset();
- // cell.symbol.clear();
- cell.set_style(background);
- }
- }
- // -- Render the contents:
+ fn size_hint(&self, area: Rect) -> Option<(usize, usize)> {
+ const MAX: usize = 5;
+ let height = std::cmp::min(self.options.len(), MAX);
+ Some((30, height))
+ }
+ fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
let style = Style::default().fg(Color::Rgb(164, 160, 232)); // lavender
let selected = Style::default().fg(Color::Rgb(255, 255, 255));
- for (i, option) in self.options.iter().take(rows as usize).enumerate() {
+ for (i, option) in self.options.iter().take(area.height as usize).enumerate() {
// TODO: set bg for the whole row if selected
surface.set_stringn(
area.x,
diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs
index 1526a210..4fbdd550 100644
--- a/helix-term/src/ui/mod.rs
+++ b/helix-term/src/ui/mod.rs
@@ -3,12 +3,14 @@ mod menu;
mod picker;
mod popup;
mod prompt;
+mod text;
pub use editor::EditorView;
pub use menu::Menu;
pub use picker::Picker;
pub use popup::Popup;
pub use prompt::{Prompt, PromptEvent};
+pub use text::Text;
pub use tui::layout::Rect;
pub use tui::style::{Color, Modifier, Style};
diff --git a/helix-term/src/ui/popup.rs b/helix-term/src/ui/popup.rs
index 673321dc..ba32e6b5 100644
--- a/helix-term/src/ui/popup.rs
+++ b/helix-term/src/ui/popup.rs
@@ -16,28 +16,28 @@ use helix_view::Editor;
// a width/height hint. maybe Popup(Box<Component>)
pub struct Popup {
- contents: String,
- position: Position,
+ contents: Box<dyn Component>,
+ position: Option<Position>,
}
impl Popup {
// TODO: it's like a slimmed down picker, share code? (picker = menu + prompt with different
// rendering)
- pub fn new(contents: String) -> Self {
+ pub fn new(contents: Box<dyn Component>) -> Self {
Self {
contents,
- position: Position::default(),
+ position: None,
}
}
- pub fn set_position(&mut self, pos: Position) {
+ pub fn set_position(&mut self, pos: Option<Position>) {
self.position = pos;
}
}
impl Component for Popup {
fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
- let event = match event {
+ let key = match event {
Event::Key(event) => event,
_ => return EventResult::Ignored,
};
@@ -49,7 +49,7 @@ impl Component for Popup {
},
)));
- match event {
+ match key {
// esc or ctrl-c aborts the completion and closes the menu
KeyEvent {
code: KeyCode::Esc, ..
@@ -60,29 +60,37 @@ impl Component for Popup {
} => {
return close_fn;
}
- _ => (),
+ _ => self.contents.handle_event(event, cx),
}
// 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::Consumed(None)
}
fn render(&self, viewport: Rect, surface: &mut Surface, cx: &mut Context) {
use tui::text::Text;
use tui::widgets::{Paragraph, Widget, Wrap};
- let contents = Text::from(self.contents.clone());
+ let position = self
+ .position
+ .or_else(|| cx.editor.cursor_position())
+ .unwrap_or_default();
- let width = contents.width().min(150) as u16;
- let height = contents.height().min(13) as u16;
+ let (width, height) = self
+ .contents
+ .size_hint(viewport)
+ .expect("Component needs size_hint implemented in order to be embedded in a popup");
+
+ let width = width.min(150) as u16;
+ let height = height.min(13) as u16;
// -- make sure frame doesn't stick out of bounds
- let mut rel_x = self.position.col as u16;
- let mut rel_y = self.position.row as u16;
+ let mut rel_x = position.col as u16;
+ let mut rel_y = position.row as u16;
if viewport.width <= rel_x + width {
rel_x -= ((rel_x + width) - viewport.width)
};
+ // TODO: be able to specify orientation preference. We want above for most popups, below
+ // for menus/autocomplete.
if height <= rel_y {
rel_y -= height // position above point
} else {
@@ -104,13 +112,6 @@ impl Component for Popup {
}
}
- // -- Render the contents:
-
- let style = Style::default().fg(Color::Rgb(164, 160, 232)); // lavender
-
- let par = Paragraph::new(contents).wrap(Wrap { trim: false });
- // .scroll(x, y) offsets
-
- par.render(area, surface);
+ self.contents.render(area, surface, cx);
}
}
diff --git a/helix-term/src/ui/text.rs b/helix-term/src/ui/text.rs
new file mode 100644
index 00000000..bacb68b8
--- /dev/null
+++ b/helix-term/src/ui/text.rs
@@ -0,0 +1,41 @@
+use crate::compositor::{Component, Compositor, Context, EventResult};
+use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
+use tui::buffer::Buffer as Surface;
+use tui::{
+ layout::Rect,
+ style::{Color, Style},
+ widgets::{Block, Borders},
+};
+
+use std::borrow::Cow;
+
+use helix_core::Position;
+use helix_view::Editor;
+
+pub struct Text {
+ contents: String,
+}
+
+impl Text {
+ pub fn new(contents: String) -> Self {
+ Self { contents }
+ }
+}
+impl Component for Text {
+ fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
+ use tui::widgets::{Paragraph, Widget, Wrap};
+ let contents = tui::text::Text::from(self.contents.clone());
+
+ let style = Style::default().fg(Color::Rgb(164, 160, 232)); // lavender
+
+ let par = Paragraph::new(contents).wrap(Wrap { trim: false });
+ // .scroll(x, y) offsets
+
+ par.render(area, surface);
+ }
+
+ fn size_hint(&self, area: Rect) -> Option<(usize, usize)> {
+ let contents = tui::text::Text::from(self.contents.clone());
+ Some((contents.width(), contents.height()))
+ }
+}