use {
crate::*,
crossterm::{
event::{
KeyCode,
KeyEvent,
KeyboardEnhancementFlags,
KeyEventKind,
KeyModifiers,
ModifierKeyCode,
PopKeyboardEnhancementFlags,
PushKeyboardEnhancementFlags,
},
execute,
terminal,
},
std::{
io,
ops::Drop,
},
};
const MAX_PRESS_COUNT: usize = 3;
#[derive(Debug)]
pub struct Combiner {
combining: bool,
keyboard_enhancement_flags_pushed: bool,
mandate_modifier_for_multiple_keys: bool,
down_keys: Vec<KeyEvent>,
shift_pressed: bool,
}
impl Default for Combiner {
fn default() -> Self {
Self {
combining: false,
keyboard_enhancement_flags_pushed: false,
mandate_modifier_for_multiple_keys: true,
down_keys: Vec::new(),
shift_pressed: false,
}
}
}
impl Combiner {
pub fn enable_combining(&mut self) -> io::Result<bool> {
if self.combining {
return Ok(true);
}
if self.keyboard_enhancement_flags_pushed {
return Ok(self.combining);
}
if !terminal::supports_keyboard_enhancement()? {
return Ok(false);
}
push_keyboard_enhancement_flags()?;
self.keyboard_enhancement_flags_pushed = true;
self.combining = true;
Ok(true)
}
pub fn disable_combining(&mut self) -> io::Result<()> {
if self.keyboard_enhancement_flags_pushed {
pop_keyboard_enhancement_flags()?;
self.keyboard_enhancement_flags_pushed = false;
}
self.combining = false;
Ok(())
}
pub fn is_combining(&self) -> bool {
self.combining
}
pub fn set_mandate_modifier_for_multiple_keys(&mut self, mandate: bool) {
self.mandate_modifier_for_multiple_keys = mandate;
}
fn combine(&mut self, clear: bool) -> Option<KeyCombination> {
let mut key_combination = KeyCombination::try_from(self.down_keys.as_slice())
.ok(); if self.shift_pressed {
if let Some(ref mut key_combination) = key_combination {
key_combination.modifiers |= KeyModifiers::SHIFT;
}
}
if clear {
self.down_keys.clear();
self.shift_pressed = false;
}
key_combination
}
pub fn transform(&mut self, key: KeyEvent) -> Option<KeyCombination> {
if self.combining {
self.transform_combining(key)
} else {
self.transform_ansi(key)
}
}
fn transform_combining(&mut self, key: KeyEvent) -> Option<KeyCombination> {
if let KeyCode::Modifier(modifier) = key.code {
if modifier == ModifierKeyCode::LeftShift || modifier == ModifierKeyCode::RightShift {
self.shift_pressed = key.kind != KeyEventKind::Release;
}
return None;
}
if
self.mandate_modifier_for_multiple_keys
&& is_key_simple(key)
&& !self.shift_pressed
&& self.down_keys.is_empty()
{
match key.kind {
KeyEventKind::Press | KeyEventKind::Repeat => {
self.down_keys.push(key);
self.combine(true)
}
KeyEventKind::Release => {
None
}
}
} else {
match key.kind {
KeyEventKind::Press => {
self.down_keys.push(key);
if self.down_keys.len() == MAX_PRESS_COUNT {
self.combine(true)
} else {
None
}
}
KeyEventKind::Release => {
self.combine(true)
}
KeyEventKind::Repeat => {
self.combine(false)
}
}
}
}
fn transform_ansi(&mut self, key: KeyEvent) -> Option<KeyCombination> {
match key.kind {
KeyEventKind::Press => Some(key.into()),
_ => {
None
}
}
}
}
pub fn is_key_simple(key: KeyEvent) -> bool {
key.modifiers.is_empty()
&& key.code != KeyCode::Char(' ')
}
impl Drop for Combiner {
fn drop(&mut self) {
if self.keyboard_enhancement_flags_pushed {
let _ = pop_keyboard_enhancement_flags();
}
}
}
pub fn push_keyboard_enhancement_flags() -> io::Result<()> {
let mut stdout = io::stdout();
execute!(
stdout,
PushKeyboardEnhancementFlags(
KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
| KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES
| KeyboardEnhancementFlags::REPORT_ALTERNATE_KEYS
| KeyboardEnhancementFlags::REPORT_EVENT_TYPES
)
)
}
pub fn pop_keyboard_enhancement_flags() -> io::Result<()>{
let mut stdout = io::stdout();
execute!(stdout, PopKeyboardEnhancementFlags)
}