@@ -0,0 +1,189 @@
1+use crate::compositor::{Component, Compositor, Context, EventResult};
2+use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
3+use tui::buffer::Buffer as Surface;
6+ style::{Color, Style},
7+ widgets::{Block, Borders},
12+use helix_core::Position;
13+use helix_view::Editor;
15+// TODO: factor out a popup component that we can reuse for displaying docs on autocomplete,
16+// diagnostics popups, etc.
25+ format_fn: Box<dyn Fn(&T) -> Cow<str>>,
26+ callback_fn: Box<dyn Fn(&mut Editor, Option<&T>, MenuEvent)>,
30+ // TODO: it's like a slimmed down picker, share code? (picker = menu + prompt with different
34+ format_fn: impl Fn(&T) -> Cow<str> + 'static,
35+ callback_fn: impl Fn(&mut Editor, Option<&T>, MenuEvent) + 'static,
40+ position: Position::default(),
41+ format_fn: Box::new(format_fn),
42+ callback_fn: Box::new(callback_fn),
46+ pub fn set_position(&mut self, pos: Position) {
50+ pub fn move_up(&mut self) {
51+ self.cursor = self.cursor.saturating_sub(1);
54+ pub fn move_down(&mut self) {
56+ if self.cursor < self.options.len() {
61+ pub fn selection(&self) -> Option<&T> {
62+ self.options.get(self.cursor)
66+use super::PromptEvent as MenuEvent;
68+impl<T> Component for Menu<T> {
69+ fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult {
70+ let event = match event {
71+ Event::Key(event) => event,
72+ _ => return EventResult::Ignored,
75+ let close_fn = EventResult::Consumed(Some(Box::new(
76+ |compositor: &mut Compositor, editor: &mut Editor| {
83+ // esc or ctrl-c aborts the completion and closes the menu
85+ code: KeyCode::Esc, ..
88+ code: KeyCode::Char('c'),
89+ modifiers: KeyModifiers::CONTROL,
91+ (self.callback_fn)(cx.editor, self.selection(), MenuEvent::Abort);
94+ // arrow up/ctrl-p/shift-tab prev completion choice (including updating the doc)
97+ modifiers: KeyModifiers::SHIFT,
100+ code: KeyCode::Up, ..
103+ code: KeyCode::Char('p'),
104+ modifiers: KeyModifiers::CONTROL,
107+ (self.callback_fn)(cx.editor, self.selection(), MenuEvent::Update);
108+ return EventResult::Consumed(None);
110+ // arrow down/ctrl-n/tab advances completion choice (including updating the doc)
113+ modifiers: KeyModifiers::NONE,
116+ code: KeyCode::Down,
120+ code: KeyCode::Char('n'),
121+ modifiers: KeyModifiers::CONTROL,
124+ (self.callback_fn)(cx.editor, self.selection(), MenuEvent::Update);
125+ return EventResult::Consumed(None);
128+ code: KeyCode::Enter,
131+ (self.callback_fn)(cx.editor, self.selection(), MenuEvent::Validate);
135+ // code: KeyCode::Char(c),
136+ // modifiers: KeyModifiers::NONE,
138+ // self.insert_char(c);
139+ // (self.callback_fn)(cx.editor, &self.line, MenuEvent::Update);
142+ // / -> edit_filter?
144+ // enter confirms the match and closes the menu
145+ // typing filters the menu
146+ // if we run out of options the menu closes itself
149+ // for some events, we want to process them but send ignore, specifically all input except
150+ // tab/enter/ctrl-k or whatever will confirm the selection/ ctrl-n/ctrl-p for scroll.
151+ // EventResult::Consumed(None)
152+ EventResult::Ignored
154+ fn render(&self, area: Rect, surface: &mut Surface, cx: &mut Context) {
155+ // render a box at x, y. Width equal to max width of item.
156+ // initially limit to n items, add support for scrolling
158+ const MAX: usize = 5;
159+ let rows = std::cmp::min(self.options.len(), MAX) as u16;
160+ let area = Rect::new(self.position.col as u16, self.position.row as u16, 30, rows);
163+ let background = cx.editor.theme.get("ui.popup");
164+ for y in area.top()..area.bottom() {
165+ for x in area.left()..area.right() {
166+ let cell = surface.get_mut(x, y);
168+ // cell.symbol.clear();
169+ cell.set_style(background);
173+ // -- Render the contents:
175+ let style = Style::default().fg(Color::Rgb(164, 160, 232)); // lavender
176+ let selected = Style::default().fg(Color::Rgb(255, 255, 255));
178+ for (i, option) in self.options.iter().take(rows as usize).enumerate() {
179+ // TODO: set bg for the whole row if selected
180+ surface.set_stringn(
183+ (self.format_fn)(option),
184+ area.width as usize - 1,
185+ if i == self.cursor { selected } else { style },