use std::{
any::TypeId,
collections::HashMap,
slice,
sync::{LazyLock, Mutex},
};
use crossterm::event::KeyEvent;
pub use self::global::*;
use super::Mode;
use crate::{
Ns, context,
data::{BulkDataWriter, Pass, RwData},
mode::{self, Binding, Bindings},
text::{Inlay, Text, txt},
ui::Widget,
};
static CUR_SEQ: LazyLock<RwData<(Vec<KeyEvent>, bool)>> = LazyLock::new(RwData::default);
mod global {
use std::{
any::TypeId,
str::Chars,
sync::{LazyLock, Mutex},
};
use crossterm::event::{KeyCode, KeyEvent, KeyModifiers as KeyMod};
use super::{MapsTo, Remapper};
use crate::{
data::{BulkDataWriter, DataMap, Pass, RwData},
mode::{Description, MappedBindings, Mode},
text::{Text, txt},
};
static REMAPPER: BulkDataWriter<Remapper> = BulkDataWriter::new();
pub(in crate::mode) static MODE_TYPE_ID: Mutex<TypeId> = Mutex::new(TypeId::of::<()>());
static SEND_KEY: LazyLock<RwData<fn(&mut Pass, KeyEvent)>> =
LazyLock::new(|| RwData::new(|_, _| {}));
pub struct RemapBuilder {
pub(super) takes: Vec<KeyEvent>,
pub(super) gives: MapsTo,
pub(super) is_alias: bool,
pub(super) doc: Option<Text>,
pub(super) remap: fn(&mut Remapper, Vec<KeyEvent>, MapsTo, bool, Option<Text>),
}
impl RemapBuilder {
pub fn doc<T: Into<Text>>(self, doc: T) {
let mut builder = self;
builder.doc = Some(doc.into());
}
}
impl Drop for RemapBuilder {
fn drop(&mut self) {
let remap = self.remap;
let takes = std::mem::take(&mut self.takes);
let gives = std::mem::replace(&mut self.gives, MapsTo::Keys(Vec::new()));
let is_alias = self.is_alias;
let doc = self.doc.take();
REMAPPER.mutate(move |remapper| remap(remapper, takes, gives, is_alias, doc));
}
}
pub fn map<M: Mode>(takes: &str, gives: impl IntoMapsTo) -> RemapBuilder {
let takes = str_to_keys(takes);
RemapBuilder {
takes,
gives: gives.into_maps_to(),
is_alias: false,
doc: None,
remap: |remapper, takes, gives, is_alias, doc| {
remapper.remap::<M>(takes, gives, is_alias, doc)
},
}
}
pub fn alias<M: Mode>(takes: &str, gives: impl IntoMapsTo) -> RemapBuilder {
let takes = str_to_keys(takes);
RemapBuilder {
takes,
gives: gives.into_maps_to(),
is_alias: true,
doc: None,
remap: |remapper, takes, gives, is_alias, doc| {
remapper.remap::<M>(takes, gives, is_alias, doc)
},
}
}
pub fn current_sequence() -> DataMap<(Vec<KeyEvent>, bool), (Vec<KeyEvent>, bool)> {
super::CUR_SEQ.map(Clone::clone)
}
pub fn reset_current_sequence(pa: &mut Pass) {
*super::CUR_SEQ.write(pa) = (Vec::new(), false)
}
pub fn current_mode_bindings(pa: &mut Pass) -> &MappedBindings {
&REMAPPER.write(pa).mapped_bindings[&MODE_TYPE_ID.lock().unwrap()]
}
pub fn change_binding_description<M: Mode>(seq: &[KeyEvent], new: Text) {
let seq = seq.to_vec();
REMAPPER.mutate(move |remapper| {
let bindings = remapper
.mapped_bindings
.entry(TypeId::of::<M>())
.or_insert_with(MappedBindings::for_mode::<M>);
bindings.replace_seq_description(&seq, new);
});
}
pub fn change_bindings<M: Mode>(bindings: super::Bindings) {
use std::collections::hash_map::Entry;
let bindings = MappedBindings::new(bindings);
REMAPPER.mutate(
move |remapper| match remapper.mapped_bindings.entry(TypeId::of::<M>()) {
Entry::Occupied(mut occupied_entry) => *occupied_entry.get_mut() = bindings,
Entry::Vacant(vacant_entry) => _ = vacant_entry.insert(bindings),
},
);
}
pub fn current_seq_descriptions(
pa: &mut Pass,
) -> (Option<&Text>, impl Iterator<Item = Description<'_>>) {
let ((cur_seq, _), remapper) = pa.write_many((&*super::CUR_SEQ, &REMAPPER));
remapper.mapped_bindings[&*MODE_TYPE_ID.lock().unwrap()].descriptions_for(cur_seq)
}
pub fn keys_to_text(keys: &[KeyEvent]) -> Text {
use crossterm::event::KeyCode::*;
let mut builder = Text::builder();
for key in keys {
if key.modifiers != KeyMod::NONE
|| !matches!(key.code, KeyCode::Char(char) if char != ' ')
{
builder.push(txt!("[key.angle]<"));
}
builder.push(modifier_text(key.modifiers));
match key.code {
Backspace => builder.push(txt!("[key.special]BS")),
Enter | Char('\n') => builder.push(txt!("[key.special]Enter")),
Left => builder.push(txt!("[key.special]Left")),
Right => builder.push(txt!("[key.special]Right")),
Up => builder.push(txt!("[key.special]Up")),
Down => builder.push(txt!("[key.special]Down")),
Home => builder.push(txt!("[key.special]Home")),
End => builder.push(txt!("[key.special]End")),
PageUp => builder.push(txt!("[key.special]PageU")),
PageDown => builder.push(txt!("[key.special]PageD")),
Tab => builder.push(txt!("[key.special]Tab")),
BackTab => builder.push(txt!("[key.special]BTab")),
Delete => builder.push(txt!("[key.special]Del")),
Insert => builder.push(txt!("[key.special]Ins")),
F(num) => builder.push(txt!("[key.special]F{num}")),
Char(' ') => builder.push(txt!("[key.char]Space")),
Char(char) => builder.push(txt!("[key.char]{char}")),
Null => builder.push(txt!("[key.special]Null")),
Esc => builder.push(txt!("[key.special]Esc")),
CapsLock => builder.push(txt!("[key.special]CapsL")),
ScrollLock => builder.push(txt!("[key.special]ScrollL")),
NumLock => builder.push(txt!("[key.special]NumL")),
PrintScreen => builder.push(txt!("[key.special]PrSc")),
Pause => builder.push(txt!("[key.special]Pause")),
Menu => builder.push(txt!("[key.special]Menu")),
KeypadBegin => builder.push(txt!("[key.special]KeypadBeg")),
Media(m_code) => builder.push(txt!("[key.special]Media{m_code}")),
Modifier(m_code) => builder.push(txt!("[key.special]Mod{m_code}")),
}
if key.modifiers != KeyMod::NONE
|| !matches!(key.code, KeyCode::Char(char) if char != ' ')
{
builder.push(txt!("[key.angle]>"));
}
}
builder.build()
}
pub fn modifier_text(modif: KeyMod) -> Text {
if modif == KeyMod::NONE {
return Text::new();
}
let mut builder = Text::builder();
builder.push(crate::form::id_of!("key.mod"));
for modif in modif.iter() {
builder.push(match modif {
KeyMod::ALT => "a",
KeyMod::CONTROL => "c",
KeyMod::SHIFT => "s",
KeyMod::META => "m",
KeyMod::SUPER => "Super",
KeyMod::HYPER => "Hyper",
_ => "",
});
}
builder.push(txt!("[key.mod.separator]-"));
builder.build()
}
pub fn keys_to_string(keys: &[KeyEvent]) -> String {
use std::fmt::Write;
use crossterm::event::{KeyCode::*, KeyModifiers as Mod};
let mut seq = String::new();
for key in keys {
if !key.modifiers.is_empty() {
seq.push('<');
for modif in key.modifiers.iter() {
seq.push_str(match modif {
Mod::ALT => "a",
Mod::CONTROL => "c",
Mod::SHIFT => "s",
Mod::META => "m",
Mod::SUPER => "Super",
Mod::HYPER => "Hyper",
_ => "",
});
}
seq.push('-');
} else if !matches!(key.code, Char(_)) {
seq.push('<');
}
match key.code {
Backspace => seq.push_str("BS>"),
Enter => seq.push_str("Enter>"),
Left => seq.push_str("Left>"),
Right => seq.push_str("Right>"),
Up => seq.push_str("Up>"),
Down => seq.push_str("Down>"),
Home => seq.push_str("Home>"),
End => seq.push_str("End>"),
PageUp => seq.push_str("PageU>"),
PageDown => seq.push_str("PageD>"),
Tab => seq.push_str("Tab>"),
BackTab => seq.push_str("BTab>"),
Delete => seq.push_str("Del>"),
Insert => seq.push_str("Ins>"),
F(num) => write!(seq, "F{num}>").unwrap(),
Char(char) => {
write!(seq, "{char}").unwrap();
if !key.modifiers.is_empty() {
seq.push('>');
}
}
Null => seq.push_str("Null>"),
Esc => seq.push_str("Esc>"),
CapsLock => seq.push_str("CapsL>"),
ScrollLock => seq.push_str("ScrollL>"),
NumLock => seq.push_str("NumL>"),
PrintScreen => seq.push_str("PrSc>"),
Pause => seq.push_str("Pause>"),
Menu => seq.push_str("Menu>"),
KeypadBegin => seq.push_str("KeypadBeg>"),
Media(m_code) => write!(seq, "Media{m_code}>").unwrap(),
Modifier(m_code) => write!(seq, "Mod{m_code}>").unwrap(),
}
}
seq
}
pub fn str_to_keys(str: &str) -> Vec<KeyEvent> {
const SPECIAL: &[(&str, KeyCode)] = &[
("Enter", KeyCode::Enter),
("Tab", KeyCode::Tab),
("Bspc", KeyCode::Backspace),
("Del", KeyCode::Delete),
("Esc", KeyCode::Esc),
("Up", KeyCode::Up),
("Down", KeyCode::Down),
("Left", KeyCode::Left),
("Right", KeyCode::Right),
("PageU", KeyCode::PageUp),
("PageD", KeyCode::PageDown),
("Home", KeyCode::Home),
("End", KeyCode::End),
("Ins", KeyCode::Insert),
("F1", KeyCode::F(1)),
("F2", KeyCode::F(2)),
("F3", KeyCode::F(3)),
("F4", KeyCode::F(4)),
("F5", KeyCode::F(5)),
("F6", KeyCode::F(6)),
("F7", KeyCode::F(7)),
("F8", KeyCode::F(8)),
("F9", KeyCode::F(9)),
("F10", KeyCode::F(10)),
("F11", KeyCode::F(11)),
("F12", KeyCode::F(12)),
];
const MODS: &[(&str, KeyMod)] = &[
("c", KeyMod::CONTROL),
("a", KeyMod::ALT),
("s", KeyMod::SHIFT),
("m", KeyMod::META),
("super", KeyMod::SUPER),
("hyper", KeyMod::HYPER),
];
fn match_key(chars: Chars) -> Option<(KeyEvent, Chars)> {
let matched_mods = {
let mut chars = chars.clone();
let mut mods = KeyMod::empty();
let mut seq = String::new();
loop {
let char = chars.next()?;
if char == '-' {
if mods.is_empty() {
break None;
} else {
break Some((mods, chars));
}
}
seq.push(char);
if let Some((_, m)) = MODS.iter().find(|(str, _)| str == &seq)
&& !mods.contains(*m)
{
mods = mods.union(*m);
seq.clear();
} else if !MODS[4..6].iter().any(|(str, _)| str.starts_with(&seq)) {
break None;
}
}
};
let (mut mods, mut chars) = match matched_mods {
Some((mods, chars)) => (mods, chars),
None => (KeyMod::empty(), chars),
};
let mut code = Some(chars.next().map(KeyCode::Char)?);
let mut seq = code.unwrap().to_string();
loop {
if let Some(c) = code.take() {
match chars.next()? {
'>' if seq.len() > 1 || !mods.is_empty() => {
if let KeyCode::Char(_) = c {
mods.remove(KeyMod::SHIFT);
}
break Some((KeyEvent::new(c, mods), chars));
}
_ if seq.len() > 1 => break None,
char => seq.push(char),
}
}
if let Some((str, c)) = SPECIAL.iter().find(|(str, _)| str.starts_with(&seq)) {
if str == &seq {
code = Some(*c);
} else {
seq.push(chars.next()?);
}
} else {
break None;
}
}
}
let mut keys = Vec::new();
let mut chars = str.chars();
let mut next = chars.next();
while let Some(char) = next {
if char == '<'
&& let Some((key, ahead)) = match_key(chars.clone())
{
keys.push(key);
chars = ahead;
} else {
keys.push(KeyEvent::from(KeyCode::Char(char)));
}
next = chars.next();
}
keys
}
#[doc(hidden)]
pub trait IntoMapsTo: Send + 'static {
fn into_maps_to(self) -> MapsTo;
}
impl IntoMapsTo for &'static str {
fn into_maps_to(self) -> MapsTo {
MapsTo::Keys(str_to_keys(self))
}
}
impl<F: for<'p> FnMut(&'p mut Pass) + Send + 'static> IntoMapsTo for F {
fn into_maps_to(self) -> MapsTo {
MapsTo::Function(Box::new(Mutex::new(self)))
}
}
pub(crate) fn send_key_event(pa: &mut Pass, mut key: KeyEvent) {
if let KeyCode::Char(_) = key.code {
key.modifiers.remove(KeyMod::SHIFT);
}
SEND_KEY.read(pa)(pa, key);
crate::hook::trigger(pa, crate::hook::KeyTyped(key));
}
pub(in crate::mode) fn set_mode_for_remapper<M: Mode>(pa: &mut Pass) {
REMAPPER
.write(pa)
.mapped_bindings
.entry(TypeId::of::<M>())
.or_insert_with(MappedBindings::for_mode::<M>);
*SEND_KEY.write(pa) = |pa, key| super::send_key::<M>(&REMAPPER, pa, key);
*MODE_TYPE_ID.lock().unwrap() = TypeId::of::<M>();
}
}
#[derive(Default)]
struct Remapper {
mapped_bindings: HashMap<TypeId, MappedBindings>,
mapped_seq: Vec<KeyEvent>,
}
impl Remapper {
fn remap<M: Mode>(
&mut self,
takes: Vec<KeyEvent>,
gives: MapsTo,
is_alias: bool,
doc: Option<Text>,
) {
fn remap_inner(
inner: &mut Remapper,
ty: TypeId,
takes: Vec<KeyEvent>,
gives: MapsTo,
is_alias: bool,
doc: Option<Text>,
) {
let mapped_bindings = inner.mapped_bindings.get_mut(&ty).unwrap();
let remap = Remap::new(takes, gives, is_alias, doc);
if let Some(i) = mapped_bindings.remaps.iter().position(|r| {
r.takes.starts_with(&remap.takes) || remap.takes.starts_with(&r.takes)
}) {
mapped_bindings.remaps[i] = remap;
} else {
mapped_bindings.remaps.push(remap);
}
}
self.mapped_bindings
.entry(TypeId::of::<M>())
.or_insert_with(MappedBindings::for_mode::<M>);
remap_inner(self, TypeId::of::<M>(), takes, gives, is_alias, doc);
}
}
fn send_key<M: Mode>(bdw: &BulkDataWriter<Remapper>, pa: &mut Pass, key: KeyEvent) {
fn send_key_inner(
bdw: &BulkDataWriter<Remapper>,
key_event: KeyEvent,
pa: &mut Pass,
ty: TypeId,
) {
let ((cur_seq, is_alias), remapper) = pa.write_many((&*CUR_SEQ, bdw));
let mapped_bindings = &remapper.mapped_bindings[&ty];
cur_seq.push(key_event);
if !mapped_bindings.sequence_has_followup(cur_seq) {
cur_seq.clear();
}
remapper.mapped_seq.push(key_event);
let clear_mapped_sequence = |pa: &mut Pass| {
bdw.write(pa).mapped_seq.clear();
CUR_SEQ.write(pa).1 = false;
};
let (mapped_seq, is_alias) = (remapper.mapped_seq.clone(), *is_alias);
let keys_to_send = if let Some(i) = mapped_bindings
.remaps
.iter()
.position(|r| r.takes.starts_with(&mapped_seq))
{
let remap = &mapped_bindings.remaps[i];
if remap.takes.len() == mapped_seq.len() {
if remap.is_alias {
remove_alias_and(pa, |_, _| {});
}
clear_mapped_sequence(pa);
let mapped_bindings = &mut bdw.write(pa).mapped_bindings;
let remap = mapped_bindings.get_mut(&ty).unwrap().remaps.remove(i);
match &remap.gives {
MapsTo::Keys(keys) => {
let keys = keys.clone();
let mapped_bindings = &mut bdw.write(pa).mapped_bindings;
mapped_bindings
.get_mut(&ty)
.unwrap()
.remaps
.insert(i, remap);
keys
}
MapsTo::Function(function) => {
function.lock().unwrap()(pa);
let mapped_bindings = &mut bdw.write(pa).mapped_bindings;
mapped_bindings
.get_mut(&ty)
.unwrap()
.remaps
.insert(i, remap);
return;
}
}
} else {
if remap.is_alias {
CUR_SEQ.write(pa).1 = true;
remove_alias_and(pa, |widget, main| {
widget.text_mut().insert_tag(
Ns::for_alias(),
main,
Inlay::new(txt!("[alias]{}", keys_to_string(&mapped_seq))),
);
});
}
return;
}
} else {
if is_alias {
remove_alias_and(pa, |_, _| {});
}
clear_mapped_sequence(pa);
mapped_seq
};
mode::send_keys_to(pa, keys_to_send);
}
send_key_inner(bdw, key, pa, TypeId::of::<M>());
}
#[derive(Debug)]
struct Remap {
takes: Vec<KeyEvent>,
gives: MapsTo,
is_alias: bool,
desc: Option<Text>,
}
impl Remap {
pub fn new(takes: Vec<KeyEvent>, gives: MapsTo, is_alias: bool, desc: Option<Text>) -> Self {
Self { takes, gives, is_alias, desc }
}
}
pub enum MapsTo {
Keys(Vec<KeyEvent>),
Function(Box<Mutex<dyn FnMut(&mut Pass) + Send>>),
}
impl std::fmt::Debug for MapsTo {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Keys(arg0) => f.debug_tuple("Keys").field(arg0).finish(),
Self::Function(_) => f.debug_tuple("Function").finish_non_exhaustive(),
}
}
}
#[derive(Debug)]
pub struct MappedBindings {
bindings: Bindings,
remaps: Vec<Remap>,
}
impl MappedBindings {
fn for_mode<M: Mode>() -> Self {
Self {
bindings: M::bindings(),
remaps: Vec::new(),
}
}
fn new(bindings: Bindings) -> Self {
Self { bindings, remaps: Vec::new() }
}
}
impl MappedBindings {
pub fn matches_sequence(&self, seq: &[KeyEvent]) -> bool {
self.remaps.iter().any(|remap| {
seq.starts_with(&remap.takes) && self.matches_sequence(&seq[remap.takes.len()..])
}) || self.bindings.matches_sequence(seq)
}
pub fn sequence_has_followup(&self, seq: &[KeyEvent]) -> bool {
self.remaps
.iter()
.any(|remap| remap.takes.starts_with(seq) && remap.takes.len() > seq.len())
|| self.bindings.sequence_has_followup(seq)
}
pub fn descriptions_for<'a>(
&'a self,
seq: &'a [KeyEvent],
) -> (Option<&'a Text>, impl Iterator<Item = Description<'a>>) {
let bindings = self.bindings.bindings_for(seq);
let iter = bindings
.into_iter()
.flat_map(|bindings| bindings.list.iter())
.map(|(pats, desc, _)| Description {
text: Some(desc),
keys: KeyDescriptions {
seq,
ty: DescriptionType::Binding(pats, pats.iter(), StripPrefix {
seq,
remaps: self.remaps.iter(),
}),
},
})
.chain(
self.remaps
.iter()
.filter(move |remap| {
if !remap.takes.starts_with(seq) {
return false;
}
if remap.desc.is_some() {
return true;
}
if let (MapsTo::Keys(gives), Some(bindings)) = (&remap.gives, bindings)
&& gives.len() == 1
&& bindings
.list
.iter()
.any(|(pats, ..)| pats.iter().any(|pat| pat.matches(gives[0])))
{
false
} else {
true
}
})
.map(|remap| Description {
text: remap.desc.as_ref().or_else(|| {
if let MapsTo::Keys(keys) = &remap.gives {
self.bindings.description_for(keys)
} else {
None
}
}),
keys: KeyDescriptions {
seq,
ty: DescriptionType::Remap(Some(remap)),
},
}),
);
(bindings.and_then(|b| b.title.as_ref()), iter)
}
fn replace_seq_description(&mut self, seq: &[KeyEvent], new: Text) {
if let Some(remap) = self.remaps.iter_mut().find(|remap| remap.takes == seq) {
remap.desc = (!new.is_empty_empty()).then_some(new);
} else if let Some(desc) = self.bindings.description_for_mut(seq) {
*desc = new;
}
}
}
pub struct Description<'a> {
pub text: Option<&'a Text>,
pub keys: KeyDescriptions<'a>,
}
#[derive(Debug)]
pub enum KeyDescription<'a> {
Binding(Binding),
Remap(&'a [KeyEvent], &'a MapsTo),
}
pub struct KeyDescriptions<'a> {
seq: &'a [KeyEvent],
ty: DescriptionType<'a>,
}
impl KeyDescriptions<'_> {
pub fn into_text(self) -> Text {
let mut builder = Text::builder();
for (i, key_desc) in self.enumerate() {
if i > 0 {
builder.push(txt!("[separator],"));
}
match key_desc {
KeyDescription::Binding(binding) => builder.push(binding.as_text()),
KeyDescription::Remap(key_events, _) => {
builder.push(txt!("[remap:100]{}", super::keys_to_text(key_events)))
}
}
}
builder.build()
}
}
impl<'a> Iterator for KeyDescriptions<'a> {
type Item = KeyDescription<'a>;
fn next(&mut self) -> Option<Self::Item> {
let (pats, pats_iter, deprefixed) = match &mut self.ty {
DescriptionType::Binding(pats, pats_iter, deprefixed) => (pats, pats_iter, deprefixed),
DescriptionType::Remap(remap) => {
return remap.take().and_then(|remap| {
remap
.takes
.strip_prefix(self.seq)
.map(|takes| KeyDescription::Remap(takes, &remap.gives))
});
}
};
pats_iter
.find_map(|pat| {
pat.as_key_event()
.is_none_or(|key_event| {
!deprefixed
.clone()
.any(|(_, takes)| takes.starts_with(&[key_event]))
})
.then_some(KeyDescription::Binding(*pat))
})
.or_else(|| {
deprefixed.find_map(|(remap, takes)| {
if remap.takes.starts_with(self.seq)
&& remap.desc.is_none()
&& let MapsTo::Keys(given_keys) = &remap.gives
&& given_keys.len() == 1
&& pats.iter().any(|pat| pat.matches(given_keys[0]))
{
Some(KeyDescription::Remap(takes, &remap.gives))
} else {
None
}
})
})
}
}
enum DescriptionType<'a> {
Binding(&'a [Binding], slice::Iter<'a, Binding>, StripPrefix<'a>),
Remap(Option<&'a Remap>),
}
fn remove_alias_and(pa: &mut Pass, f: impl FnOnce(&mut dyn Widget, usize)) {
let widget = context::current_widget_node(pa);
widget.mutate_data(pa, |handle| {
let pa = unsafe { &mut Pass::new() };
let widget = handle.write(pa);
if let Some(main) = widget.text().get_main_sel() {
let byte = main.caret().byte();
widget.text_mut().remove_tags(Ns::for_alias(), ..);
f(&mut *widget, byte)
}
})
}
#[derive(Clone)]
struct StripPrefix<'a> {
seq: &'a [KeyEvent],
remaps: slice::Iter<'a, Remap>,
}
impl<'a> Iterator for StripPrefix<'a> {
type Item = (&'a Remap, &'a [KeyEvent]);
fn next(&mut self) -> Option<Self::Item> {
let remap = self.remaps.next()?;
Some((remap, remap.takes.strip_prefix(self.seq)?))
}
}