Skip to content

Commit

Permalink
Increase support for kitty enhanced keyboard protocol (#688)
Browse files Browse the repository at this point in the history
  • Loading branch information
pianohacker committed Jul 24, 2022
1 parent 4dcc6fc commit 60e51be
Show file tree
Hide file tree
Showing 4 changed files with 293 additions and 12 deletions.
14 changes: 12 additions & 2 deletions examples/event-match-modifiers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,37 @@
//!
//! cargo run --example event-match-modifiers

use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};

fn match_event(read_event: Event) {
match read_event {
// Match one one modifier:
Event::Key(KeyEvent {
modifiers: KeyModifiers::CONTROL,
code,
..
}) => {
println!("Control + {:?}", code);
}
Event::Key(KeyEvent {
modifiers: KeyModifiers::SHIFT,
code,
..
}) => {
println!("Shift + {:?}", code);
}
Event::Key(KeyEvent {
modifiers: KeyModifiers::ALT,
code,
..
}) => {
println!("Alt + {:?}", code);
}

// Match on multiple modifiers:
Event::Key(KeyEvent { code, modifiers }) => {
Event::Key(KeyEvent {
code, modifiers, ..
}) => {
if modifiers == (KeyModifiers::ALT | KeyModifiers::SHIFT) {
println!("Alt + Shift {:?}", code);
} else {
Expand All @@ -43,21 +48,26 @@ fn main() {
match_event(Event::Key(KeyEvent {
modifiers: KeyModifiers::CONTROL,
code: KeyCode::Char('z'),
kind: KeyEventKind::Press,
}));
match_event(Event::Key(KeyEvent {
modifiers: KeyModifiers::SHIFT,
code: KeyCode::Left,
kind: KeyEventKind::Press,
}));
match_event(Event::Key(KeyEvent {
modifiers: KeyModifiers::ALT,
code: KeyCode::Delete,
kind: KeyEventKind::Press,
}));
match_event(Event::Key(KeyEvent {
modifiers: KeyModifiers::ALT | KeyModifiers::SHIFT,
code: KeyCode::Right,
kind: KeyEventKind::Press,
}));
match_event(Event::Key(KeyEvent {
modifiers: KeyModifiers::ALT | KeyModifiers::CONTROL,
code: KeyCode::Home,
kind: KeyEventKind::Press,
}));
}
155 changes: 152 additions & 3 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -294,6 +294,120 @@ impl Command for DisableMouseCapture {
}
}

bitflags! {
/// Represents special flags that tell compatible terminals to add extra information to keyboard events.
///
/// See <https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement> for more information.
///
/// Alternate keys and Unicode codepoints are not yet supported by crossterm.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct KeyboardEnhancementFlags: u8 {
/// Represent Escape and modified keys using CSI-u sequences, so they can be unambiguously
/// read.
const DISAMBIGUATE_ESCAPE_CODES = 0b0000_0001;
/// Add extra events with [`KeyEvent.kind`] set to [`KeyEventKind::Repeat`] or
/// [`KeyEventKind::Release`] when keys are autorepeated or released.
const REPORT_EVENT_TYPES = 0b0000_0010;
// Send [alternate keycodes](https://sw.kovidgoyal.net/kitty/keyboard-protocol/#key-codes)
// in addition to the base keycode.
//
// *Note*: these are not yet supported by crossterm.
// const REPORT_ALTERNATE_KEYS = 0b0000_0100;
/// Represent all keyboard events as CSI-u sequences. This is required to get repeat/release
/// events for plain-text keys.
const REPORT_ALL_KEYS_AS_ESCAPE_CODES = 0b0000_1000;
// Send the Unicode codepoint as well as the keycode.
//
// *Note*: this is not yet supported by crossterm.
// const REPORT_ASSOCIATED_TEXT = 0b0001_0000;
}
}

/// A command that enables the [kitty keyboard protocol](https://sw.kovidgoyal.net/kitty/keyboard-protocol/), which adds extra information to keyboard events and removes ambiguity for modifier keys.
///
/// It should be paired with [`PopKeyboardEnhancementFlags`] at the end of execution.
///
/// Example usage:
/// ```no_run
/// use std::io::{Write, stdout};
/// use crossterm::execute;
/// use crossterm::event::{
/// KeyboardEnhancementFlags,
/// PushKeyboardEnhancementFlags,
/// PopKeyboardEnhancementFlags
/// };
///
/// let mut stdout = stdout();
///
/// execute!(
/// stdout,
/// PushKeyboardEnhancementFlags(
/// KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
/// )
/// );
///
/// // ...
///
/// execute!(stdout, PopKeyboardEnhancementFlags);
/// ```
///
/// Note that, currently, only the following support this protocol:
/// * [kitty terminal](https://sw.kovidgoyal.net/kitty/)
/// * [foot terminal](https://codeberg.org/dnkl/foot/issues/319)
/// * [WezTerm terminal](https://wezfurlong.org/wezterm/config/lua/config/enable_kitty_keyboard.html)
/// * [notcurses library](https://github.com/dankamongmen/notcurses/issues/2131)
/// * [neovim text editor](https://github.com/neovim/neovim/pull/18181)
/// * [kakoune text editor](https://github.com/mawww/kakoune/issues/4103)
/// * [dte text editor](https://gitlab.com/craigbarnes/dte/-/issues/138)
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PushKeyboardEnhancementFlags(pub KeyboardEnhancementFlags);

impl Command for PushKeyboardEnhancementFlags {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
write!(f, "{}{}u", csi!(">"), self.0.bits())
}

#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"Keyboard progressive enhancement not implemented on Windows.",
))
}

#[cfg(windows)]
fn is_ansi_code_supported(&self) -> bool {
false
}
}

/// A command that disables extra kinds of keyboard events.
///
/// Specifically, it pops one level of keyboard enhancement flags.
///
/// See [`PushKeyboardEnhancementFlags`] and <https://sw.kovidgoyal.net/kitty/keyboard-protocol/> for more information.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PopKeyboardEnhancementFlags;

impl Command for PopKeyboardEnhancementFlags {
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
f.write_str(csi!("<1u"))
}

#[cfg(windows)]
fn execute_winapi(&self) -> Result<()> {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"Keyboard progressive enhancement not implemented on Windows.",
))
}

#[cfg(windows)]
fn is_ansi_code_supported(&self) -> bool {
false
}
}

/// Represents an event.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
Expand Down Expand Up @@ -384,6 +498,15 @@ bitflags! {
}
}

/// Represents a keyboard event kind.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
pub enum KeyEventKind {
Press,
Repeat,
Release,
}

/// Represents a key event.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(Debug, PartialOrd, Clone, Copy)]
Expand All @@ -392,11 +515,29 @@ pub struct KeyEvent {
pub code: KeyCode,
/// Additional key modifiers.
pub modifiers: KeyModifiers,
/// Kind of event.
pub kind: KeyEventKind,
}

impl KeyEvent {
pub const fn new(code: KeyCode, modifiers: KeyModifiers) -> KeyEvent {
KeyEvent { code, modifiers }
KeyEvent {
code,
modifiers,
kind: KeyEventKind::Press,
}
}

pub const fn new_with_kind(
code: KeyCode,
modifiers: KeyModifiers,
kind: KeyEventKind,
) -> KeyEvent {
KeyEvent {
code,
modifiers,
kind,
}
}

// modifies the KeyEvent,
Expand All @@ -422,6 +563,7 @@ impl From<KeyCode> for KeyEvent {
KeyEvent {
code,
modifiers: KeyModifiers::empty(),
kind: KeyEventKind::Press,
}
}
}
Expand All @@ -431,22 +573,29 @@ impl PartialEq for KeyEvent {
let KeyEvent {
code: lhs_code,
modifiers: lhs_modifiers,
kind: lhs_kind,
} = self.normalize_case();
let KeyEvent {
code: rhs_code,
modifiers: rhs_modifiers,
kind: rhs_kind,
} = other.normalize_case();
(lhs_code == rhs_code) && (lhs_modifiers == rhs_modifiers)
(lhs_code == rhs_code) && (lhs_modifiers == rhs_modifiers) && (lhs_kind == rhs_kind)
}
}

impl Eq for KeyEvent {}

impl Hash for KeyEvent {
fn hash<H: Hasher>(&self, state: &mut H) {
let KeyEvent { code, modifiers } = self.normalize_case();
let KeyEvent {
code,
modifiers,
kind,
} = self.normalize_case();
code.hash(state);
modifiers.hash(state);
kind.hash(state);
}
}

Expand Down
Loading

0 comments on commit 60e51be

Please sign in to comment.