Include pastes in input events

This commit is contained in:
Joscha 2022-08-10 23:19:18 +02:00
parent 7733b1a2c8
commit 5ad9f0f3e7
9 changed files with 93 additions and 83 deletions

2
Cargo.lock generated
View file

@ -1248,7 +1248,7 @@ dependencies = [
[[package]] [[package]]
name = "toss" name = "toss"
version = "0.1.0" version = "0.1.0"
source = "git+https://github.com/Garmelon/toss.git?rev=fbe9e065fcc76f445c8e4feee04dcbf230586a4c#fbe9e065fcc76f445c8e4feee04dcbf230586a4c" source = "git+https://github.com/Garmelon/toss.git?rev=7e429132458514e8dc99ab6be789b9c8225ed00e#7e429132458514e8dc99ab6be789b9c8225ed00e"
dependencies = [ dependencies = [
"crossterm", "crossterm",
"unicode-linebreak", "unicode-linebreak",

View file

@ -34,7 +34,7 @@ features = ["rustls-tls-native-roots"]
[dependencies.toss] [dependencies.toss]
git = "https://github.com/Garmelon/toss.git" git = "https://github.com/Garmelon/toss.git"
rev = "fbe9e065fcc76f445c8e4feee04dcbf230586a4c" rev = "7e429132458514e8dc99ab6be789b9c8225ed00e"
# [patch."https://github.com/Garmelon/toss.git"] # [patch."https://github.com/Garmelon/toss.git"]
# toss = { path = "../toss/" } # toss = { path = "../toss/" }

View file

@ -9,7 +9,7 @@ use std::convert::Infallible;
use std::sync::{Arc, Weak}; use std::sync::{Arc, Weak};
use std::time::{Duration, Instant}; use std::time::{Duration, Instant};
use crossterm::event::{Event, KeyCode, MouseEvent}; use crossterm::event::{Event, KeyCode};
use parking_lot::FairMutex; use parking_lot::FairMutex;
use tokio::sync::mpsc::error::TryRecvError; use tokio::sync::mpsc::error::TryRecvError;
use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender}; use tokio::sync::mpsc::{self, UnboundedReceiver, UnboundedSender};
@ -21,7 +21,7 @@ use crate::vault::Vault;
pub use self::chat::ChatMsg; pub use self::chat::ChatMsg;
use self::chat::ChatState; use self::chat::ChatState;
use self::input::{key, KeyBindingsList, KeyEvent}; use self::input::{key, InputEvent, KeyBindingsList, KeyEvent};
use self::rooms::Rooms; use self::rooms::Rooms;
use self::widgets::layer::Layer; use self::widgets::layer::Layer;
use self::widgets::list::ListState; use self::widgets::list::ListState;
@ -158,12 +158,13 @@ impl Ui {
let result = match event { let result = match event {
UiEvent::Redraw => EventHandleResult::Continue, UiEvent::Redraw => EventHandleResult::Continue,
UiEvent::Term(Event::Key(event)) => { UiEvent::Term(event) => {
self.handle_key_event(event.into(), terminal, &crossterm_lock) if let Some(event) = InputEvent::from_event(event) {
.await self.handle_event(terminal, &crossterm_lock, &event).await
} else {
EventHandleResult::Continue
}
} }
UiEvent::Term(Event::Mouse(event)) => self.handle_mouse_event(event).await?,
UiEvent::Term(_) => EventHandleResult::Continue,
}; };
match result { match result {
EventHandleResult::Continue => {} EventHandleResult::Continue => {}
@ -219,11 +220,11 @@ impl Ui {
} }
} }
async fn handle_key_event( async fn handle_event(
&mut self, &mut self,
event: KeyEvent,
terminal: &mut Terminal, terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>, crossterm_lock: &Arc<FairMutex<()>>,
event: &InputEvent,
) -> EventHandleResult { ) -> EventHandleResult {
if let key!(Ctrl + 'c') = event { if let key!(Ctrl + 'c') = event {
// Exit unconditionally on ctrl+c. Previously, shift+q would also // Exit unconditionally on ctrl+c. Previously, shift+q would also
@ -261,12 +262,12 @@ impl Ui {
let handled = match self.mode { let handled = match self.mode {
Mode::Main => { Mode::Main => {
self.rooms self.rooms
.handle_key_event(terminal, crossterm_lock, event) .handle_event(terminal, crossterm_lock, event)
.await .await
} }
Mode::Log => self Mode::Log => self
.log_chat .log_chat
.handle_key_event(terminal, crossterm_lock, event, false) .handle_event(terminal, crossterm_lock, event, false)
.await .await
.handled(), .handled(),
}; };
@ -282,11 +283,4 @@ impl Ui {
EventHandleResult::Continue EventHandleResult::Continue
} }
async fn handle_mouse_event(
&mut self,
_event: MouseEvent,
) -> anyhow::Result<EventHandleResult> {
Ok(EventHandleResult::Continue)
}
} }

View file

@ -14,7 +14,7 @@ use crate::store::{Msg, MsgStore};
use self::tree::{TreeView, TreeViewState}; use self::tree::{TreeView, TreeViewState};
use super::input::{KeyBindingsList, KeyEvent}; use super::input::{InputEvent, KeyBindingsList};
use super::widgets::Widget; use super::widgets::Widget;
/////////// ///////////
@ -90,17 +90,17 @@ impl<M: Msg, S: MsgStore<M>> ChatState<M, S> {
} }
} }
pub async fn handle_key_event( pub async fn handle_event(
&mut self, &mut self,
terminal: &mut Terminal, terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>, crossterm_lock: &Arc<FairMutex<()>>,
event: KeyEvent, event: &InputEvent,
can_compose: bool, can_compose: bool,
) -> Reaction<M> { ) -> Reaction<M> {
match self.mode { match self.mode {
Mode::Tree => { Mode::Tree => {
self.tree self.tree
.handle_key_event(terminal, crossterm_lock, event, can_compose) .handle_event(terminal, crossterm_lock, event, can_compose)
.await .await
} }
} }

View file

@ -14,7 +14,7 @@ use toss::frame::{Frame, Pos, Size};
use toss::terminal::Terminal; use toss::terminal::Terminal;
use crate::store::{Msg, MsgStore}; use crate::store::{Msg, MsgStore};
use crate::ui::input::{key, KeyBindingsList, KeyEvent}; use crate::ui::input::{key, InputEvent, KeyBindingsList, KeyEvent};
use crate::ui::util; use crate::ui::util;
use crate::ui::widgets::editor::EditorState; use crate::ui::widgets::editor::EditorState;
use crate::ui::widgets::Widget; use crate::ui::widgets::Widget;
@ -79,7 +79,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
bindings.binding("z", "center cursor on screen"); bindings.binding("z", "center cursor on screen");
} }
async fn handle_movement_key_event(&mut self, frame: &mut Frame, event: KeyEvent) -> bool { async fn handle_movement_event(&mut self, frame: &mut Frame, event: &InputEvent) -> bool {
let chat_height = frame.size().height - 3; let chat_height = frame.size().height - 3;
match event { match event {
@ -115,7 +115,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
bindings.binding("ctrl+s", "mark all older messages as seen"); bindings.binding("ctrl+s", "mark all older messages as seen");
} }
async fn handle_action_key_event(&mut self, event: KeyEvent, id: Option<&M::Id>) -> bool { async fn handle_action_event(&mut self, event: &InputEvent, id: Option<&M::Id>) -> bool {
match event { match event {
key!(' ') => { key!(' ') => {
if let Some(id) = id { if let Some(id) = id {
@ -162,9 +162,9 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
bindings.binding("t", "start a new thread"); bindings.binding("t", "start a new thread");
} }
async fn handle_edit_initiating_key_event( async fn handle_edit_initiating_event(
&mut self, &mut self,
event: KeyEvent, event: &InputEvent,
id: Option<M::Id>, id: Option<M::Id>,
) -> bool { ) -> bool {
match event { match event {
@ -198,20 +198,20 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
} }
} }
async fn handle_normal_key_event( async fn handle_normal_event(
&mut self, &mut self,
frame: &mut Frame, frame: &mut Frame,
event: KeyEvent, event: &InputEvent,
can_compose: bool, can_compose: bool,
id: Option<M::Id>, id: Option<M::Id>,
) -> bool { ) -> bool {
#[allow(clippy::if_same_then_else)] #[allow(clippy::if_same_then_else)]
if self.handle_movement_key_event(frame, event).await { if self.handle_movement_event(frame, event).await {
true true
} else if self.handle_action_key_event(event, id.as_ref()).await { } else if self.handle_action_event(event, id.as_ref()).await {
true true
} else if can_compose { } else if can_compose {
self.handle_edit_initiating_key_event(event, id).await self.handle_edit_initiating_event(event, id).await
} else { } else {
false false
} }
@ -223,11 +223,11 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
util::list_editor_key_bindings(bindings, |_| true, true); util::list_editor_key_bindings(bindings, |_| true, true);
} }
fn handle_editor_key_event( fn handle_editor_event(
&mut self, &mut self,
terminal: &mut Terminal, terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>, crossterm_lock: &Arc<FairMutex<()>>,
event: KeyEvent, event: &InputEvent,
coming_from: Option<M::Id>, coming_from: Option<M::Id>,
parent: Option<M::Id>, parent: Option<M::Id>,
) -> Reaction<M> { ) -> Reaction<M> {
@ -250,7 +250,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
} }
_ => { _ => {
let handled = util::handle_editor_key_event( let handled = util::handle_editor_event(
&self.editor, &self.editor,
terminal, terminal,
crossterm_lock, crossterm_lock,
@ -281,17 +281,17 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
} }
} }
async fn handle_key_event( async fn handle_event(
&mut self, &mut self,
terminal: &mut Terminal, terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>, crossterm_lock: &Arc<FairMutex<()>>,
event: KeyEvent, event: &InputEvent,
can_compose: bool, can_compose: bool,
) -> Reaction<M> { ) -> Reaction<M> {
match &self.cursor { match &self.cursor {
Cursor::Bottom => { Cursor::Bottom => {
if self if self
.handle_normal_key_event(terminal.frame(), event, can_compose, None) .handle_normal_event(terminal.frame(), event, can_compose, None)
.await .await
{ {
Reaction::Handled Reaction::Handled
@ -302,7 +302,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
Cursor::Msg(id) => { Cursor::Msg(id) => {
let id = id.clone(); let id = id.clone();
if self if self
.handle_normal_key_event(terminal.frame(), event, can_compose, Some(id)) .handle_normal_event(terminal.frame(), event, can_compose, Some(id))
.await .await
{ {
Reaction::Handled Reaction::Handled
@ -313,7 +313,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
Cursor::Editor { Cursor::Editor {
coming_from, coming_from,
parent, parent,
} => self.handle_editor_key_event( } => self.handle_editor_event(
terminal, terminal,
crossterm_lock, crossterm_lock,
event, event,
@ -321,10 +321,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
parent.clone(), parent.clone(),
), ),
Cursor::Pseudo { .. } => { Cursor::Pseudo { .. } => {
if self if self.handle_movement_event(terminal.frame(), event).await {
.handle_movement_key_event(terminal.frame(), event)
.await
{
Reaction::Handled Reaction::Handled
} else { } else {
Reaction::NotHandled Reaction::NotHandled
@ -367,17 +364,17 @@ impl<M: Msg, S: MsgStore<M>> TreeViewState<M, S> {
self.0.lock().await.list_key_bindings(bindings, can_compose); self.0.lock().await.list_key_bindings(bindings, can_compose);
} }
pub async fn handle_key_event( pub async fn handle_event(
&mut self, &mut self,
terminal: &mut Terminal, terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>, crossterm_lock: &Arc<FairMutex<()>>,
event: KeyEvent, event: &InputEvent,
can_compose: bool, can_compose: bool,
) -> Reaction<M> { ) -> Reaction<M> {
self.0 self.0
.lock() .lock()
.await .await
.handle_key_event(terminal, crossterm_lock, event, can_compose) .handle_event(terminal, crossterm_lock, event, can_compose)
.await .await
} }

View file

@ -13,7 +13,7 @@ use crate::euph::api::{SessionType, SessionView, Snowflake};
use crate::euph::{self, Joined, Status}; use crate::euph::{self, Joined, Status};
use crate::store::MsgStore; use crate::store::MsgStore;
use crate::ui::chat::{ChatState, Reaction}; use crate::ui::chat::{ChatState, Reaction};
use crate::ui::input::{key, KeyBindingsList, KeyEvent}; use crate::ui::input::{key, InputEvent, KeyBindingsList, KeyEvent};
use crate::ui::widgets::background::Background; use crate::ui::widgets::background::Background;
use crate::ui::widgets::border::Border; use crate::ui::widgets::border::Border;
use crate::ui::widgets::editor::EditorState; use crate::ui::widgets::editor::EditorState;
@ -352,11 +352,11 @@ impl EuphRoom {
} }
} }
pub async fn handle_key_event( pub async fn handle_event(
&mut self, &mut self,
terminal: &mut Terminal, terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>, crossterm_lock: &Arc<FairMutex<()>>,
event: KeyEvent, event: &InputEvent,
) -> bool { ) -> bool {
match &self.state { match &self.state {
State::Normal => { State::Normal => {
@ -365,7 +365,7 @@ impl EuphRoom {
if let Ok(Some(Status::Joined(joined))) = room.status().await { if let Ok(Some(Status::Joined(joined))) = room.status().await {
match self match self
.chat .chat
.handle_key_event(terminal, crossterm_lock, event, true) .handle_event(terminal, crossterm_lock, event, true)
.await .await
{ {
Reaction::NotHandled => {} Reaction::NotHandled => {}
@ -391,7 +391,7 @@ impl EuphRoom {
} }
self.chat self.chat
.handle_key_event(terminal, crossterm_lock, event, false) .handle_event(terminal, crossterm_lock, event, false)
.await .await
.handled() .handled()
} }
@ -407,7 +407,7 @@ impl EuphRoom {
self.state = State::Normal; self.state = State::Normal;
true true
} }
_ => util::handle_editor_key_event( _ => util::handle_editor_event(
ed, ed,
terminal, terminal,
crossterm_lock, crossterm_lock,

View file

@ -1,6 +1,6 @@
use std::convert::Infallible; use std::convert::Infallible;
use crossterm::event::{KeyCode, KeyModifiers}; use crossterm::event::{Event, KeyCode, KeyModifiers};
use crossterm::style::{ContentStyle, Stylize}; use crossterm::style::{ContentStyle, Stylize};
use toss::styled::Styled; use toss::styled::Styled;
@ -16,6 +16,22 @@ use super::widgets::resize::Resize;
use super::widgets::text::Text; use super::widgets::text::Text;
use super::widgets::BoxedWidget; use super::widgets::BoxedWidget;
#[derive(Debug, Clone)]
pub enum InputEvent {
Key(KeyEvent),
Paste(String),
}
impl InputEvent {
pub fn from_event(event: Event) -> Option<Self> {
match event {
crossterm::event::Event::Key(key) => Some(Self::Key(key.into())),
crossterm::event::Event::Paste(text) => Some(Self::Paste(text)),
_ => None,
}
}
}
/// A key event data type that is a bit easier to pattern match on than /// A key event data type that is a bit easier to pattern match on than
/// [`crossterm::event::KeyEvent`]. /// [`crossterm::event::KeyEvent`].
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
@ -39,27 +55,30 @@ impl From<crossterm::event::KeyEvent> for KeyEvent {
#[rustfmt::skip] #[rustfmt::skip]
macro_rules! key { macro_rules! key {
// key!(Paste text)
( Paste $text:ident ) => { InputEvent::Paste($text) };
// key!('a') // key!('a')
( $key:literal ) => { KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: false, alt: false, } }; ( $key:literal ) => { InputEvent::Key(KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: false, alt: false, }) };
( Ctrl + $key:literal ) => { KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: true, alt: false, } }; ( Ctrl + $key:literal ) => { InputEvent::Key(KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: true, alt: false, }) };
( Alt + $key:literal ) => { KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: false, alt: true, } }; ( Alt + $key:literal ) => { InputEvent::Key(KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: false, alt: true, }) };
// key!(Char(xyz)) // key!(Char c)
( Char $key:pat ) => { KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: false, alt: false, } }; ( Char $key:pat ) => { InputEvent::Key(KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: false, alt: false, }) };
( Ctrl + Char $key:pat ) => { KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: true, alt: false, } }; ( Ctrl + Char $key:pat ) => { InputEvent::Key(KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: true, alt: false, }) };
( Alt + Char $key:pat ) => { KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: false, alt: true, } }; ( Alt + Char $key:pat ) => { InputEvent::Key(KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: false, alt: true, }) };
// key!(F(n)) // key!(F n)
( F $key:pat ) => { KeyEvent { code: KeyCode::F($key), shift: false, ctrl: false, alt: false, } }; ( F $key:pat ) => { InputEvent::Key(KeyEvent { code: KeyCode::F($key), shift: false, ctrl: false, alt: false, }) };
( Shift + F $key:pat ) => { KeyEvent { code: KeyCode::F($key), shift: true, ctrl: false, alt: false, } }; ( Shift + F $key:pat ) => { InputEvent::Key(KeyEvent { code: KeyCode::F($key), shift: true, ctrl: false, alt: false, }) };
( Ctrl + F $key:pat ) => { KeyEvent { code: KeyCode::F($key), shift: false, ctrl: true, alt: false, } }; ( Ctrl + F $key:pat ) => { InputEvent::Key(KeyEvent { code: KeyCode::F($key), shift: false, ctrl: true, alt: false, }) };
( Alt + F $key:pat ) => { KeyEvent { code: KeyCode::F($key), shift: false, ctrl: false, alt: true, } }; ( Alt + F $key:pat ) => { InputEvent::Key(KeyEvent { code: KeyCode::F($key), shift: false, ctrl: false, alt: true, }) };
// key!(other) // key!(other)
( $key:ident ) => { KeyEvent { code: KeyCode::$key, shift: false, ctrl: false, alt: false, } }; ( $key:ident ) => { InputEvent::Key(KeyEvent { code: KeyCode::$key, shift: false, ctrl: false, alt: false, }) };
( Shift + $key:ident ) => { KeyEvent { code: KeyCode::$key, shift: true, ctrl: false, alt: false, } }; ( Shift + $key:ident ) => { InputEvent::Key(KeyEvent { code: KeyCode::$key, shift: true, ctrl: false, alt: false, }) };
( Ctrl + $key:ident ) => { KeyEvent { code: KeyCode::$key, shift: false, ctrl: true, alt: false, } }; ( Ctrl + $key:ident ) => { InputEvent::Key(KeyEvent { code: KeyCode::$key, shift: false, ctrl: true, alt: false, }) };
( Alt + $key:ident ) => { KeyEvent { code: KeyCode::$key, shift: false, ctrl: false, alt: true, } }; ( Alt + $key:ident ) => { InputEvent::Key(KeyEvent { code: KeyCode::$key, shift: false, ctrl: false, alt: true, }) };
} }
pub(crate) use key; pub(crate) use key;

View file

@ -14,7 +14,7 @@ use crate::euph::{Joined, Status};
use crate::vault::Vault; use crate::vault::Vault;
use super::euph::room::EuphRoom; use super::euph::room::EuphRoom;
use super::input::{key, KeyBindingsList, KeyEvent}; use super::input::{key, InputEvent, KeyBindingsList, KeyEvent};
use super::widgets::background::Background; use super::widgets::background::Background;
use super::widgets::border::Border; use super::widgets::border::Border;
use super::widgets::editor::EditorState; use super::widgets::editor::EditorState;
@ -270,11 +270,11 @@ impl Rooms {
} }
} }
pub async fn handle_key_event( pub async fn handle_event(
&mut self, &mut self,
terminal: &mut Terminal, terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>, crossterm_lock: &Arc<FairMutex<()>>,
event: KeyEvent, event: &InputEvent,
) -> bool { ) -> bool {
self.stabilize_rooms().await; self.stabilize_rooms().await;
@ -318,7 +318,7 @@ impl Rooms {
}, },
State::ShowRoom(name) => { State::ShowRoom(name) => {
if let Some(room) = self.euph_rooms.get_mut(name) { if let Some(room) = self.euph_rooms.get_mut(name) {
if room.handle_key_event(terminal, crossterm_lock, event).await { if room.handle_event(terminal, crossterm_lock, event).await {
return true; return true;
} }
@ -340,7 +340,7 @@ impl Rooms {
} }
} }
_ => { _ => {
return util::handle_editor_key_event( return util::handle_editor_event(
ed, ed,
terminal, terminal,
crossterm_lock, crossterm_lock,

View file

@ -4,7 +4,7 @@ use crossterm::event::KeyCode;
use parking_lot::FairMutex; use parking_lot::FairMutex;
use toss::terminal::Terminal; use toss::terminal::Terminal;
use super::input::{key, KeyBindingsList, KeyEvent}; use super::input::{key, InputEvent, KeyBindingsList, KeyEvent};
use super::widgets::editor::EditorState; use super::widgets::editor::EditorState;
pub fn prompt( pub fn prompt(
@ -59,11 +59,11 @@ pub fn list_editor_key_bindings(
bindings.binding("↑/↓", "move cursor up/down"); bindings.binding("↑/↓", "move cursor up/down");
} }
pub fn handle_editor_key_event( pub fn handle_editor_event(
editor: &EditorState, editor: &EditorState,
terminal: &mut Terminal, terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>, crossterm_lock: &Arc<FairMutex<()>>,
event: KeyEvent, event: &InputEvent,
char_filter: impl Fn(char) -> bool, char_filter: impl Fn(char) -> bool,
can_edit_externally: bool, can_edit_externally: bool,
) -> bool { ) -> bool {
@ -71,13 +71,13 @@ pub fn handle_editor_key_event(
// Enter with *any* modifier pressed - if ctrl and shift don't // Enter with *any* modifier pressed - if ctrl and shift don't
// work, maybe alt does // work, maybe alt does
key!(Enter) => return false, key!(Enter) => return false,
KeyEvent { InputEvent::Key(KeyEvent {
code: KeyCode::Enter, code: KeyCode::Enter,
.. ..
} if char_filter('\n') => editor.insert_char(terminal.frame(), '\n'), }) if char_filter('\n') => editor.insert_char(terminal.frame(), '\n'),
// Editing // Editing
key!(Char ch) if char_filter(ch) => editor.insert_char(terminal.frame(), ch), key!(Char ch) if char_filter(*ch) => editor.insert_char(terminal.frame(), *ch),
key!(Ctrl + 'h') | key!(Backspace) => editor.backspace(terminal.frame()), key!(Ctrl + 'h') | key!(Backspace) => editor.backspace(terminal.frame()),
key!(Ctrl + 'd') | key!(Delete) => editor.delete(), key!(Ctrl + 'd') | key!(Delete) => editor.delete(),
key!(Ctrl + 'l') => editor.clear(), key!(Ctrl + 'l') => editor.clear(),