Update crossterm to version 0.26.1 (#560)

* update crossterm to 0.26.1

* add event_listener_kitty_proto example

* add comment

* remove trait
This commit is contained in:
WindSoilder 2023-04-14 01:24:17 +08:00 committed by GitHub
parent 89cb811838
commit 27f4417191
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 271 additions and 79 deletions

4
Cargo.lock generated
View File

@ -187,9 +187,9 @@ dependencies = [
[[package]]
name = "crossterm"
version = "0.24.0"
version = "0.26.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab9f7409c70a38a56216480fba371ee460207dd8926ccf5b4160591759559170"
checksum = "a84cda67535339806297f1b331d6dd6320470d2a0fe65381e79ee9e156dd3d13"
dependencies = [
"bitflags",
"crossterm_winapi",

View File

@ -15,7 +15,7 @@ doctest = true
[dependencies]
chrono = "0.4.19"
clipboard = { version = "0.5.0", optional = true }
crossterm = { version = "0.24.0", features = ["serde"] }
crossterm = { version = "0.26.1", features = ["serde"] }
itertools = "0.10.3"
nu-ansi-term = "0.47.0"
serde = { version = "1.0", features = ["derive"] }

View File

@ -12,7 +12,7 @@ use {
},
};
use crossterm::cursor::CursorShape;
use crossterm::cursor::SetCursorStyle;
use reedline::CursorConfig;
#[cfg(not(any(feature = "sqlite", feature = "sqlite-dynlib")))]
use reedline::FileBackedHistory;
@ -61,8 +61,8 @@ fn main() -> Result<()> {
let completer = Box::new(DefaultCompleter::new_with_wordlen(commands.clone(), 2));
let cursor_config = CursorConfig {
vi_insert: Some(CursorShape::Line),
vi_normal: Some(CursorShape::Block),
vi_insert: Some(SetCursorStyle::BlinkingBar),
vi_normal: Some(SetCursorStyle::SteadyBlock),
emacs: None,
};

View File

@ -38,11 +38,17 @@ fn print_events_helper() -> Result<()> {
// It's guaranteed that read() wont block if `poll` returns `Ok(true)`
let event = crossterm::event::read()?;
if let Event::Key(KeyEvent { code, modifiers }) = event {
if let Event::Key(KeyEvent {
code,
modifiers,
kind,
state,
}) = event
{
match code {
KeyCode::Char(c) => {
println!(
"Char: {} code: {:#08x}; Modifier {:?}; Flags {:#08b}\r",
"Char: {} code: {:#08x}; Modifier {:?}; Flags {:#08b}; Kind {kind:?}; state {state:?}\r",
c,
u32::from(c),
modifiers,
@ -51,7 +57,7 @@ fn print_events_helper() -> Result<()> {
}
_ => {
println!(
"Keycode: {code:?}; Modifier {modifiers:?}; Flags {modifiers:#08b}\r"
"Keycode: {code:?}; Modifier {modifiers:?}; Flags {modifiers:#08b}; Kind {kind:?}; state {state:?}\r"
);
}
}

View File

@ -0,0 +1,104 @@
use crossterm::event::{
KeyboardEnhancementFlags, PopKeyboardEnhancementFlags, PushKeyboardEnhancementFlags,
};
use crossterm::execute;
use {
crossterm::{
event::{poll, Event, KeyCode, KeyEvent},
terminal, Result,
},
std::{
io::{stdout, Write},
time::Duration,
},
};
fn main() -> Result<()> {
println!("Ready to print events (Abort with ESC):");
print_events()?;
println!();
Ok(())
}
/// **For debugging purposes only:** Track the terminal events observed by [`Reedline`] and print them.
pub fn print_events() -> Result<()> {
stdout().flush()?;
terminal::enable_raw_mode()?;
// enable kitty protocol
//
// 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)
//
// Refer to https://sw.kovidgoyal.net/kitty/keyboard-protocol/ if you're curious.
execute!(
stdout(),
PushKeyboardEnhancementFlags(
KeyboardEnhancementFlags::REPORT_EVENT_TYPES
| KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
)
)
.unwrap();
let result = print_events_helper();
execute!(stdout(), PopKeyboardEnhancementFlags).unwrap();
terminal::disable_raw_mode()?;
result
}
// this fn is totally ripped off from crossterm's examples
// it's really a diagnostic routine to see if crossterm is
// even seeing the events. if you press a key and no events
// are printed, it's a good chance your terminal is eating
// those events.
fn print_events_helper() -> Result<()> {
loop {
// Wait up to 5s for another event
if poll(Duration::from_millis(5_000))? {
// It's guaranteed that read() wont block if `poll` returns `Ok(true)`
let event = crossterm::event::read()?;
if let Event::Key(KeyEvent {
code,
modifiers,
kind,
state,
}) = event
{
match code {
KeyCode::Char(c) => {
println!(
"Char: {} code: {:#08x}; Modifier {:?}; Flags {:#08b}; Kind {kind:?}; state {state:?}\r",
c,
u32::from(c),
modifiers,
modifiers
);
}
_ => {
println!(
"Keycode: {code:?}; Modifier {modifiers:?}; Flags {modifiers:#08b}; Kind {kind:?}; state {state:?}\r"
);
}
}
} else {
println!("Event::{event:?}\r");
}
// hit the esc key to git out
if event == Event::Key(KeyCode::Esc.into()) {
break;
}
} else {
// Timeout expired, no event for 5s
println!("Waiting for you to type...\r");
}
}
Ok(())
}

View File

@ -1,5 +1,7 @@
use crate::{enums::ReedlineEvent, PromptEditMode};
pub use crossterm::event::Event;
use crate::{
enums::{ReedlineEvent, ReedlineRawEvent},
PromptEditMode,
};
/// Define the style of parsing for the edit events
/// Available default options:
@ -7,7 +9,7 @@ pub use crossterm::event::Event;
/// - Vi
pub trait EditMode: Send {
/// Translate the given user input event into what the `LineEditor` understands
fn parse_event(&mut self, event: Event) -> ReedlineEvent;
fn parse_event(&mut self, event: ReedlineRawEvent) -> ReedlineEvent;
/// What to display in the prompt indicator
fn edit_mode(&self) -> PromptEditMode;

View File

@ -1,13 +1,13 @@
use crossterm::cursor::CursorShape;
use crossterm::cursor::SetCursorStyle;
/// Maps cursor shapes to each edit mode (emacs, vi normal & vi insert).
/// If any of the fields is `None`, the cursor won't get changed by Reedline for that mode.
#[derive(Default)]
pub struct CursorConfig {
/// The cursor to be used when in vi insert mode
pub vi_insert: Option<CursorShape>,
pub vi_insert: Option<SetCursorStyle>,
/// The cursor to be used when in vi normal mode
pub vi_normal: Option<CursorShape>,
pub vi_normal: Option<SetCursorStyle>,
/// The cursor to be used when in emacs mode
pub emacs: Option<CursorShape>,
pub emacs: Option<SetCursorStyle>,
}

View File

@ -6,7 +6,7 @@ use crate::{
},
EditMode,
},
enums::{EditCommand, ReedlineEvent},
enums::{EditCommand, ReedlineEvent, ReedlineRawEvent},
PromptEditMode,
};
use crossterm::event::{Event, KeyCode, KeyEvent, KeyModifiers};
@ -108,9 +108,11 @@ impl Default for Emacs {
}
impl EditMode for Emacs {
fn parse_event(&mut self, event: Event) -> ReedlineEvent {
match event {
Event::Key(KeyEvent { code, modifiers }) => match (modifiers, code) {
fn parse_event(&mut self, event: ReedlineRawEvent) -> ReedlineEvent {
match event.into() {
Event::Key(KeyEvent {
code, modifiers, ..
}) => match (modifiers, code) {
(modifier, KeyCode::Char(c)) => {
// Note. The modifier can also be a combination of modifiers, for
// example:
@ -152,6 +154,9 @@ impl EditMode for Emacs {
Event::Mouse(_) => ReedlineEvent::Mouse,
Event::Resize(width, height) => ReedlineEvent::Resize(width, height),
Event::FocusGained => ReedlineEvent::None,
Event::FocusLost => ReedlineEvent::None,
Event::Paste(_) => ReedlineEvent::None,
}
}
@ -175,10 +180,11 @@ mod test {
#[test]
fn ctrl_l_leads_to_clear_screen_event() {
let mut emacs = Emacs::default();
let ctrl_l = Event::Key(KeyEvent {
modifiers: KeyModifiers::CONTROL,
code: KeyCode::Char('l'),
});
let ctrl_l = ReedlineRawEvent::convert_from(Event::Key(KeyEvent::new(
KeyCode::Char('l'),
KeyModifiers::CONTROL,
)))
.unwrap();
let result = emacs.parse_event(ctrl_l);
assert_eq!(result, ReedlineEvent::ClearScreen);
@ -194,10 +200,11 @@ mod test {
);
let mut emacs = Emacs::new(keybindings);
let ctrl_l = Event::Key(KeyEvent {
modifiers: KeyModifiers::CONTROL,
code: KeyCode::Char('l'),
});
let ctrl_l = ReedlineRawEvent::convert_from(Event::Key(KeyEvent::new(
KeyCode::Char('l'),
KeyModifiers::CONTROL,
)))
.unwrap();
let result = emacs.parse_event(ctrl_l);
assert_eq!(result, ReedlineEvent::HistoryHintComplete);
@ -206,10 +213,11 @@ mod test {
#[test]
fn inserting_character_works() {
let mut emacs = Emacs::default();
let l = Event::Key(KeyEvent {
modifiers: KeyModifiers::NONE,
code: KeyCode::Char('l'),
});
let l = ReedlineRawEvent::convert_from(Event::Key(KeyEvent::new(
KeyCode::Char('l'),
KeyModifiers::NONE,
)))
.unwrap();
let result = emacs.parse_event(l);
assert_eq!(
@ -222,10 +230,11 @@ mod test {
fn inserting_capital_character_works() {
let mut emacs = Emacs::default();
let uppercase_l = Event::Key(KeyEvent {
modifiers: KeyModifiers::SHIFT,
code: KeyCode::Char('l'),
});
let uppercase_l = ReedlineRawEvent::convert_from(Event::Key(KeyEvent::new(
KeyCode::Char('l'),
KeyModifiers::SHIFT,
)))
.unwrap();
let result = emacs.parse_event(uppercase_l);
assert_eq!(
@ -239,10 +248,11 @@ mod test {
let keybindings = Keybindings::default();
let mut emacs = Emacs::new(keybindings);
let ctrl_l = Event::Key(KeyEvent {
modifiers: KeyModifiers::CONTROL,
code: KeyCode::Char('l'),
});
let ctrl_l = ReedlineRawEvent::convert_from(Event::Key(KeyEvent::new(
KeyCode::Char('l'),
KeyModifiers::CONTROL,
)))
.unwrap();
let result = emacs.parse_event(ctrl_l);
assert_eq!(result, ReedlineEvent::None);
@ -252,10 +262,11 @@ mod test {
fn inserting_capital_character_for_non_ascii_remains_as_is() {
let mut emacs = Emacs::default();
let uppercase_l = Event::Key(KeyEvent {
modifiers: KeyModifiers::SHIFT,
code: KeyCode::Char('😀'),
});
let uppercase_l = ReedlineRawEvent::convert_from(Event::Key(KeyEvent::new(
KeyCode::Char('😀'),
KeyModifiers::SHIFT,
)))
.unwrap();
let result = emacs.parse_event(uppercase_l);
assert_eq!(

View File

@ -4,7 +4,7 @@ mod emacs;
mod keybindings;
mod vi;
pub use base::{EditMode, Event};
pub use base::EditMode;
pub use cursors::CursorConfig;
pub use emacs::{default_emacs_keybindings, Emacs};
pub use keybindings::Keybindings;

View File

@ -11,7 +11,7 @@ use self::motion::ViCharSearch;
use super::EditMode;
use crate::{
edit_mode::{keybindings::Keybindings, vi::parser::parse},
enums::{EditCommand, ReedlineEvent},
enums::{EditCommand, ReedlineEvent, ReedlineRawEvent},
PromptEditMode, PromptViMode,
};
@ -57,9 +57,11 @@ impl Vi {
}
impl EditMode for Vi {
fn parse_event(&mut self, event: Event) -> ReedlineEvent {
match event {
Event::Key(KeyEvent { code, modifiers }) => match (self.mode, modifiers, code) {
fn parse_event(&mut self, event: ReedlineRawEvent) -> ReedlineEvent {
match event.into() {
Event::Key(KeyEvent {
code, modifiers, ..
}) => match (self.mode, modifiers, code) {
(ViMode::Normal, modifier, KeyCode::Char(c)) => {
let c = c.to_ascii_lowercase();
@ -149,6 +151,9 @@ impl EditMode for Vi {
Event::Mouse(_) => ReedlineEvent::Mouse,
Event::Resize(width, height) => ReedlineEvent::Resize(width, height),
Event::FocusGained => ReedlineEvent::None,
Event::FocusLost => ReedlineEvent::None,
Event::Paste(_) => ReedlineEvent::None,
}
}
@ -168,10 +173,11 @@ mod test {
#[test]
fn esc_leads_to_normal_mode_test() {
let mut vi = Vi::default();
let esc = Event::Key(KeyEvent {
modifiers: KeyModifiers::NONE,
code: KeyCode::Esc,
});
let esc = ReedlineRawEvent::convert_from(Event::Key(KeyEvent::new(
KeyCode::Esc,
KeyModifiers::NONE,
)))
.unwrap();
let result = vi.parse_event(esc);
assert_eq!(
@ -197,10 +203,11 @@ mod test {
..Default::default()
};
let esc = Event::Key(KeyEvent {
modifiers: KeyModifiers::NONE,
code: KeyCode::Char('e'),
});
let esc = ReedlineRawEvent::convert_from(Event::Key(KeyEvent::new(
KeyCode::Char('e'),
KeyModifiers::NONE,
)))
.unwrap();
let result = vi.parse_event(esc);
assert_eq!(result, ReedlineEvent::ClearScreen);
@ -222,10 +229,11 @@ mod test {
..Default::default()
};
let esc = Event::Key(KeyEvent {
modifiers: KeyModifiers::SHIFT,
code: KeyCode::Char('$'),
});
let esc = ReedlineRawEvent::convert_from(Event::Key(KeyEvent::new(
KeyCode::Char('$'),
KeyModifiers::SHIFT,
)))
.unwrap();
let result = vi.parse_event(esc);
assert_eq!(result, ReedlineEvent::CtrlD);
@ -241,10 +249,11 @@ mod test {
..Default::default()
};
let esc = Event::Key(KeyEvent {
modifiers: KeyModifiers::NONE,
code: KeyCode::Char('q'),
});
let esc = ReedlineRawEvent::convert_from(Event::Key(KeyEvent::new(
KeyCode::Char('q'),
KeyModifiers::NONE,
)))
.unwrap();
let result = vi.parse_event(esc);
assert_eq!(result, ReedlineEvent::None);

View File

@ -1,4 +1,4 @@
use crate::CursorConfig;
use crate::{enums::ReedlineRawEvent, CursorConfig};
#[cfg(feature = "bashisms")]
use crate::{
history::SearchFilter,
@ -493,7 +493,7 @@ impl Reedline {
self.repaint(prompt)?;
let mut crossterm_events: Vec<Event> = vec![];
let mut crossterm_events: Vec<ReedlineRawEvent> = vec![];
let mut reedline_events: Vec<ReedlineEvent> = vec![];
loop {
@ -528,18 +528,29 @@ impl Reedline {
enter @ Event::Key(KeyEvent {
code: KeyCode::Enter,
modifiers: KeyModifiers::NONE,
..
}) => {
crossterm_events.push(enter);
// Break early to check if the input is complete and
// can be send to the hosting application. If
// multiple complete entries are submitted, events
// are still in the crossterm queue for us to
// process.
paste_enter_state = crossterm_events.len() > EVENTS_THRESHOLD;
break;
let enter = ReedlineRawEvent::convert_from(enter);
match enter {
Some(enter) => {
crossterm_events.push(enter);
// Break early to check if the input is complete and
// can be send to the hosting application. If
// multiple complete entries are submitted, events
// are still in the crossterm queue for us to
// process.
paste_enter_state = crossterm_events.len() > EVENTS_THRESHOLD;
break;
}
None => continue,
}
}
x => {
crossterm_events.push(x);
let raw_event = ReedlineRawEvent::convert_from(x);
match raw_event {
Some(evt) => crossterm_events.push(evt),
None => continue,
}
}
}
}

View File

@ -1,3 +1,4 @@
use crossterm::event::{Event, KeyEvent, KeyEventKind};
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use strum_macros::EnumIter;
@ -560,3 +561,42 @@ pub(crate) enum EventStatus {
Inapplicable,
Exits(Signal),
}
/// A simple wrapper for [crossterm::event::Event]
///
/// Which will make sure that the given event doesn't contain [KeyEventKind::Release]
/// and convert from [KeyEventKind::Repeat] to [KeyEventKind::Press]
pub struct ReedlineRawEvent {
inner: Event,
}
impl ReedlineRawEvent {
/// It will return None if `evt` is released Key.
pub fn convert_from(evt: Event) -> Option<Self> {
match evt {
Event::Key(KeyEvent {
kind: KeyEventKind::Release,
..
}) => None,
Event::Key(KeyEvent {
code,
modifiers,
kind: KeyEventKind::Repeat,
state,
}) => Some(Self {
inner: Event::Key(KeyEvent {
code,
modifiers,
kind: KeyEventKind::Press,
state,
}),
}),
other => Some(Self { inner: other }),
}
}
/// Consume and get crossterm event object.
pub fn into(self) -> Event {
self.inner
}
}

View File

@ -230,7 +230,7 @@ pub use core_editor::Editor;
pub use core_editor::LineBuffer;
mod enums;
pub use enums::{EditCommand, ReedlineEvent, Signal, UndoBehavior};
pub use enums::{EditCommand, ReedlineEvent, ReedlineRawEvent, Signal, UndoBehavior};
mod painting;
pub use painting::{Painter, StyledText};
@ -259,7 +259,7 @@ pub use prompt::{
mod edit_mode;
pub use edit_mode::{
default_emacs_keybindings, default_vi_insert_keybindings, default_vi_normal_keybindings,
CursorConfig, EditMode, Emacs, Event, Keybindings, Vi,
CursorConfig, EditMode, Emacs, Keybindings, Vi,
};
mod highlighter;

View File

@ -186,7 +186,7 @@ impl Painter {
_ => None,
};
if let Some(shape) = shape {
self.stdout.queue(cursor::SetCursorShape(shape))?;
self.stdout.queue(shape)?;
}
}
self.stdout.queue(cursor::Show)?;

View File

@ -57,6 +57,15 @@ impl Display for ReedLineCrossTermKeyCode {
KeyCode::Char(_) => write!(f, "Char_<letter>"),
KeyCode::Null => write!(f, "Null"),
KeyCode::Esc => write!(f, "Esc"),
KeyCode::CapsLock => write!(f, "CapsLock"),
KeyCode::ScrollLock => write!(f, "ScrollLock"),
KeyCode::NumLock => write!(f, "NumLock"),
KeyCode::PrintScreen => write!(f, "PrintScreen"),
KeyCode::Pause => write!(f, "Pause"),
KeyCode::Menu => write!(f, "Menu"),
KeyCode::KeypadBegin => write!(f, "KeypadBegin"),
KeyCode::Media(_) => write!(f, "Media<media>"),
KeyCode::Modifier(_) => write!(f, "Modifier<modifier>"),
},
}
}