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]]
name = "toss"
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 = [
"crossterm",
"unicode-linebreak",

View file

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

View file

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

View file

@ -14,7 +14,7 @@ use toss::frame::{Frame, Pos, Size};
use toss::terminal::Terminal;
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::widgets::editor::EditorState;
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");
}
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;
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");
}
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 {
key!(' ') => {
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");
}
async fn handle_edit_initiating_key_event(
async fn handle_edit_initiating_event(
&mut self,
event: KeyEvent,
event: &InputEvent,
id: Option<M::Id>,
) -> bool {
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,
frame: &mut Frame,
event: KeyEvent,
event: &InputEvent,
can_compose: bool,
id: Option<M::Id>,
) -> bool {
#[allow(clippy::if_same_then_else)]
if self.handle_movement_key_event(frame, event).await {
if self.handle_movement_event(frame, event).await {
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
} else if can_compose {
self.handle_edit_initiating_key_event(event, id).await
self.handle_edit_initiating_event(event, id).await
} else {
false
}
@ -223,11 +223,11 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
util::list_editor_key_bindings(bindings, |_| true, true);
}
fn handle_editor_key_event(
fn handle_editor_event(
&mut self,
terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>,
event: KeyEvent,
event: &InputEvent,
coming_from: Option<M::Id>,
parent: Option<M::Id>,
) -> 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,
terminal,
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,
terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>,
event: KeyEvent,
event: &InputEvent,
can_compose: bool,
) -> Reaction<M> {
match &self.cursor {
Cursor::Bottom => {
if self
.handle_normal_key_event(terminal.frame(), event, can_compose, None)
.handle_normal_event(terminal.frame(), event, can_compose, None)
.await
{
Reaction::Handled
@ -302,7 +302,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
Cursor::Msg(id) => {
let id = id.clone();
if self
.handle_normal_key_event(terminal.frame(), event, can_compose, Some(id))
.handle_normal_event(terminal.frame(), event, can_compose, Some(id))
.await
{
Reaction::Handled
@ -313,7 +313,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
Cursor::Editor {
coming_from,
parent,
} => self.handle_editor_key_event(
} => self.handle_editor_event(
terminal,
crossterm_lock,
event,
@ -321,10 +321,7 @@ impl<M: Msg, S: MsgStore<M>> InnerTreeViewState<M, S> {
parent.clone(),
),
Cursor::Pseudo { .. } => {
if self
.handle_movement_key_event(terminal.frame(), event)
.await
{
if self.handle_movement_event(terminal.frame(), event).await {
Reaction::Handled
} else {
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);
}
pub async fn handle_key_event(
pub async fn handle_event(
&mut self,
terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>,
event: KeyEvent,
event: &InputEvent,
can_compose: bool,
) -> Reaction<M> {
self.0
.lock()
.await
.handle_key_event(terminal, crossterm_lock, event, can_compose)
.handle_event(terminal, crossterm_lock, event, can_compose)
.await
}

View file

@ -13,7 +13,7 @@ use crate::euph::api::{SessionType, SessionView, Snowflake};
use crate::euph::{self, Joined, Status};
use crate::store::MsgStore;
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::border::Border;
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,
terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>,
event: KeyEvent,
event: &InputEvent,
) -> bool {
match &self.state {
State::Normal => {
@ -365,7 +365,7 @@ impl EuphRoom {
if let Ok(Some(Status::Joined(joined))) = room.status().await {
match self
.chat
.handle_key_event(terminal, crossterm_lock, event, true)
.handle_event(terminal, crossterm_lock, event, true)
.await
{
Reaction::NotHandled => {}
@ -391,7 +391,7 @@ impl EuphRoom {
}
self.chat
.handle_key_event(terminal, crossterm_lock, event, false)
.handle_event(terminal, crossterm_lock, event, false)
.await
.handled()
}
@ -407,7 +407,7 @@ impl EuphRoom {
self.state = State::Normal;
true
}
_ => util::handle_editor_key_event(
_ => util::handle_editor_event(
ed,
terminal,
crossterm_lock,

View file

@ -1,6 +1,6 @@
use std::convert::Infallible;
use crossterm::event::{KeyCode, KeyModifiers};
use crossterm::event::{Event, KeyCode, KeyModifiers};
use crossterm::style::{ContentStyle, Stylize};
use toss::styled::Styled;
@ -16,6 +16,22 @@ use super::widgets::resize::Resize;
use super::widgets::text::Text;
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
/// [`crossterm::event::KeyEvent`].
#[derive(Debug, Clone, Copy)]
@ -39,27 +55,30 @@ impl From<crossterm::event::KeyEvent> for KeyEvent {
#[rustfmt::skip]
macro_rules! key {
// key!(Paste text)
( Paste $text:ident ) => { InputEvent::Paste($text) };
// key!('a')
( $key:literal ) => { KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: false, alt: false, } };
( Ctrl + $key:literal ) => { KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: true, alt: false, } };
( Alt + $key:literal ) => { KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: false, alt: true, } };
( $key:literal ) => { InputEvent::Key(KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: false, alt: false, }) };
( Ctrl + $key:literal ) => { InputEvent::Key(KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: true, alt: false, }) };
( Alt + $key:literal ) => { InputEvent::Key(KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: false, alt: true, }) };
// key!(Char(xyz))
( Char $key:pat ) => { KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: false, alt: false, } };
( Ctrl + Char $key:pat ) => { KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: true, alt: false, } };
( Alt + Char $key:pat ) => { KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: false, alt: true, } };
// key!(Char c)
( Char $key:pat ) => { InputEvent::Key(KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: false, alt: false, }) };
( Ctrl + Char $key:pat ) => { InputEvent::Key(KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: true, alt: false, }) };
( Alt + Char $key:pat ) => { InputEvent::Key(KeyEvent { code: KeyCode::Char($key), shift: _, ctrl: false, alt: true, }) };
// key!(F(n))
( F $key:pat ) => { 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, } };
( Ctrl + F $key:pat ) => { 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, } };
// key!(F n)
( F $key:pat ) => { InputEvent::Key(KeyEvent { code: KeyCode::F($key), shift: false, 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 ) => { InputEvent::Key(KeyEvent { code: KeyCode::F($key), shift: false, ctrl: true, alt: false, }) };
( Alt + F $key:pat ) => { InputEvent::Key(KeyEvent { code: KeyCode::F($key), shift: false, ctrl: false, alt: true, }) };
// key!(other)
( $key:ident ) => { KeyEvent { code: KeyCode::$key, shift: false, ctrl: false, alt: false, } };
( Shift + $key:ident ) => { KeyEvent { code: KeyCode::$key, shift: true, ctrl: false, alt: false, } };
( Ctrl + $key:ident ) => { KeyEvent { code: KeyCode::$key, shift: false, ctrl: true, alt: false, } };
( Alt + $key:ident ) => { KeyEvent { code: KeyCode::$key, shift: false, ctrl: false, alt: true, } };
( $key:ident ) => { InputEvent::Key(KeyEvent { code: KeyCode::$key, shift: false, ctrl: false, alt: false, }) };
( Shift + $key:ident ) => { InputEvent::Key(KeyEvent { code: KeyCode::$key, shift: true, ctrl: false, alt: false, }) };
( Ctrl + $key:ident ) => { InputEvent::Key(KeyEvent { code: KeyCode::$key, shift: false, ctrl: true, alt: false, }) };
( Alt + $key:ident ) => { InputEvent::Key(KeyEvent { code: KeyCode::$key, shift: false, ctrl: false, alt: true, }) };
}
pub(crate) use key;

View file

@ -14,7 +14,7 @@ use crate::euph::{Joined, Status};
use crate::vault::Vault;
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::border::Border;
use super::widgets::editor::EditorState;
@ -270,11 +270,11 @@ impl Rooms {
}
}
pub async fn handle_key_event(
pub async fn handle_event(
&mut self,
terminal: &mut Terminal,
crossterm_lock: &Arc<FairMutex<()>>,
event: KeyEvent,
event: &InputEvent,
) -> bool {
self.stabilize_rooms().await;
@ -318,7 +318,7 @@ impl Rooms {
},
State::ShowRoom(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;
}
@ -340,7 +340,7 @@ impl Rooms {
}
}
_ => {
return util::handle_editor_key_event(
return util::handle_editor_event(
ed,
terminal,
crossterm_lock,

View file

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