diff options
Diffstat (limited to 'helix-term/src/ui')
-rw-r--r-- | helix-term/src/ui/editor.rs | 76 | ||||
-rw-r--r-- | helix-term/src/ui/info.rs | 2 | ||||
-rw-r--r-- | helix-term/src/ui/mod.rs | 1 | ||||
-rw-r--r-- | helix-term/src/ui/picker.rs | 1 | ||||
-rw-r--r-- | helix-term/src/ui/prompt.rs | 58 |
5 files changed, 118 insertions, 20 deletions
diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 482a4117..3e131bf1 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -2,7 +2,7 @@ use crate::{ commands, compositor::{Component, Context, EventResult}, key, - keymap::Keymaps, + keymap::{KeymapResult, Keymaps}, ui::{Completion, ProgressSpinners}, }; @@ -15,6 +15,7 @@ use helix_core::{ use helix_view::{ document::Mode, graphics::{CursorKind, Modifier, Rect, Style}, + info::Info, input::KeyEvent, keyboard::{KeyCode, KeyModifiers}, Document, Editor, Theme, View, @@ -30,6 +31,7 @@ pub struct EditorView { last_insert: (commands::Command, Vec<KeyEvent>), completion: Option<Completion>, spinners: ProgressSpinners, + pub autoinfo: Option<Info>, } const OFFSET: u16 = 7; // 1 diagnostic + 5 linenr + 1 gutter @@ -48,6 +50,7 @@ impl EditorView { last_insert: (commands::Command::normal_mode, Vec::new()), completion: None, spinners: ProgressSpinners::default(), + autoinfo: None, } } @@ -594,19 +597,53 @@ impl EditorView { ); } - fn insert_mode(&self, cx: &mut commands::Context, event: KeyEvent) { - if let Some(command) = self.keymaps[&Mode::Insert].get(&event) { - command.execute(cx); - } else if let KeyEvent { - code: KeyCode::Char(ch), - .. - } = event - { - commands::insert::insert_char(cx, ch); + /// Handle events by looking them up in `self.keymaps`. Returns None + /// if event was handled (a command was executed or a subkeymap was + /// activated). Only KeymapResult::{NotFound, Cancelled} is returned + /// otherwise. + fn handle_keymap_event( + &mut self, + mode: Mode, + cxt: &mut commands::Context, + event: KeyEvent, + ) -> Option<KeymapResult> { + self.autoinfo = None; + match self.keymaps.get_mut(&mode).unwrap().get(event) { + KeymapResult::Matched(command) => command.execute(cxt), + KeymapResult::Pending(node) => self.autoinfo = Some(node.into()), + k @ KeymapResult::NotFound | k @ KeymapResult::Cancelled(_) => return Some(k), } + None } - fn command_mode(&self, mode: Mode, cxt: &mut commands::Context, event: KeyEvent) { + fn insert_mode(&mut self, cx: &mut commands::Context, event: KeyEvent) { + if let Some(keyresult) = self.handle_keymap_event(Mode::Insert, cx, event) { + match keyresult { + KeymapResult::NotFound => { + if let Some(ch) = event.char() { + commands::insert::insert_char(cx, ch) + } + } + KeymapResult::Cancelled(pending) => { + for ev in pending { + match ev.char() { + Some(ch) => commands::insert::insert_char(cx, ch), + None => { + if let KeymapResult::Matched(command) = + self.keymaps.get_mut(&Mode::Insert).unwrap().get(ev) + { + command.execute(cx); + } + } + } + } + } + _ => unreachable!(), + } + } + } + + fn command_mode(&mut self, mode: Mode, cxt: &mut commands::Context, event: KeyEvent) { match event { // count handling key!(i @ '0'..='9') => { @@ -619,8 +656,8 @@ impl EditorView { // first execute whatever put us into insert mode self.last_insert.0.execute(cxt); // then replay the inputs - for key in &self.last_insert.1 { - self.insert_mode(cxt, *key) + for &key in &self.last_insert.1.clone() { + self.insert_mode(cxt, key) } } _ => { @@ -633,9 +670,7 @@ impl EditorView { // set the register cxt.selected_register = cxt.editor.selected_register.take(); - if let Some(command) = self.keymaps[&mode].get(&event) { - command.execute(cxt); - } + self.handle_keymap_event(mode, cxt, event); } } } @@ -749,7 +784,11 @@ impl Component for EditorView { // how we entered insert mode is important, and we should track that so // we can repeat the side effect. - self.last_insert.0 = self.keymaps[&mode][&key]; + self.last_insert.0 = match self.keymaps.get_mut(&mode).unwrap().get(key) { + KeymapResult::Matched(command) => command, + // FIXME: insert mode can only be entered through single KeyCodes + _ => unimplemented!(), + }; self.last_insert.1.clear(); } (Mode::Insert, Mode::Normal) => { @@ -787,9 +826,8 @@ impl Component for EditorView { ); } - if let Some(info) = std::mem::take(&mut cx.editor.autoinfo) { + if let Some(ref info) = self.autoinfo { info.render(area, surface, cx); - cx.editor.autoinfo = Some(info); } // render status msg diff --git a/helix-term/src/ui/info.rs b/helix-term/src/ui/info.rs index e5f20562..36b096db 100644 --- a/helix-term/src/ui/info.rs +++ b/helix-term/src/ui/info.rs @@ -8,7 +8,7 @@ impl Component for Info { fn render(&self, viewport: Rect, surface: &mut Surface, cx: &mut Context) { let style = cx.editor.theme.get("ui.popup"); let block = Block::default() - .title(self.title) + .title(self.title.as_str()) .borders(Borders::ALL) .border_style(style); let Info { width, height, .. } = self; diff --git a/helix-term/src/ui/mod.rs b/helix-term/src/ui/mod.rs index 288d3d2e..9e71cfe7 100644 --- a/helix-term/src/ui/mod.rs +++ b/helix-term/src/ui/mod.rs @@ -36,6 +36,7 @@ pub fn regex_prompt( Prompt::new( prompt, + None, |_input: &str| Vec::new(), // this is fine because Vec::new() doesn't allocate move |cx: &mut crate::compositor::Context, input: &str, event: PromptEvent| { match event { diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 733be2fc..0b67cd9c 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -43,6 +43,7 @@ impl<T> Picker<T> { ) -> Self { let prompt = Prompt::new( "".to_string(), + None, |_pattern: &str| Vec::new(), |_editor: &mut Context, _pattern: &str, _event: PromptEvent| { // diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index 2df1e281..57daef3a 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -20,6 +20,8 @@ pub struct Prompt { cursor: usize, completion: Vec<Completion>, selection: Option<usize>, + history_register: Option<char>, + history_pos: Option<usize>, completion_fn: Box<dyn FnMut(&str) -> Vec<Completion>>, callback_fn: Box<dyn FnMut(&mut Context, &str, PromptEvent)>, pub doc_fn: Box<dyn Fn(&str) -> Option<&'static str>>, @@ -54,6 +56,7 @@ pub enum Movement { impl Prompt { pub fn new( prompt: String, + history_register: Option<char>, mut completion_fn: impl FnMut(&str) -> Vec<Completion> + 'static, callback_fn: impl FnMut(&mut Context, &str, PromptEvent) + 'static, ) -> Self { @@ -63,6 +66,8 @@ impl Prompt { cursor: 0, completion: completion_fn(""), selection: None, + history_register, + history_pos: None, completion_fn: Box::new(completion_fn), callback_fn: Box::new(callback_fn), doc_fn: Box::new(|_| None), @@ -226,6 +231,28 @@ impl Prompt { self.exit_selection(); } + pub fn change_history(&mut self, register: &[String], direction: CompletionDirection) { + if register.is_empty() { + return; + } + + let end = register.len().saturating_sub(1); + + let index = match direction { + CompletionDirection::Forward => self.history_pos.map_or(0, |i| i + 1), + CompletionDirection::Backward => { + self.history_pos.unwrap_or(register.len()).saturating_sub(1) + } + } + .min(end); + + self.line = register[index].clone(); + + self.history_pos = Some(index); + + self.move_end(); + } + pub fn change_completion_selection(&mut self, direction: CompletionDirection) { if self.completion.is_empty() { return; @@ -468,10 +495,41 @@ impl Component for Prompt { self.exit_selection(); } else { (self.callback_fn)(cx, &self.line, PromptEvent::Validate); + + if let Some(register) = self.history_register { + // store in history + let register = cx.editor.registers.get_mut(register); + register.push(self.line.clone()); + } return close_fn; } } KeyEvent { + code: KeyCode::Char('p'), + modifiers: KeyModifiers::CONTROL, + } + | KeyEvent { + code: KeyCode::Up, .. + } => { + if let Some(register) = self.history_register { + let register = cx.editor.registers.get_mut(register); + self.change_history(register.read(), CompletionDirection::Backward); + } + } + KeyEvent { + code: KeyCode::Char('n'), + modifiers: KeyModifiers::CONTROL, + } + | KeyEvent { + code: KeyCode::Down, + .. + } => { + if let Some(register) = self.history_register { + let register = cx.editor.registers.get_mut(register); + self.change_history(register.read(), CompletionDirection::Forward); + } + } + KeyEvent { code: KeyCode::Tab, .. } => self.change_completion_selection(CompletionDirection::Forward), KeyEvent { |