Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
//! #[cfg(feature = "bracketed-paste")]
//! Event::Paste(data) => println!("{:?}", data),
//! Event::Resize(width, height) => println!("New size {}x{}", width, height),
//! Event::ApplicationProgramCommand(command) => println!("New APC {}", command),
//! }
//! }
//! execute!(
Expand Down Expand Up @@ -100,6 +101,7 @@
//! #[cfg(feature = "bracketed-paste")]
//! Event::Paste(data) => println!("Pasted {:?}", data),
//! Event::Resize(width, height) => println!("New size {}x{}", width, height),
//! Event::ApplicationProgramCommand(command) => println!("New APC {}", command),
//! }
//! } else {
//! // Timeout expired and no `Event` is available
Expand Down Expand Up @@ -563,6 +565,9 @@ pub enum Event {
/// A resize event with new dimensions after resize (columns, rows).
/// **Note** that resize events can occur in batches.
Resize(u16, u16),
/// Application Program Command sent by the terminal.
/// Primarily used by the Kitty terminal for graphics commands.
ApplicationProgramCommand(String),
}

impl Event {
Expand Down Expand Up @@ -755,6 +760,28 @@ impl Event {
_ => None,
}
}

/// Returns the string of the command if the event is a APC event, otherwise `None`.
///
/// This is a convenience method that makes code which only cares about resize events easier to write.
///
/// # Examples
///
/// ```no_run
/// use crossterm::event;
///
/// while let Some(command) = event::read()?.as_apc_event() {
/// // ...
/// }
/// # std::io::Result::Ok(())
/// ```
#[inline]
pub fn as_apc_event(&self) -> Option<&str> {
match self {
Event::ApplicationProgramCommand(command) => Some(command),
_ => None,
}
}
}

/// Represents a mouse event.
Expand Down Expand Up @@ -1713,35 +1740,66 @@ mod tests {
assert_eq!(event.as_key_press_event(), Some(ESC_PRESSED));
assert_eq!(event.as_key_release_event(), None);
assert_eq!(event.as_key_repeat_event(), None);
assert_eq!(event.as_mouse_event(), None);
assert_eq!(event.as_resize_event(), None);
assert_eq!(event.as_apc_event(), None);

let event = Event::Key(ESC_RELEASED);
assert_eq!(event.as_key_event(), Some(ESC_RELEASED));
assert_eq!(event.as_key_release_event(), Some(ESC_RELEASED));
assert_eq!(event.as_key_press_event(), None);
assert_eq!(event.as_key_repeat_event(), None);
assert_eq!(event.as_mouse_event(), None);
assert_eq!(event.as_resize_event(), None);
assert_eq!(event.as_apc_event(), None);

let event = Event::Key(ESC_REPEAT);
assert_eq!(event.as_key_event(), Some(ESC_REPEAT));
assert_eq!(event.as_key_repeat_event(), Some(ESC_REPEAT));
assert_eq!(event.as_key_press_event(), None);
assert_eq!(event.as_key_release_event(), None);
assert_eq!(event.as_mouse_event(), None);
assert_eq!(event.as_resize_event(), None);
assert_eq!(event.as_apc_event(), None);

let event = Event::Resize(1, 1);
assert_eq!(event.as_resize_event(), Some((1, 1)));
assert_eq!(event.as_key_event(), None);
assert_eq!(event.as_key_release_event(), None);
assert_eq!(event.as_key_press_event(), None);
assert_eq!(event.as_key_repeat_event(), None);
assert_eq!(event.as_mouse_event(), None);
assert_eq!(event.as_apc_event(), None);

let event = Event::Mouse(MOUSE_CLICK);
assert_eq!(event.as_mouse_event(), Some(MOUSE_CLICK));
assert_eq!(event.as_key_event(), None);
assert_eq!(event.as_key_release_event(), None);
assert_eq!(event.as_key_press_event(), None);
assert_eq!(event.as_key_repeat_event(), None);
assert_eq!(event.as_resize_event(), None);
assert_eq!(event.as_apc_event(), None);

#[cfg(feature = "bracketed-paste")]
{
let event = Event::Paste("".to_string());
assert_eq!(event.as_paste_event(), Some(""));
assert_eq!(event.as_key_event(), None);
assert_eq!(event.as_key_release_event(), None);
assert_eq!(event.as_key_press_event(), None);
assert_eq!(event.as_key_repeat_event(), None);
assert_eq!(event.as_resize_event(), None);
assert_eq!(event.as_mouse_event(), None);
assert_eq!(event.as_apc_event(), None);
}

let event = Event::ApplicationProgramCommand("".to_string());
assert_eq!(event.as_apc_event(), Some(""));
assert_eq!(event.as_key_event(), None);
assert_eq!(event.as_key_release_event(), None);
assert_eq!(event.as_key_press_event(), None);
assert_eq!(event.as_key_repeat_event(), None);
assert_eq!(event.as_resize_event(), None);
assert_eq!(event.as_mouse_event(), None);
}
}
51 changes: 50 additions & 1 deletion src/event/sys/unix/parse.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::io;
use std::{io, str};

use crate::event::{
Event, KeyCode, KeyEvent, KeyEventKind, KeyEventState, KeyModifiers, KeyboardEnhancementFlags,
Expand Down Expand Up @@ -75,6 +75,7 @@ pub(crate) fn parse_event(
}
b'[' => parse_csi(buffer),
b'\x1B' => Ok(Some(InternalEvent::Event(Event::Key(KeyCode::Esc.into())))),
b'_' => parse_apc(buffer),
_ => parse_event(&buffer[1..], input_available).map(|event_option| {
event_option.map(|event| {
if let InternalEvent::Event(Event::Key(key_event)) = event {
Expand Down Expand Up @@ -134,6 +135,20 @@ fn char_code_to_event(code: KeyCode) -> KeyEvent {
KeyEvent::new(code, modifiers)
}

pub(crate) fn parse_apc(buffer: &[u8]) -> io::Result<Option<InternalEvent>> {
assert!(buffer.starts_with(b"\x1B_")); // ESC _
if !buffer.ends_with(b"\x1B\x5C") || buffer.len() == 2 {
return Ok(None);
}

// APC strings should contain only ASCII characters
let apc_buf = &buffer[2..buffer.len() - 2];
let input_str = str::from_utf8(apc_buf).map_err(|_| could_not_parse_event_error())?;
let input_event = Event::ApplicationProgramCommand(input_str.to_string());

Ok(Some(InternalEvent::Event(input_event)))
}

pub(crate) fn parse_csi(buffer: &[u8]) -> io::Result<Option<InternalEvent>> {
assert!(buffer.starts_with(b"\x1B[")); // ESC [

Expand Down Expand Up @@ -995,6 +1010,14 @@ mod tests {
KeyModifiers::SHIFT
)))),
);

// parse_apc
assert_eq!(
parse_event("\x1B_APC; Test\x1B\\".as_bytes(), false).unwrap(),
Some(InternalEvent::Event(Event::ApplicationProgramCommand(
"APC; Test".to_string()
)))
)
}

#[test]
Expand Down Expand Up @@ -1503,4 +1526,30 @@ mod tests {
)))),
);
}

#[test]
fn test_parse_apc() {
assert_eq!(
parse_event(b"\x1B__APC; Test_\x1B\\", false).unwrap(),
Some(InternalEvent::Event(Event::ApplicationProgramCommand(
"_APC; Test_".to_string()
)))
);

assert_eq!(parse_event(b"\x1B_", false).unwrap(), None);
assert_eq!(parse_event(b"\x1B_\x1B", false).unwrap(), None);
assert_eq!(
parse_event(b"\x1B_\x1B\\", false).unwrap(),
Some(InternalEvent::Event(Event::ApplicationProgramCommand(
"".to_string()
)))
);

assert_ne!(
parse_event(b"\x1BAPC; Test_\x1B\\", false).unwrap(),
Some(InternalEvent::Event(Event::ApplicationProgramCommand(
"APC; Test_".to_string()
)))
);
}
}
2 changes: 1 addition & 1 deletion src/terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ pub fn size() -> io::Result<(u16, u16)> {
sys::size()
}

#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct WindowSize {
pub rows: u16,
pub columns: u16,
Expand Down