Skip to content

Commit

Permalink
Allow multi key keymaps in insert mode
Browse files Browse the repository at this point in the history
  • Loading branch information
sudormrfbin committed Jul 16, 2021
1 parent 47df8ce commit da83e46
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 34 deletions.
4 changes: 1 addition & 3 deletions helix-term/src/keymap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ macro_rules! alt {
};
}


pub type Keymap = HashMap<KeyEvent, KeyNode>;

#[derive(Debug, Clone, PartialEq, Deserialize)]
Expand All @@ -52,7 +51,6 @@ pub enum KeyNode {
SubKeymap(Keymap),
}


#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(transparent)]
pub struct Keymaps(pub HashMap<Mode, Keymap>);
Expand Down Expand Up @@ -270,8 +268,8 @@ pub fn merge_keys(mut config: Config) -> Config {

#[test]
fn merge_partial_keys() {
use KeyNode::*;
use helix_view::keyboard::{KeyCode, KeyModifiers};
use KeyNode::*;
let config = Config {
keys: Keymaps(hashmap! {
Mode::Normal => hashmap! {
Expand Down
98 changes: 67 additions & 31 deletions helix-term/src/ui/editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ use tui::buffer::Buffer as Surface;
pub struct EditorView {
keymaps: Keymaps,
current_subkeymap: Option<Keymap>,
subkeymap_level: usize,
on_next_key: Option<Box<dyn FnOnce(&mut commands::Context, KeyEvent)>>,
last_insert: (commands::Command, Vec<KeyEvent>),
completion: Option<Completion>,
Expand All @@ -46,6 +47,7 @@ impl EditorView {
Self {
keymaps,
current_subkeymap: None,
subkeymap_level: 0,
on_next_key: None,
last_insert: (commands::Command::normal_mode, Vec::new()),
completion: None,
Expand Down Expand Up @@ -561,15 +563,68 @@ impl EditorView {
);
}

fn insert_mode(&self, cx: &mut commands::Context, event: KeyEvent) {
if let Some(KeyNode::KeyCommand(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. Note that the event
/// may be at the top level or any nested subkeymap inside keymaps. Because of
/// this, handling an event does not necessarily mean that a command was
/// executed; it may be a multi keystroke mapping. Returns None if event was
/// handled, Some(n) otherwise where n denotes number of pending events to
/// be handled
fn handle_keymap_event(
&mut self,
mode: Mode,
cxt: &mut commands::Context,
event: KeyEvent,
) -> Option<usize> {
let keymap = match self.current_subkeymap {
Some(ref keymap) => keymap,
None => &self.keymaps[&mode],
};
if let Some(keynode) = keymap.get(&event) {
match keynode {
KeyNode::KeyCommand(command) => {
command.execute(cxt);
self.current_subkeymap = None;
self.subkeymap_level = 0;
}
KeyNode::SubKeymap(subkeymap) => {
self.current_subkeymap = Some(subkeymap.clone());
self.subkeymap_level += 1;
}
}
None
} else {
// previous events activated nested subkeymaps but current event
// cancelled it, so return the earlier swallowed events + current event
// only current event is pending if no subkeymaps were activated earlier
let pending_events = self.subkeymap_level + 1;
self.current_subkeymap = None;
self.subkeymap_level = 0;
Some(pending_events)
}
}

fn insert_mode(&mut self, cx: &mut commands::Context, event: KeyEvent) {
if let Some(pending_events) = self.handle_keymap_event(Mode::Insert, cx, event) {
let event = &[event];
let pending = if pending_events == 1 {
event
} else {
let history = &self.last_insert.1;
&history[history.len() - pending_events..]
};

for ev in pending {
match ev.char() {
Some(ch) => commands::insert::insert_char(cx, ch),
None => {
if let Some(KeyNode::KeyCommand(command)) =
self.keymaps[&Mode::Insert].get(ev)
{
command.execute(cx);
}
}
}
}
}
}

Expand All @@ -586,8 +641,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)
}
}
_ => {
Expand All @@ -600,26 +655,7 @@ impl EditorView {
// set the register
cxt.selected_register = cxt.editor.selected_register.take();

// if self.current_subkeymap.is_none() {
// self.current_subkeymap = self.keymaps.get(&mode).cloned();
// }
let keymap = match self.current_subkeymap {
Some(ref keymap) => keymap,
None => &self.keymaps[&mode],
};
if let Some(keynode) = keymap.get(&event) {
match keynode {
KeyNode::KeyCommand(command) => {
command.execute(cxt);
self.current_subkeymap = None
}
KeyNode::SubKeymap(subkeymap) => {
self.current_subkeymap = Some(subkeymap.clone())
}
}
} else {
self.current_subkeymap = None
}
self.handle_keymap_event(mode, cxt, event);
}
}
}
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 @@ -14,6 +14,16 @@ pub struct KeyEvent {
pub modifiers: KeyModifiers,
}

impl KeyEvent {
/// Get only the character involved in this event
pub fn char(&self) -> Option<char> {
match self.code {
KeyCode::Char(ch) => Some(ch),
_ => None,
}
}
}

pub(crate) mod keys {
pub(crate) const BACKSPACE: &str = "backspace";
pub(crate) const ENTER: &str = "ret";
Expand Down

0 comments on commit da83e46

Please sign in to comment.