Skip to content

Commit 60e51be

Browse files
authored
Increase support for kitty enhanced keyboard protocol (#688)
1 parent 4dcc6fc commit 60e51be

File tree

4 files changed

+293
-12
lines changed

4 files changed

+293
-12
lines changed

examples/event-match-modifiers.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,32 +2,37 @@
22
//!
33
//! cargo run --example event-match-modifiers
44
5-
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
5+
use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
66

77
fn match_event(read_event: Event) {
88
match read_event {
99
// Match one one modifier:
1010
Event::Key(KeyEvent {
1111
modifiers: KeyModifiers::CONTROL,
1212
code,
13+
..
1314
}) => {
1415
println!("Control + {:?}", code);
1516
}
1617
Event::Key(KeyEvent {
1718
modifiers: KeyModifiers::SHIFT,
1819
code,
20+
..
1921
}) => {
2022
println!("Shift + {:?}", code);
2123
}
2224
Event::Key(KeyEvent {
2325
modifiers: KeyModifiers::ALT,
2426
code,
27+
..
2528
}) => {
2629
println!("Alt + {:?}", code);
2730
}
2831

2932
// Match on multiple modifiers:
30-
Event::Key(KeyEvent { code, modifiers }) => {
33+
Event::Key(KeyEvent {
34+
code, modifiers, ..
35+
}) => {
3136
if modifiers == (KeyModifiers::ALT | KeyModifiers::SHIFT) {
3237
println!("Alt + Shift {:?}", code);
3338
} else {
@@ -43,21 +48,26 @@ fn main() {
4348
match_event(Event::Key(KeyEvent {
4449
modifiers: KeyModifiers::CONTROL,
4550
code: KeyCode::Char('z'),
51+
kind: KeyEventKind::Press,
4652
}));
4753
match_event(Event::Key(KeyEvent {
4854
modifiers: KeyModifiers::SHIFT,
4955
code: KeyCode::Left,
56+
kind: KeyEventKind::Press,
5057
}));
5158
match_event(Event::Key(KeyEvent {
5259
modifiers: KeyModifiers::ALT,
5360
code: KeyCode::Delete,
61+
kind: KeyEventKind::Press,
5462
}));
5563
match_event(Event::Key(KeyEvent {
5664
modifiers: KeyModifiers::ALT | KeyModifiers::SHIFT,
5765
code: KeyCode::Right,
66+
kind: KeyEventKind::Press,
5867
}));
5968
match_event(Event::Key(KeyEvent {
6069
modifiers: KeyModifiers::ALT | KeyModifiers::CONTROL,
6170
code: KeyCode::Home,
71+
kind: KeyEventKind::Press,
6272
}));
6373
}

src/event.rs

Lines changed: 152 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,120 @@ impl Command for DisableMouseCapture {
294294
}
295295
}
296296

297+
bitflags! {
298+
/// Represents special flags that tell compatible terminals to add extra information to keyboard events.
299+
///
300+
/// See <https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement> for more information.
301+
///
302+
/// Alternate keys and Unicode codepoints are not yet supported by crossterm.
303+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
304+
pub struct KeyboardEnhancementFlags: u8 {
305+
/// Represent Escape and modified keys using CSI-u sequences, so they can be unambiguously
306+
/// read.
307+
const DISAMBIGUATE_ESCAPE_CODES = 0b0000_0001;
308+
/// Add extra events with [`KeyEvent.kind`] set to [`KeyEventKind::Repeat`] or
309+
/// [`KeyEventKind::Release`] when keys are autorepeated or released.
310+
const REPORT_EVENT_TYPES = 0b0000_0010;
311+
// Send [alternate keycodes](https://sw.kovidgoyal.net/kitty/keyboard-protocol/#key-codes)
312+
// in addition to the base keycode.
313+
//
314+
// *Note*: these are not yet supported by crossterm.
315+
// const REPORT_ALTERNATE_KEYS = 0b0000_0100;
316+
/// Represent all keyboard events as CSI-u sequences. This is required to get repeat/release
317+
/// events for plain-text keys.
318+
const REPORT_ALL_KEYS_AS_ESCAPE_CODES = 0b0000_1000;
319+
// Send the Unicode codepoint as well as the keycode.
320+
//
321+
// *Note*: this is not yet supported by crossterm.
322+
// const REPORT_ASSOCIATED_TEXT = 0b0001_0000;
323+
}
324+
}
325+
326+
/// 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.
327+
///
328+
/// It should be paired with [`PopKeyboardEnhancementFlags`] at the end of execution.
329+
///
330+
/// Example usage:
331+
/// ```no_run
332+
/// use std::io::{Write, stdout};
333+
/// use crossterm::execute;
334+
/// use crossterm::event::{
335+
/// KeyboardEnhancementFlags,
336+
/// PushKeyboardEnhancementFlags,
337+
/// PopKeyboardEnhancementFlags
338+
/// };
339+
///
340+
/// let mut stdout = stdout();
341+
///
342+
/// execute!(
343+
/// stdout,
344+
/// PushKeyboardEnhancementFlags(
345+
/// KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
346+
/// )
347+
/// );
348+
///
349+
/// // ...
350+
///
351+
/// execute!(stdout, PopKeyboardEnhancementFlags);
352+
/// ```
353+
///
354+
/// Note that, currently, only the following support this protocol:
355+
/// * [kitty terminal](https://sw.kovidgoyal.net/kitty/)
356+
/// * [foot terminal](https://codeberg.org/dnkl/foot/issues/319)
357+
/// * [WezTerm terminal](https://wezfurlong.org/wezterm/config/lua/config/enable_kitty_keyboard.html)
358+
/// * [notcurses library](https://github.com/dankamongmen/notcurses/issues/2131)
359+
/// * [neovim text editor](https://github.com/neovim/neovim/pull/18181)
360+
/// * [kakoune text editor](https://github.com/mawww/kakoune/issues/4103)
361+
/// * [dte text editor](https://gitlab.com/craigbarnes/dte/-/issues/138)
362+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
363+
pub struct PushKeyboardEnhancementFlags(pub KeyboardEnhancementFlags);
364+
365+
impl Command for PushKeyboardEnhancementFlags {
366+
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
367+
write!(f, "{}{}u", csi!(">"), self.0.bits())
368+
}
369+
370+
#[cfg(windows)]
371+
fn execute_winapi(&self) -> Result<()> {
372+
Err(io::Error::new(
373+
io::ErrorKind::Unsupported,
374+
"Keyboard progressive enhancement not implemented on Windows.",
375+
))
376+
}
377+
378+
#[cfg(windows)]
379+
fn is_ansi_code_supported(&self) -> bool {
380+
false
381+
}
382+
}
383+
384+
/// A command that disables extra kinds of keyboard events.
385+
///
386+
/// Specifically, it pops one level of keyboard enhancement flags.
387+
///
388+
/// See [`PushKeyboardEnhancementFlags`] and <https://sw.kovidgoyal.net/kitty/keyboard-protocol/> for more information.
389+
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
390+
pub struct PopKeyboardEnhancementFlags;
391+
392+
impl Command for PopKeyboardEnhancementFlags {
393+
fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
394+
f.write_str(csi!("<1u"))
395+
}
396+
397+
#[cfg(windows)]
398+
fn execute_winapi(&self) -> Result<()> {
399+
Err(io::Error::new(
400+
io::ErrorKind::Unsupported,
401+
"Keyboard progressive enhancement not implemented on Windows.",
402+
))
403+
}
404+
405+
#[cfg(windows)]
406+
fn is_ansi_code_supported(&self) -> bool {
407+
false
408+
}
409+
}
410+
297411
/// Represents an event.
298412
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
299413
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
@@ -384,6 +498,15 @@ bitflags! {
384498
}
385499
}
386500

501+
/// Represents a keyboard event kind.
502+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
503+
#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
504+
pub enum KeyEventKind {
505+
Press,
506+
Repeat,
507+
Release,
508+
}
509+
387510
/// Represents a key event.
388511
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
389512
#[derive(Debug, PartialOrd, Clone, Copy)]
@@ -392,11 +515,29 @@ pub struct KeyEvent {
392515
pub code: KeyCode,
393516
/// Additional key modifiers.
394517
pub modifiers: KeyModifiers,
518+
/// Kind of event.
519+
pub kind: KeyEventKind,
395520
}
396521

397522
impl KeyEvent {
398523
pub const fn new(code: KeyCode, modifiers: KeyModifiers) -> KeyEvent {
399-
KeyEvent { code, modifiers }
524+
KeyEvent {
525+
code,
526+
modifiers,
527+
kind: KeyEventKind::Press,
528+
}
529+
}
530+
531+
pub const fn new_with_kind(
532+
code: KeyCode,
533+
modifiers: KeyModifiers,
534+
kind: KeyEventKind,
535+
) -> KeyEvent {
536+
KeyEvent {
537+
code,
538+
modifiers,
539+
kind,
540+
}
400541
}
401542

402543
// modifies the KeyEvent,
@@ -422,6 +563,7 @@ impl From<KeyCode> for KeyEvent {
422563
KeyEvent {
423564
code,
424565
modifiers: KeyModifiers::empty(),
566+
kind: KeyEventKind::Press,
425567
}
426568
}
427569
}
@@ -431,22 +573,29 @@ impl PartialEq for KeyEvent {
431573
let KeyEvent {
432574
code: lhs_code,
433575
modifiers: lhs_modifiers,
576+
kind: lhs_kind,
434577
} = self.normalize_case();
435578
let KeyEvent {
436579
code: rhs_code,
437580
modifiers: rhs_modifiers,
581+
kind: rhs_kind,
438582
} = other.normalize_case();
439-
(lhs_code == rhs_code) && (lhs_modifiers == rhs_modifiers)
583+
(lhs_code == rhs_code) && (lhs_modifiers == rhs_modifiers) && (lhs_kind == rhs_kind)
440584
}
441585
}
442586

443587
impl Eq for KeyEvent {}
444588

445589
impl Hash for KeyEvent {
446590
fn hash<H: Hasher>(&self, state: &mut H) {
447-
let KeyEvent { code, modifiers } = self.normalize_case();
591+
let KeyEvent {
592+
code,
593+
modifiers,
594+
kind,
595+
} = self.normalize_case();
448596
code.hash(state);
449597
modifiers.hash(state);
598+
kind.hash(state);
450599
}
451600
}
452601

0 commit comments

Comments
 (0)