mirror of https://github.com/helix-editor/helix
parent
11c4e0b053
commit
0b85c16be9
|
@ -1117,16 +1117,8 @@ pub fn completion(cx: &mut Context) {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
cx.callback = Some(Box::new(
|
let popup = Popup::new(Box::new(menu));
|
||||||
move |compositor: &mut Compositor, editor: &mut Editor| {
|
cx.push_layer(Box::new(popup));
|
||||||
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));
|
|
||||||
},
|
|
||||||
));
|
|
||||||
|
|
||||||
// TODO!: when iterating over items, show the docs in 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
|
// skip if contents empty
|
||||||
|
|
||||||
// Popup: box frame + Box<Component> for internal content.
|
let contents = ui::Text::new(contents);
|
||||||
// it will use the contents.size_hint/required size to figure out sizing & positioning
|
let mut popup = Popup::new(Box::new(contents));
|
||||||
// can also use render_buffer to render the content.
|
cx.push_layer(Box::new(popup));
|
||||||
// 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));
|
|
||||||
},
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,9 @@ pub struct Context<'a> {
|
||||||
|
|
||||||
pub trait Component {
|
pub trait Component {
|
||||||
/// 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
|
||||||
|
}
|
||||||
// , args: ()
|
// , args: ()
|
||||||
|
|
||||||
/// Should redraw? Useful for saving redraw cycles if we know component didn't change.
|
/// 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> {
|
fn cursor_position(&self, area: Rect, ctx: &Editor) -> Option<Position> {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn size_hint(&self, area: Rect) -> Option<(usize, usize)> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For v1:
|
// For v1:
|
||||||
|
|
|
@ -20,8 +20,6 @@ pub struct Menu<T> {
|
||||||
|
|
||||||
cursor: usize,
|
cursor: usize,
|
||||||
|
|
||||||
position: Position,
|
|
||||||
|
|
||||||
format_fn: Box<dyn Fn(&T) -> Cow<str>>,
|
format_fn: Box<dyn Fn(&T) -> Cow<str>>,
|
||||||
callback_fn: Box<dyn Fn(&mut Editor, Option<&T>, MenuEvent)>,
|
callback_fn: Box<dyn Fn(&mut Editor, Option<&T>, MenuEvent)>,
|
||||||
}
|
}
|
||||||
|
@ -37,16 +35,11 @@ impl<T> Menu<T> {
|
||||||
Self {
|
Self {
|
||||||
options,
|
options,
|
||||||
cursor: 0,
|
cursor: 0,
|
||||||
position: Position::default(),
|
|
||||||
format_fn: Box::new(format_fn),
|
format_fn: Box::new(format_fn),
|
||||||
callback_fn: Box::new(callback_fn),
|
callback_fn: Box::new(callback_fn),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_position(&mut self, pos: Position) {
|
|
||||||
self.position = pos;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn move_up(&mut self) {
|
pub fn move_up(&mut self) {
|
||||||
self.cursor = self.cursor.saturating_sub(1);
|
self.cursor = self.cursor.saturating_sub(1);
|
||||||
}
|
}
|
||||||
|
@ -151,31 +144,18 @@ impl<T> Component for Menu<T> {
|
||||||
// EventResult::Consumed(None)
|
// EventResult::Consumed(None)
|
||||||
EventResult::Ignored
|
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.
|
fn size_hint(&self, area: Rect) -> Option<(usize, usize)> {
|
||||||
// initially limit to n items, add support for scrolling
|
|
||||||
//
|
|
||||||
const MAX: usize = 5;
|
const MAX: usize = 5;
|
||||||
let rows = std::cmp::min(self.options.len(), MAX) as u16;
|
let height = std::cmp::min(self.options.len(), MAX);
|
||||||
let area = Rect::new(self.position.col as u16, self.position.row as u16, 30, rows);
|
Some((30, height))
|
||||||
|
}
|
||||||
// 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 render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
|
||||||
let style = Style::default().fg(Color::Rgb(164, 160, 232)); // lavender
|
let style = Style::default().fg(Color::Rgb(164, 160, 232)); // lavender
|
||||||
let selected = Style::default().fg(Color::Rgb(255, 255, 255));
|
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
|
// TODO: set bg for the whole row if selected
|
||||||
surface.set_stringn(
|
surface.set_stringn(
|
||||||
area.x,
|
area.x,
|
||||||
|
|
|
@ -3,12 +3,14 @@ mod menu;
|
||||||
mod picker;
|
mod picker;
|
||||||
mod popup;
|
mod popup;
|
||||||
mod prompt;
|
mod prompt;
|
||||||
|
mod text;
|
||||||
|
|
||||||
pub use editor::EditorView;
|
pub use editor::EditorView;
|
||||||
pub use menu::Menu;
|
pub use menu::Menu;
|
||||||
pub use picker::Picker;
|
pub use picker::Picker;
|
||||||
pub use popup::Popup;
|
pub use popup::Popup;
|
||||||
pub use prompt::{Prompt, PromptEvent};
|
pub use prompt::{Prompt, PromptEvent};
|
||||||
|
pub use text::Text;
|
||||||
|
|
||||||
pub use tui::layout::Rect;
|
pub use tui::layout::Rect;
|
||||||
pub use tui::style::{Color, Modifier, Style};
|
pub use tui::style::{Color, Modifier, Style};
|
||||||
|
|
|
@ -16,28 +16,28 @@ use helix_view::Editor;
|
||||||
// a width/height hint. maybe Popup(Box<Component>)
|
// a width/height hint. maybe Popup(Box<Component>)
|
||||||
|
|
||||||
pub struct Popup {
|
pub struct Popup {
|
||||||
contents: String,
|
contents: Box<dyn Component>,
|
||||||
position: Position,
|
position: Option<Position>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Popup {
|
impl Popup {
|
||||||
// TODO: it's like a slimmed down picker, share code? (picker = menu + prompt with different
|
// TODO: it's like a slimmed down picker, share code? (picker = menu + prompt with different
|
||||||
// rendering)
|
// rendering)
|
||||||
pub fn new(contents: String) -> Self {
|
pub fn new(contents: Box<dyn Component>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
contents,
|
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;
|
self.position = pos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for Popup {
|
impl Component for Popup {
|
||||||
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 key = match event {
|
||||||
Event::Key(event) => event,
|
Event::Key(event) => event,
|
||||||
_ => return EventResult::Ignored,
|
_ => 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
|
// esc or ctrl-c aborts the completion and closes the menu
|
||||||
KeyEvent {
|
KeyEvent {
|
||||||
code: KeyCode::Esc, ..
|
code: KeyCode::Esc, ..
|
||||||
|
@ -60,29 +60,37 @@ impl Component for Popup {
|
||||||
} => {
|
} => {
|
||||||
return close_fn;
|
return close_fn;
|
||||||
}
|
}
|
||||||
_ => (),
|
_ => self.contents.handle_event(event, cx),
|
||||||
}
|
}
|
||||||
// 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)
|
|
||||||
}
|
}
|
||||||
fn render(&self, viewport: Rect, surface: &mut Surface, cx: &mut Context) {
|
fn render(&self, viewport: Rect, surface: &mut Surface, cx: &mut Context) {
|
||||||
use tui::text::Text;
|
use tui::text::Text;
|
||||||
use tui::widgets::{Paragraph, Widget, Wrap};
|
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 (width, height) = self
|
||||||
let height = contents.height().min(13) as u16;
|
.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
|
// -- make sure frame doesn't stick out of bounds
|
||||||
let mut rel_x = self.position.col as u16;
|
let mut rel_x = position.col as u16;
|
||||||
let mut rel_y = self.position.row as u16;
|
let mut rel_y = position.row as u16;
|
||||||
if viewport.width <= rel_x + width {
|
if viewport.width <= rel_x + width {
|
||||||
rel_x -= ((rel_x + width) - viewport.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 {
|
if height <= rel_y {
|
||||||
rel_y -= height // position above point
|
rel_y -= height // position above point
|
||||||
} else {
|
} else {
|
||||||
|
@ -104,13 +112,6 @@ impl Component for Popup {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -- Render the contents:
|
self.contents.render(area, surface, cx);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue