diff options
author | Blaž Hrastnik | 2021-04-05 09:23:37 +0000 |
---|---|---|
committer | Blaž Hrastnik | 2021-04-05 09:23:37 +0000 |
commit | 95d0bba81ae8ed035399b2cb362d2f65481d4781 (patch) | |
tree | af8d35e7de2864abec5d4188bdb7a29fb2ef56c2 /helix-term/src/ui | |
parent | 59a0fc7b59186b3bedb01dd5b958d3b97b9fbba2 (diff) |
ui: Improve completion state handling.
Diffstat (limited to 'helix-term/src/ui')
-rw-r--r-- | helix-term/src/ui/completion.rs | 75 | ||||
-rw-r--r-- | helix-term/src/ui/editor.rs | 37 | ||||
-rw-r--r-- | helix-term/src/ui/menu.rs | 10 | ||||
-rw-r--r-- | helix-term/src/ui/picker.rs | 2 | ||||
-rw-r--r-- | helix-term/src/ui/popup.rs | 6 |
5 files changed, 88 insertions, 42 deletions
diff --git a/helix-term/src/ui/completion.rs b/helix-term/src/ui/completion.rs index 53241c57..637fc5f4 100644 --- a/helix-term/src/ui/completion.rs +++ b/helix-term/src/ui/completion.rs @@ -12,6 +12,7 @@ use std::borrow::Cow; use helix_core::{Position, Transaction}; use helix_view::Editor; +use crate::commands; use crate::ui::{Menu, Popup, PromptEvent}; use helix_lsp::lsp; @@ -112,44 +113,50 @@ impl Completion { trigger_offset, } } + + pub fn update(&mut self, cx: &mut commands::Context) { + // recompute menu based on matches + let menu = self.popup.contents_mut(); + let (view, doc) = cx.editor.current(); + + // cx.hooks() + // cx.add_hook(enum type, ||) + // cx.trigger_hook(enum type, &str, ...) <-- there has to be enough to identify doc/view + // callback with editor & compositor + // + // trigger_hook sends event into channel, that's consumed in the global loop and + // triggers all registered callbacks + // TODO: hooks should get processed immediately so maybe do it after select!(), before + // looping? + + let cursor = doc.selection(view.id).cursor(); + if self.trigger_offset <= cursor { + let fragment = doc.text().slice(self.trigger_offset..=cursor); + let text = Cow::from(fragment); + // TODO: logic is same as ui/picker + menu.score(&text); + } + } + + pub fn is_empty(&self) -> bool { + self.popup.contents().is_empty() + } } +// need to: +// - trigger on the right trigger char +// - detect previous open instance and recycle +// - update after input, but AFTER the document has changed +// - if no more matches, need to auto close +// +// missing bits: +// - a more robust hook system: emit to a channel, process in main loop +// - a way to find specific layers in compositor +// - components register for hooks, then unregister when terminated +// ... since completion is a special case, maybe just build it into doc/render? + impl Component for Completion { fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { - // input - if let Event::Key(KeyEvent { - code: KeyCode::Char(ch), - .. - }) = event - { - // recompute menu based on matches - let menu = self.popup.contents(); - let (view, doc) = cx.editor.current(); - - // cx.hooks() - // cx.add_hook(enum type, ||) - // cx.trigger_hook(enum type, &str, ...) <-- there has to be enough to identify doc/view - // callback with editor & compositor - // - // trigger_hook sends event into channel, that's consumed in the global loop and - // triggers all registered callbacks - // TODO: hooks should get processed immediately so maybe do it after select!(), before - // looping? - - let cursor = doc.selection(view.id).cursor(); - if self.trigger_offset <= cursor { - let fragment = doc.text().slice(self.trigger_offset..cursor); - // ^ problem seems to be that we handle events here before the editor layer, so the - // keypress isn't included in the editor layer yet... - // so we can't use ..= for now. - let text = Cow::from(fragment); - // TODO: logic is same as ui/picker - menu.score(&text); - - // TODO: if after scoring the selection is 0 items, remove popup - } - } - self.popup.handle_event(event, cx) } diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 24c46bde..16a77b6c 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -3,7 +3,7 @@ use crate::{ compositor::{Component, Compositor, Context, EventResult}, key, keymap::{self, Keymaps}, - ui::text_color, + ui::{text_color, Completion}, }; use helix_core::{ @@ -29,6 +29,7 @@ pub struct EditorView { on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>, status_msg: Option<String>, last_insert: (commands::Command, Vec<KeyEvent>), + completion: Option<Completion>, } const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter @@ -40,6 +41,7 @@ impl EditorView { on_next_key: None, status_msg: None, last_insert: (commands::normal_mode, Vec::new()), + completion: None, } } @@ -435,15 +437,15 @@ impl EditorView { ); } - fn insert_mode(&self, cxt: &mut commands::Context, event: KeyEvent) { + fn insert_mode(&self, cx: &mut commands::Context, event: KeyEvent) { if let Some(command) = self.keymap[&Mode::Insert].get(&event) { - command(cxt); + command(cx); } else if let KeyEvent { code: KeyCode::Char(ch), .. } = event { - commands::insert::insert_char(cxt, ch); + commands::insert::insert_char(cx, ch); } } @@ -476,6 +478,18 @@ impl EditorView { } } } + + pub fn set_completion( + &mut self, + items: Vec<helix_lsp::lsp::CompletionItem>, + trigger_offset: usize, + size: Rect, + ) { + let mut completion = Completion::new(items, trigger_offset); + // TODO : propagate required size on resize to completion too + completion.required_size((size.width, size.height)); + self.completion = Some(completion); + } } impl Component for EditorView { @@ -512,7 +526,15 @@ impl Component for EditorView { // record last_insert key self.last_insert.1.push(event); - self.insert_mode(&mut cxt, event) + self.insert_mode(&mut cxt, event); + + if let Some(completion) = &mut self.completion { + completion.update(&mut cxt); + if completion.is_empty() { + self.completion = None; + } + // TODO: if exiting InsertMode, remove completion + } } mode => self.command_mode(mode, &mut cxt, event), } @@ -547,6 +569,11 @@ impl Component for EditorView { let doc = cx.editor.document(view.doc).unwrap(); self.render_view(doc, view, view.area, surface, &cx.editor.theme, is_focused); } + + if let Some(completion) = &self.completion { + completion.render(area, surface, cx) + // render completion here + } } fn cursor_position(&self, area: Rect, editor: &Editor) -> Option<Position> { diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index fbd25a6d..30ac044c 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -123,11 +123,19 @@ impl<T> Menu<T> { .map(|(index, _score)| &self.options[*index]) }) } + + pub fn is_empty(&self) -> bool { + self.matches.is_empty() + } + + pub fn len(&self) -> usize { + self.matches.len() + } } use super::PromptEvent as MenuEvent; -impl<T> Component for Menu<T> { +impl<T: 'static> Component for Menu<T> { fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { let event = match event { Event::Key(event) => event, diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 6ac35fba..ef2fe2a8 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -118,7 +118,7 @@ impl<T> Picker<T> { // - on input change: // - score all the names in relation to input -impl<T> Component for Picker<T> { +impl<T: 'static> Component for Picker<T> { fn handle_event(&mut self, event: Event, cx: &mut Context) -> EventResult { let key_event = match event { Event::Key(event) => event, diff --git a/helix-term/src/ui/popup.rs b/helix-term/src/ui/popup.rs index f1666451..44e79c4f 100644 --- a/helix-term/src/ui/popup.rs +++ b/helix-term/src/ui/popup.rs @@ -46,7 +46,11 @@ impl<T: Component> Popup<T> { } } - pub fn contents(&mut self) -> &mut T { + pub fn contents(&self) -> &T { + &self.contents + } + + pub fn contents_mut(&mut self) -> &mut T { &mut self.contents } } |