diff --git a/.github/workflows/crossterm_test.yml b/.github/workflows/crossterm_test.yml
index 3ce9f1f..8b2509f 100644
--- a/.github/workflows/crossterm_test.yml
+++ b/.github/workflows/crossterm_test.yml
@@ -62,6 +62,9 @@ jobs:
- name: Test all features
run: cargo test --all-features -- --nocapture --test-threads 1
continue-on-error: ${{ matrix.can-fail }}
+ - name: Test no default features
+ run: cargo test --no-default-features -- --nocapture --test-threads 1
+ continue-on-error: ${{ matrix.can-fail }}
- name: Test Packaging
if: matrix.rust == 'stable'
run: cargo package
diff --git a/Cargo.toml b/Cargo.toml
index 0893b03..197b22a 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -26,7 +26,8 @@ all-features = true
# Features
#
[features]
-default = []
+default = ["bracketed-paste"]
+bracketed-paste = []
event-stream = ["futures-core"]
#
@@ -72,6 +73,10 @@ serde_json = "1.0"
#
# Examples
#
+[[example]]
+name = "event-read"
+required-features = ["bracketed-paste"]
+
[[example]]
name = "event-stream-async-std"
required-features = ["event-stream"]
diff --git a/examples/event-match-modifiers.rs b/examples/event-match-modifiers.rs
index 183109d..c3f75e9 100644
--- a/examples/event-match-modifiers.rs
+++ b/examples/event-match-modifiers.rs
@@ -2,7 +2,7 @@
//!
//! cargo run --example event-match-modifiers
-use crossterm::event::{Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
+use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
fn match_event(read_event: Event) {
match read_event {
diff --git a/examples/event-read.rs b/examples/event-read.rs
index 549e05a..74a9b6b 100644
--- a/examples/event-read.rs
+++ b/examples/event-read.rs
@@ -10,8 +10,8 @@ use crossterm::event::{
use crossterm::{
cursor::position,
event::{
- read, DisableFocusChange, DisableMouseCapture, EnableFocusChange, EnableMouseCapture,
- Event, KeyCode,
+ read, DisableBracketedPaste, DisableFocusChange, DisableMouseCapture, EnableBracketedPaste,
+ EnableFocusChange, EnableMouseCapture, Event, KeyCode,
},
execute,
terminal::{disable_raw_mode, enable_raw_mode},
@@ -36,9 +36,9 @@ fn print_events() -> Result<()> {
println!("Cursor position: {:?}\r", position());
}
- if let Event::Resize(_, _) = event {
- let (original_size, new_size) = flush_resize_events(event);
- println!("Resize from: {:?}, to: {:?}", original_size, new_size);
+ if let Event::Resize(x, y) = event {
+ let (original_size, new_size) = flush_resize_events((x, y));
+ println!("Resize from: {:?}, to: {:?}\r", original_size, new_size);
}
if event == Event::Key(KeyCode::Esc.into()) {
@@ -52,18 +52,15 @@ fn print_events() -> Result<()> {
// Resize events can occur in batches.
// With a simple loop they can be flushed.
// This function will keep the first and last resize event.
-fn flush_resize_events(event: Event) -> ((u16, u16), (u16, u16)) {
- if let Event::Resize(x, y) = event {
- let mut last_resize = (x, y);
- while let Ok(true) = poll(Duration::from_millis(50)) {
- if let Ok(Event::Resize(x, y)) = read() {
- last_resize = (x, y);
- }
+fn flush_resize_events(first_resize: (u16, u16)) -> ((u16, u16), (u16, u16)) {
+ let mut last_resize = first_resize;
+ while let Ok(true) = poll(Duration::from_millis(50)) {
+ if let Ok(Event::Resize(x, y)) = read() {
+ last_resize = (x, y);
}
-
- return ((x, y), last_resize);
}
- ((0, 0), (0, 0))
+
+ return (first_resize, last_resize);
}
fn main() -> Result<()> {
@@ -74,8 +71,9 @@ fn main() -> Result<()> {
let mut stdout = stdout();
execute!(
stdout,
+ EnableBracketedPaste,
EnableFocusChange,
- EnableMouseCapture,
+ EnableMouseCapture
PushKeyboardEnhancementFlags(
KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
| KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES
@@ -89,6 +87,7 @@ fn main() -> Result<()> {
execute!(
stdout,
+ DisableBracketedPaste,
PopKeyboardEnhancementFlags,
DisableFocusChange,
DisableMouseCapture
diff --git a/src/event.rs b/src/event.rs
index 42411b5..ab89575 100644
--- a/src/event.rs
+++ b/src/event.rs
@@ -38,6 +38,8 @@
//! Event::FocusLost => println!("FocusLost"),
//! Event::Key(event) => println!("{:?}", event),
//! Event::Mouse(event) => println!("{:?}", event),
+//! #[cfg(feature = "bracketed-paste")]
+//! Event::Paste(data) => println!("{:?}", data),
//! Event::Resize(width, height) => println!("New size {}x{}", width, height),
//! }
//! }
@@ -63,6 +65,8 @@
//! Event::FocusLost => println!("FocusLost"),
//! Event::Key(event) => println!("{:?}", event),
//! Event::Mouse(event) => println!("{:?}", event),
+//! #[cfg(feature = "bracketed-paste")]
+//! Event::Paste(data) => println!("Pasted {:?}", data),
//! Event::Resize(width, height) => println!("New size {}x{}", width, height),
//! }
//! } else {
@@ -416,6 +420,8 @@ impl Command for PopKeyboardEnhancementFlags {
/// A command that enables focus event emission.
///
+/// It should be paired with [`DisableFocusChange`] at the end of execution.
+///
/// Focus events can be captured with [read](./fn.read.html)/[poll](./fn.poll.html).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct EnableFocusChange;
@@ -433,8 +439,6 @@ impl Command for EnableFocusChange {
}
/// A command that disables focus event emission.
-///
-/// Focus events can be captured with [read](./fn.read.html)/[poll](./fn.poll.html).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DisableFocusChange;
@@ -450,9 +454,52 @@ impl Command for DisableFocusChange {
}
}
+/// A command that enables [bracketed paste mode](https://en.wikipedia.org/wiki/Bracketed-paste).
+///
+/// It should be paired with [`DisableBracketedPaste`] at the end of execution.
+///
+/// This is not supported in older Windows terminals without
+/// [virtual terminal sequences](https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences).
+#[cfg(feature = "bracketed-paste")]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct EnableBracketedPaste;
+
+#[cfg(feature = "bracketed-paste")]
+impl Command for EnableBracketedPaste {
+ fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
+ f.write_str(csi!("?2004h"))
+ }
+
+ #[cfg(windows)]
+ fn execute_winapi(&self) -> Result<()> {
+ Err(io::Error::new(
+ io::ErrorKind::Unsupported,
+ "Bracketed paste not implemented in the legacy Windows API.",
+ ))
+ }
+}
+
+/// A command that disables bracketed paste mode.
+#[cfg(feature = "bracketed-paste")]
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub struct DisableBracketedPaste;
+
+#[cfg(feature = "bracketed-paste")]
+impl Command for DisableBracketedPaste {
+ fn write_ansi(&self, f: &mut impl fmt::Write) -> fmt::Result {
+ f.write_str(csi!("?2004l"))
+ }
+
+ #[cfg(windows)]
+ fn execute_winapi(&self) -> Result<()> {
+ Ok(())
+ }
+}
+
/// Represents an event.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
-#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Copy, Hash)]
+#[cfg_attr(not(feature = "bracketed-paste"), derive(Copy))]
+#[derive(Debug, PartialOrd, PartialEq, Eq, Clone, Hash)]
pub enum Event {
/// The terminal gained focus
FocusGained,
@@ -462,6 +509,10 @@ pub enum Event {
Key(KeyEvent),
/// A single mouse event with additional pressed modifiers.
Mouse(MouseEvent),
+ /// A string that was pasted into the terminal. Only emitted if bracketed paste has been
+ /// enabled.
+ #[cfg(feature = "bracketed-paste")]
+ Paste(String),
/// An resize event with new dimensions after resize (columns, rows).
/// **Note** that resize events can be occur in batches.
Resize(u16, u16),
diff --git a/src/event/sys/unix/parse.rs b/src/event/sys/unix/parse.rs
index d96a2f3..e5b2459 100644
--- a/src/event/sys/unix/parse.rs
+++ b/src/event/sys/unix/parse.rs
@@ -177,11 +177,15 @@ pub(crate) fn parse_csi(buffer: &[u8]) -> Result