From 34f46e7502a562ffb1a3f681046c6cb7e86f7286 Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Fri, 3 Dec 2021 12:48:07 +0900 Subject: Bump rust to 1.57, fix new lint failures --- helix-term/src/ui/picker.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'helix-term/src/ui/picker.rs') diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 6b1c5832..16bf08a3 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -46,7 +46,7 @@ pub struct FilePicker { } pub enum CachedPreview { - Document(Document), + Document(Box), Binary, LargeFile, NotFound, @@ -140,7 +140,7 @@ impl FilePicker { _ => { // TODO: enable syntax highlighting; blocked by async rendering Document::open(path, None, Some(&editor.theme), None) - .map(CachedPreview::Document) + .map(|doc| CachedPreview::Document(Box::new(doc))) .unwrap_or(CachedPreview::NotFound) } }, @@ -421,19 +421,19 @@ impl Component for Picker { } key!(Enter) => { if let Some(option) = self.selection() { - (self.callback_fn)(&mut cx.editor, option, Action::Replace); + (self.callback_fn)(cx.editor, option, Action::Replace); } return close_fn; } ctrl!('s') => { if let Some(option) = self.selection() { - (self.callback_fn)(&mut cx.editor, option, Action::HorizontalSplit); + (self.callback_fn)(cx.editor, option, Action::HorizontalSplit); } return close_fn; } ctrl!('v') => { if let Some(option) = self.selection() { - (self.callback_fn)(&mut cx.editor, option, Action::VerticalSplit); + (self.callback_fn)(cx.editor, option, Action::VerticalSplit); } return close_fn; } -- cgit v1.2.3-70-g09d2 From cab09093dd5bf9b4707bfdfd8529b348c02670ea Mon Sep 17 00:00:00 2001 From: Blaž Hrastnik Date: Mon, 6 Dec 2021 12:24:25 +0900 Subject: fix: Normalize backtab into shift-tab Fixes #1150 --- book/src/remapping.md | 5 ++--- helix-term/src/ui/menu.rs | 2 +- helix-term/src/ui/picker.rs | 2 +- helix-term/src/ui/prompt.rs | 2 +- helix-view/src/input.rs | 24 ++++++++++++++---------- helix-view/src/keyboard.rs | 5 +---- 6 files changed, 20 insertions(+), 20 deletions(-) (limited to 'helix-term/src/ui/picker.rs') diff --git a/book/src/remapping.md b/book/src/remapping.md index fffd189b..ad63b60b 100644 --- a/book/src/remapping.md +++ b/book/src/remapping.md @@ -42,10 +42,9 @@ Control, Shift and Alt modifiers are encoded respectively with the prefixes | Down | `"down"` | | Home | `"home"` | | End | `"end"` | -| Page | `"pageup"` | -| Page | `"pagedown"` | +| Page Up | `"pageup"` | +| Page Down | `"pagedown"` | | Tab | `"tab"` | -| Back | `"backtab"` | | Delete | `"del"` | | Insert | `"ins"` | | Null | `"null"` | diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index e891c149..9a885a36 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -202,7 +202,7 @@ impl Component for Menu { return close_fn; } // arrow up/ctrl-p/shift-tab prev completion choice (including updating the doc) - shift!(BackTab) | key!(Up) | ctrl!('p') | ctrl!('k') => { + shift!(Tab) | key!(Up) | ctrl!('p') | ctrl!('k') => { self.move_up(); (self.callback_fn)(cx.editor, self.selection(), MenuEvent::Update); return EventResult::Consumed(None); diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 16bf08a3..1c963f97 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -410,7 +410,7 @@ impl Component for Picker { }))); match key_event.into() { - shift!(BackTab) | key!(Up) | ctrl!('p') | ctrl!('k') => { + shift!(Tab) | key!(Up) | ctrl!('p') | ctrl!('k') => { self.move_up(); } key!(Tab) | key!(Down) | ctrl!('n') | ctrl!('j') => { diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index e90b0772..a7ef231c 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -505,7 +505,7 @@ impl Component for Prompt { self.change_completion_selection(CompletionDirection::Forward); (self.callback_fn)(cx, &self.line, PromptEvent::Update) } - shift!(BackTab) => { + shift!(Tab) => { self.change_completion_selection(CompletionDirection::Backward); (self.callback_fn)(cx, &self.line, PromptEvent::Update) } diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index 580204cc..b207c3ed 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -36,7 +36,6 @@ pub(crate) mod keys { pub(crate) const PAGEUP: &str = "pageup"; pub(crate) const PAGEDOWN: &str = "pagedown"; pub(crate) const TAB: &str = "tab"; - pub(crate) const BACKTAB: &str = "backtab"; pub(crate) const DELETE: &str = "del"; pub(crate) const INSERT: &str = "ins"; pub(crate) const NULL: &str = "null"; @@ -82,7 +81,6 @@ impl fmt::Display for KeyEvent { KeyCode::PageUp => f.write_str(keys::PAGEUP)?, KeyCode::PageDown => f.write_str(keys::PAGEDOWN)?, KeyCode::Tab => f.write_str(keys::TAB)?, - KeyCode::BackTab => f.write_str(keys::BACKTAB)?, KeyCode::Delete => f.write_str(keys::DELETE)?, KeyCode::Insert => f.write_str(keys::INSERT)?, KeyCode::Null => f.write_str(keys::NULL)?, @@ -116,7 +114,6 @@ impl UnicodeWidthStr for KeyEvent { KeyCode::PageUp => keys::PAGEUP.len(), KeyCode::PageDown => keys::PAGEDOWN.len(), KeyCode::Tab => keys::TAB.len(), - KeyCode::BackTab => keys::BACKTAB.len(), KeyCode::Delete => keys::DELETE.len(), KeyCode::Insert => keys::INSERT.len(), KeyCode::Null => keys::NULL.len(), @@ -166,7 +163,6 @@ impl std::str::FromStr for KeyEvent { keys::PAGEUP => KeyCode::PageUp, keys::PAGEDOWN => KeyCode::PageDown, keys::TAB => KeyCode::Tab, - keys::BACKTAB => KeyCode::BackTab, keys::DELETE => KeyCode::Delete, keys::INSERT => KeyCode::Insert, keys::NULL => KeyCode::Null, @@ -220,12 +216,20 @@ impl<'de> Deserialize<'de> for KeyEvent { #[cfg(feature = "term")] impl From for KeyEvent { - fn from( - crossterm::event::KeyEvent { code, modifiers }: crossterm::event::KeyEvent, - ) -> KeyEvent { - KeyEvent { - code: code.into(), - modifiers: modifiers.into(), + fn from(crossterm::event::KeyEvent { code, modifiers }: crossterm::event::KeyEvent) -> Self { + if code == crossterm::event::KeyCode::BackTab { + // special case for BackTab -> Shift-Tab + let mut modifiers: KeyModifiers = modifiers.into(); + modifiers.insert(KeyModifiers::SHIFT); + Self { + code: KeyCode::Tab, + modifiers, + } + } else { + Self { + code: code.into(), + modifiers: modifiers.into(), + } } } } diff --git a/helix-view/src/keyboard.rs b/helix-view/src/keyboard.rs index 810aa063..f1717209 100644 --- a/helix-view/src/keyboard.rs +++ b/helix-view/src/keyboard.rs @@ -79,8 +79,6 @@ pub enum KeyCode { PageDown, /// Tab key. Tab, - /// Shift + Tab key. - BackTab, /// Delete key. Delete, /// Insert key. @@ -116,7 +114,6 @@ impl From for crossterm::event::KeyCode { KeyCode::PageUp => CKeyCode::PageUp, KeyCode::PageDown => CKeyCode::PageDown, KeyCode::Tab => CKeyCode::Tab, - KeyCode::BackTab => CKeyCode::BackTab, KeyCode::Delete => CKeyCode::Delete, KeyCode::Insert => CKeyCode::Insert, KeyCode::F(f_number) => CKeyCode::F(f_number), @@ -144,7 +141,7 @@ impl From for KeyCode { CKeyCode::PageUp => KeyCode::PageUp, CKeyCode::PageDown => KeyCode::PageDown, CKeyCode::Tab => KeyCode::Tab, - CKeyCode::BackTab => KeyCode::BackTab, + CKeyCode::BackTab => unreachable!("BackTab should have been handled on KeyEvent level"), CKeyCode::Delete => KeyCode::Delete, CKeyCode::Insert => KeyCode::Insert, CKeyCode::F(f_number) => KeyCode::F(f_number), -- cgit v1.2.3-70-g09d2 From e91d357fae04766b9781fe51a0809d35175fe1cf Mon Sep 17 00:00:00 2001 From: Omnikar Date: Sun, 12 Dec 2021 07:16:48 -0500 Subject: Macros (#1234) * Macros WIP `helix_term::compositor::Callback` changed to take a `&mut Context` as a parameter for use by `play_macro` * Default to `@` register for macros * Import `KeyEvent` * Special-case shift-tab -> backtab in `KeyEvent` conversion * Move key recording to the compositor * Add comment * Add persistent display of macro recording status When macro recording is active, the pending keys display will be shifted 3 characters left, and the register being recorded to will be displayed between brackets — e.g., `[@]` — right of the pending keys display. * Fix/add documentation--- book/src/keymap.md | 2 ++ helix-term/src/commands.rs | 59 ++++++++++++++++++++++++++++++++++++++++++-- helix-term/src/compositor.rs | 9 +++++-- helix-term/src/keymap.rs | 3 +++ helix-term/src/ui/editor.rs | 22 +++++++++++++++-- helix-term/src/ui/menu.rs | 2 +- helix-term/src/ui/picker.rs | 2 +- helix-term/src/ui/popup.rs | 2 +- helix-term/src/ui/prompt.rs | 2 +- helix-view/src/editor.rs | 3 +++ helix-view/src/input.rs | 20 +++++++++++++++ 11 files changed, 116 insertions(+), 10 deletions(-) (limited to 'helix-term/src/ui/picker.rs') diff --git a/book/src/keymap.md b/book/src/keymap.md index 5a804c3c..f0a2cb30 100644 --- a/book/src/keymap.md +++ b/book/src/keymap.md @@ -77,6 +77,8 @@ | `Alt-c` | Change selection (delete and enter insert mode, without yanking) | `change_selection_noyank` | | `Ctrl-a` | Increment object (number) under cursor | `increment` | | `Ctrl-x` | Decrement object (number) under cursor | `decrement` | +| `q` | Start/stop macro recording to the selected register | `record_macro` | +| `Q` | Play back a recorded macro from the selected register | `play_macro` | #### Shell diff --git a/helix-term/src/commands.rs b/helix-term/src/commands.rs index 314cd11f..50554731 100644 --- a/helix-term/src/commands.rs +++ b/helix-term/src/commands.rs @@ -70,7 +70,7 @@ pub struct Context<'a> { impl<'a> Context<'a> { /// Push a new component onto the compositor. pub fn push_layer(&mut self, component: Box) { - self.callback = Some(Box::new(|compositor: &mut Compositor| { + self.callback = Some(Box::new(|compositor: &mut Compositor, _| { compositor.push(component) })); } @@ -395,6 +395,8 @@ impl MappableCommand { rename_symbol, "Rename symbol", increment, "Increment", decrement, "Decrement", + record_macro, "Record macro", + play_macro, "Play macro", ); } @@ -3441,7 +3443,7 @@ fn apply_workspace_edit( fn last_picker(cx: &mut Context) { // TODO: last picker does not seem to work well with buffer_picker - cx.callback = Some(Box::new(|compositor: &mut Compositor| { + cx.callback = Some(Box::new(|compositor: &mut Compositor, _| { if let Some(picker) = compositor.last_picker.take() { compositor.push(picker); } @@ -5870,3 +5872,56 @@ fn increment_impl(cx: &mut Context, amount: i64) { doc.append_changes_to_history(view.id); } } + +fn record_macro(cx: &mut Context) { + if let Some((reg, mut keys)) = cx.editor.macro_recording.take() { + // Remove the keypress which ends the recording + keys.pop(); + let s = keys + .into_iter() + .map(|key| format!("{}", key)) + .collect::>() + .join(" "); + cx.editor.registers.get_mut(reg).write(vec![s]); + cx.editor + .set_status(format!("Recorded to register {}", reg)); + } else { + let reg = cx.register.take().unwrap_or('@'); + cx.editor.macro_recording = Some((reg, Vec::new())); + cx.editor + .set_status(format!("Recording to register {}", reg)); + } +} + +fn play_macro(cx: &mut Context) { + let reg = cx.register.unwrap_or('@'); + let keys = match cx + .editor + .registers + .get(reg) + .and_then(|reg| reg.read().get(0)) + .context("Register empty") + .and_then(|s| { + s.split_whitespace() + .map(str::parse::) + .collect::, _>>() + .context("Failed to parse macro") + }) { + Ok(keys) => keys, + Err(e) => { + cx.editor.set_error(format!("{}", e)); + return; + } + }; + let count = cx.count(); + + cx.callback = Some(Box::new( + move |compositor: &mut Compositor, cx: &mut compositor::Context| { + for _ in 0..count { + for &key in keys.iter() { + compositor.handle_event(crossterm::event::Event::Key(key.into()), cx); + } + } + }, + )); +} diff --git a/helix-term/src/compositor.rs b/helix-term/src/compositor.rs index 30554ebb..321f56a5 100644 --- a/helix-term/src/compositor.rs +++ b/helix-term/src/compositor.rs @@ -7,7 +7,7 @@ use helix_view::graphics::{CursorKind, Rect}; use crossterm::event::Event; use tui::buffer::Buffer as Surface; -pub type Callback = Box; +pub type Callback = Box; // --> EventResult should have a callback that takes a context with methods like .popup(), // .prompt() etc. That way we can abstract it from the renderer. @@ -131,12 +131,17 @@ impl Compositor { } pub fn handle_event(&mut self, event: Event, cx: &mut Context) -> bool { + // If it is a key event and a macro is being recorded, push the key event to the recording. + if let (Event::Key(key), Some((_, keys))) = (event, &mut cx.editor.macro_recording) { + keys.push(key.into()); + } + // propagate events through the layers until we either find a layer that consumes it or we // run out of layers (event bubbling) for layer in self.layers.iter_mut().rev() { match layer.handle_event(event, cx) { EventResult::Consumed(Some(callback)) => { - callback(self); + callback(self, cx); return true; } EventResult::Consumed(None) => return true, diff --git a/helix-term/src/keymap.rs b/helix-term/src/keymap.rs index b1613252..257d5f29 100644 --- a/helix-term/src/keymap.rs +++ b/helix-term/src/keymap.rs @@ -593,6 +593,9 @@ impl Default for Keymaps { // paste_all "P" => paste_before, + "q" => record_macro, + "Q" => play_macro, + ">" => indent, "<" => unindent, "=" => format_selections, diff --git a/helix-term/src/ui/editor.rs b/helix-term/src/ui/editor.rs index 39ee15b4..bac1f171 100644 --- a/helix-term/src/ui/editor.rs +++ b/helix-term/src/ui/editor.rs @@ -1100,13 +1100,31 @@ impl Component for EditorView { disp.push_str(&s); } } + let style = cx.editor.theme.get("ui.text"); + let macro_width = if cx.editor.macro_recording.is_some() { + 3 + } else { + 0 + }; surface.set_string( - area.x + area.width.saturating_sub(key_width), + area.x + area.width.saturating_sub(key_width + macro_width), area.y + area.height.saturating_sub(1), disp.get(disp.len().saturating_sub(key_width as usize)..) .unwrap_or(&disp), - cx.editor.theme.get("ui.text"), + style, ); + if let Some((reg, _)) = cx.editor.macro_recording { + let disp = format!("[{}]", reg); + let style = style + .fg(helix_view::graphics::Color::Yellow) + .add_modifier(Modifier::BOLD); + surface.set_string( + area.x + area.width.saturating_sub(3), + area.y + area.height.saturating_sub(1), + &disp, + style, + ); + } } if let Some(completion) = self.completion.as_mut() { diff --git a/helix-term/src/ui/menu.rs b/helix-term/src/ui/menu.rs index 9a885a36..69053db3 100644 --- a/helix-term/src/ui/menu.rs +++ b/helix-term/src/ui/menu.rs @@ -190,7 +190,7 @@ impl Component for Menu { _ => return EventResult::Ignored, }; - let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor| { + let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _| { // remove the layer compositor.pop(); }))); diff --git a/helix-term/src/ui/picker.rs b/helix-term/src/ui/picker.rs index 1c963f97..1ef94df0 100644 --- a/helix-term/src/ui/picker.rs +++ b/helix-term/src/ui/picker.rs @@ -404,7 +404,7 @@ impl Component for Picker { _ => return EventResult::Ignored, }; - let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor| { + let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _| { // remove the layer compositor.last_picker = compositor.pop(); }))); diff --git a/helix-term/src/ui/popup.rs b/helix-term/src/ui/popup.rs index c55f030f..bf7510a2 100644 --- a/helix-term/src/ui/popup.rs +++ b/helix-term/src/ui/popup.rs @@ -100,7 +100,7 @@ impl Component for Popup { _ => return EventResult::Ignored, }; - let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor| { + let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _| { // remove the layer compositor.pop(); }))); diff --git a/helix-term/src/ui/prompt.rs b/helix-term/src/ui/prompt.rs index a7ef231c..07e1b33c 100644 --- a/helix-term/src/ui/prompt.rs +++ b/helix-term/src/ui/prompt.rs @@ -426,7 +426,7 @@ impl Component for Prompt { _ => return EventResult::Ignored, }; - let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor| { + let close_fn = EventResult::Consumed(Some(Box::new(|compositor: &mut Compositor, _| { // remove the layer compositor.pop(); }))); diff --git a/helix-view/src/editor.rs b/helix-view/src/editor.rs index 9034d12c..dcbcbe4f 100644 --- a/helix-view/src/editor.rs +++ b/helix-view/src/editor.rs @@ -2,6 +2,7 @@ use crate::{ clipboard::{get_clipboard_provider, ClipboardProvider}, document::SCRATCH_BUFFER_NAME, graphics::{CursorKind, Rect}, + input::KeyEvent, theme::{self, Theme}, tree::{self, Tree}, Document, DocumentId, View, ViewId, @@ -160,6 +161,7 @@ pub struct Editor { pub count: Option, pub selected_register: Option, pub registers: Registers, + pub macro_recording: Option<(char, Vec)>, pub theme: Theme, pub language_servers: helix_lsp::Registry, pub clipboard_provider: Box, @@ -203,6 +205,7 @@ impl Editor { documents: BTreeMap::new(), count: None, selected_register: None, + macro_recording: None, theme: theme_loader.default(), language_servers, syn_loader, diff --git a/helix-view/src/input.rs b/helix-view/src/input.rs index b207c3ed..92caa517 100644 --- a/helix-view/src/input.rs +++ b/helix-view/src/input.rs @@ -234,6 +234,26 @@ impl From for KeyEvent { } } +#[cfg(feature = "term")] +impl From for crossterm::event::KeyEvent { + fn from(KeyEvent { code, modifiers }: KeyEvent) -> Self { + if code == KeyCode::Tab && modifiers.contains(KeyModifiers::SHIFT) { + // special case for Shift-Tab -> BackTab + let mut modifiers = modifiers; + modifiers.remove(KeyModifiers::SHIFT); + crossterm::event::KeyEvent { + code: crossterm::event::KeyCode::BackTab, + modifiers: modifiers.into(), + } + } else { + crossterm::event::KeyEvent { + code: code.into(), + modifiers: modifiers.into(), + } + } + } +} + #[cfg(test)] mod test { use super::*; -- cgit v1.2.3-70-g09d2