Skip to content

Commit

Permalink
Macros WIP
Browse files Browse the repository at this point in the history
`helix-term::compositor::Callback` changed to take a `&mut Context` as
a parameter for use by `play_macro`
  • Loading branch information
Omnikar committed Dec 5, 2021
1 parent 461cd20 commit 3712df3
Show file tree
Hide file tree
Showing 10 changed files with 82 additions and 8 deletions.
59 changes: 57 additions & 2 deletions helix-term/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn Component>) {
self.callback = Some(Box::new(|compositor: &mut Compositor| {
self.callback = Some(Box::new(|compositor: &mut Compositor, _| {
compositor.push(component)
}));
}
Expand Down Expand Up @@ -394,6 394,8 @@ impl MappableCommand {
rename_symbol, "Rename symbol",
increment, "Increment",
decrement, "Decrement",
record_macro, "Toggle macro recording",
play_macro, "Play back a recorded macro",
);
}

Expand Down Expand Up @@ -3430,7 3432,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);
}
Expand Down Expand Up @@ -5849,3 5851,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::<Vec<_>>()
.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::<KeyEvent>)
.collect::<Result<Vec<_>, _>>()
.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);
}
}
},
));
}
4 changes: 2 additions & 2 deletions helix-term/src/compositor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 7,7 @@ use helix_view::graphics::{CursorKind, Rect};
use crossterm::event::Event;
use tui::buffer::Buffer as Surface;

pub type Callback = Box<dyn FnOnce(&mut Compositor)>;
pub type Callback = Box<dyn FnOnce(&mut Compositor, &mut Context)>;

// --> EventResult should have a callback that takes a context with methods like .popup(),
// .prompt() etc. That way we can abstract it from the renderer.
Expand Down Expand Up @@ -131,7 131,7 @@ impl Compositor {
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,
Expand Down
3 changes: 3 additions & 0 deletions helix-term/src/keymap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -593,6 593,9 @@ impl Default for Keymaps {
// paste_all
"P" => paste_before,

"q" => record_macro,
"Q" => play_macro,

">" => indent,
"<" => unindent,
"=" => format_selections,
Expand Down
4 changes: 4 additions & 0 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 947,10 @@ impl Component for EditorView {
let (_, doc) = current!(cxt.editor);
let mode = doc.mode();

if let Some((_, keys)) = &mut cxt.editor.macro_recording {
keys.push(key);
}

if let Some(on_next_key) = self.on_next_key.take() {
// if there's a command waiting input, do that first
on_next_key(&mut cxt, key);
Expand Down
2 changes: 1 addition & 1 deletion helix-term/src/ui/menu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 190,7 @@ impl<T: Item 'static> Component for Menu<T> {
_ => 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();
})));
Expand Down
2 changes: 1 addition & 1 deletion helix-term/src/ui/picker.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 404,7 @@ impl<T: 'static> Component for Picker<T> {
_ => 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();
})));
Expand Down
2 changes: 1 addition & 1 deletion helix-term/src/ui/popup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 93,7 @@ impl<T: Component> Component for Popup<T> {
_ => 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();
})));
Expand Down
2 changes: 1 addition & 1 deletion helix-term/src/ui/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
})));
Expand Down
2 changes: 2 additions & 0 deletions helix-view/src/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 160,7 @@ pub struct Editor {
pub count: Option<std::num::NonZeroUsize>,
pub selected_register: Option<char>,
pub registers: Registers,
pub macro_recording: Option<(char, Vec<crate::input::KeyEvent>)>,
pub theme: Theme,
pub language_servers: helix_lsp::Registry,
pub clipboard_provider: Box<dyn ClipboardProvider>,
Expand Down Expand Up @@ -203,6 204,7 @@ impl Editor {
documents: BTreeMap::new(),
count: None,
selected_register: None,
macro_recording: None,
theme: theme_loader.default(),
language_servers,
syn_loader,
Expand Down
10 changes: 10 additions & 0 deletions helix-view/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 230,16 @@ impl From<crossterm::event::KeyEvent> for KeyEvent {
}
}

#[cfg(feature = "term")]
impl From<KeyEvent> for crossterm::event::KeyEvent {
fn from(KeyEvent { code, modifiers }: KeyEvent) -> Self {
crossterm::event::KeyEvent {
code: code.into(),
modifiers: modifiers.into(),
}
}
}

#[cfg(test)]
mod test {
use super::*;
Expand Down

0 comments on commit 3712df3

Please sign in to comment.