2018-08-07 12:01:15 +00:00
|
|
|
/*
|
2020-02-04 13:52:12 +00:00
|
|
|
* meli
|
2018-08-07 12:01:15 +00:00
|
|
|
*
|
|
|
|
* Copyright 2017-2018 Manos Pitsidianakis
|
|
|
|
*
|
|
|
|
* This file is part of meli.
|
|
|
|
*
|
|
|
|
* meli is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
* (at your option) any later version.
|
|
|
|
*
|
|
|
|
* meli is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
* along with meli. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
2023-06-09 17:31:20 +00:00
|
|
|
use std::os::unix::io::{AsRawFd, RawFd};
|
|
|
|
|
2019-09-09 09:53:39 +00:00
|
|
|
use crossbeam::{channel::Receiver, select};
|
2023-06-09 17:31:20 +00:00
|
|
|
use nix::poll::{poll, PollFd, PollFlags};
|
2019-08-25 18:44:19 +00:00
|
|
|
use serde::{Serialize, Serializer};
|
2023-06-09 17:31:20 +00:00
|
|
|
use termion::{
|
|
|
|
event::{
|
|
|
|
Event as TermionEvent, Key as TermionKey, MouseButton as TermionMouseButton,
|
|
|
|
MouseEvent as TermionMouseEvent,
|
|
|
|
},
|
|
|
|
input::TermReadEventsAndRaw,
|
|
|
|
};
|
2023-04-30 16:39:41 +00:00
|
|
|
|
|
|
|
use super::*;
|
2018-08-06 13:53:23 +00:00
|
|
|
|
2019-05-13 19:05:00 +00:00
|
|
|
#[derive(Debug, PartialEq, Eq, Clone)]
|
2018-08-06 13:53:23 +00:00
|
|
|
pub enum Key {
|
|
|
|
/// Backspace.
|
|
|
|
Backspace,
|
|
|
|
/// Left arrow.
|
|
|
|
Left,
|
|
|
|
/// Right arrow.
|
|
|
|
Right,
|
|
|
|
/// Up arrow.
|
|
|
|
Up,
|
|
|
|
/// Down arrow.
|
|
|
|
Down,
|
|
|
|
/// Home key.
|
|
|
|
Home,
|
|
|
|
/// End key.
|
|
|
|
End,
|
|
|
|
/// Page Up key.
|
|
|
|
PageUp,
|
|
|
|
/// Page Down key.
|
|
|
|
PageDown,
|
|
|
|
/// Delete key.
|
|
|
|
Delete,
|
|
|
|
/// Insert key.
|
|
|
|
Insert,
|
|
|
|
/// Function keys.
|
|
|
|
///
|
|
|
|
/// Only function keys 1 through 12 are supported.
|
|
|
|
F(u8),
|
|
|
|
/// Normal character.
|
|
|
|
Char(char),
|
|
|
|
/// Alt modified character.
|
|
|
|
Alt(char),
|
|
|
|
/// Ctrl modified character.
|
|
|
|
///
|
2023-04-30 16:39:41 +00:00
|
|
|
/// Note that certain keys may not be modifiable with `ctrl`, due to
|
|
|
|
/// limitations of terminals.
|
2018-08-06 13:53:23 +00:00
|
|
|
Ctrl(char),
|
|
|
|
/// Null byte.
|
|
|
|
Null,
|
|
|
|
/// Esc key.
|
|
|
|
Esc,
|
2023-06-09 17:31:20 +00:00
|
|
|
Mouse(MouseEvent),
|
2018-09-09 14:09:45 +00:00
|
|
|
Paste(String),
|
|
|
|
}
|
|
|
|
|
2023-06-09 17:31:20 +00:00
|
|
|
/// A mouse related event.
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
|
|
#[serde(untagged)]
|
|
|
|
pub enum MouseEvent {
|
|
|
|
/// A mouse button was pressed.
|
|
|
|
///
|
|
|
|
/// The coordinates are one-based.
|
|
|
|
Press(MouseButton, u16, u16),
|
|
|
|
/// A mouse button was released.
|
|
|
|
///
|
|
|
|
/// The coordinates are one-based.
|
|
|
|
Release(u16, u16),
|
|
|
|
/// A mouse button is held over the given coordinates.
|
|
|
|
///
|
|
|
|
/// The coordinates are one-based.
|
|
|
|
Hold(u16, u16),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<TermionMouseEvent> for MouseEvent {
|
|
|
|
fn from(val: TermionMouseEvent) -> Self {
|
|
|
|
use TermionMouseEvent::*;
|
|
|
|
match val {
|
|
|
|
Press(btn, a, b) => Self::Press(btn.into(), a, b),
|
|
|
|
Release(a, b) => Self::Release(a, b),
|
|
|
|
Hold(a, b) => Self::Hold(a, b),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A mouse button.
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
|
|
#[serde(untagged)]
|
|
|
|
pub enum MouseButton {
|
|
|
|
/// The left mouse button.
|
|
|
|
Left,
|
|
|
|
/// The right mouse button.
|
|
|
|
Right,
|
|
|
|
/// The middle mouse button.
|
|
|
|
Middle,
|
|
|
|
/// Mouse wheel is going up.
|
|
|
|
///
|
|
|
|
/// This event is typically only used with Mouse::Press.
|
|
|
|
WheelUp,
|
|
|
|
/// Mouse wheel is going down.
|
|
|
|
///
|
|
|
|
/// This event is typically only used with Mouse::Press.
|
|
|
|
WheelDown,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<TermionMouseButton> for MouseButton {
|
|
|
|
fn from(val: TermionMouseButton) -> Self {
|
|
|
|
use TermionMouseButton::*;
|
|
|
|
match val {
|
|
|
|
Left => Self::Left,
|
|
|
|
Right => Self::Right,
|
|
|
|
Middle => Self::Middle,
|
|
|
|
WheelUp => Self::WheelUp,
|
|
|
|
WheelDown => Self::WheelDown,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-10-14 09:50:38 +00:00
|
|
|
|
2019-03-30 19:21:17 +00:00
|
|
|
impl fmt::Display for Key {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
2019-06-18 18:13:58 +00:00
|
|
|
use crate::Key::*;
|
2019-03-30 19:21:17 +00:00
|
|
|
match self {
|
|
|
|
F(n) => write!(f, "F{}", n),
|
2020-08-18 09:12:10 +00:00
|
|
|
Char(' ') => write!(f, "Space"),
|
2019-03-30 19:21:17 +00:00
|
|
|
Char('\t') => write!(f, "Tab"),
|
|
|
|
Char('\n') => write!(f, "Enter"),
|
|
|
|
Char(c) => write!(f, "{}", c),
|
|
|
|
Alt(c) => write!(f, "M-{}", c),
|
|
|
|
Ctrl(c) => write!(f, "C-{}", c),
|
|
|
|
Paste(_) => write!(f, "Pasted buf"),
|
2019-04-04 12:29:33 +00:00
|
|
|
Null => write!(f, "Null byte"),
|
|
|
|
Esc => write!(f, "Esc"),
|
|
|
|
Backspace => write!(f, "Backspace"),
|
|
|
|
Left => write!(f, "Left"),
|
|
|
|
Right => write!(f, "Right"),
|
|
|
|
Up => write!(f, "Up"),
|
|
|
|
Down => write!(f, "Down"),
|
|
|
|
Home => write!(f, "Home"),
|
|
|
|
End => write!(f, "End"),
|
|
|
|
PageUp => write!(f, "PageUp"),
|
|
|
|
PageDown => write!(f, "PageDown"),
|
|
|
|
Delete => write!(f, "Delete"),
|
|
|
|
Insert => write!(f, "Insert"),
|
2020-10-14 09:50:38 +00:00
|
|
|
Mouse(_) => write!(f, "Mouse"),
|
2019-03-30 19:21:17 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-09-09 14:09:45 +00:00
|
|
|
impl<'a> From<&'a String> for Key {
|
|
|
|
fn from(v: &'a String) -> Self {
|
|
|
|
Key::Paste(v.to_string())
|
|
|
|
}
|
2018-08-06 13:53:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl From<TermionKey> for Key {
|
|
|
|
fn from(k: TermionKey) -> Self {
|
|
|
|
match k {
|
|
|
|
TermionKey::Backspace => Key::Backspace,
|
|
|
|
TermionKey::Left => Key::Left,
|
|
|
|
TermionKey::Right => Key::Right,
|
|
|
|
TermionKey::Up => Key::Up,
|
|
|
|
TermionKey::Down => Key::Down,
|
|
|
|
TermionKey::Home => Key::Home,
|
|
|
|
TermionKey::End => Key::End,
|
|
|
|
TermionKey::PageUp => Key::PageUp,
|
|
|
|
TermionKey::PageDown => Key::PageDown,
|
|
|
|
TermionKey::Delete => Key::Delete,
|
|
|
|
TermionKey::Insert => Key::Insert,
|
|
|
|
TermionKey::F(u) => Key::F(u),
|
|
|
|
TermionKey::Char(c) => Key::Char(c),
|
|
|
|
TermionKey::Alt(c) => Key::Alt(c),
|
|
|
|
TermionKey::Ctrl(c) => Key::Ctrl(c),
|
|
|
|
TermionKey::Null => Key::Null,
|
|
|
|
TermionKey::Esc => Key::Esc,
|
|
|
|
_ => Key::Char(' '),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-29 07:54:40 +00:00
|
|
|
impl PartialEq<Key> for &Key {
|
|
|
|
fn eq(&self, other: &Key) -> bool {
|
|
|
|
**self == *other
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-09-11 12:19:40 +00:00
|
|
|
#[derive(PartialEq, Eq)]
|
2020-02-04 13:52:12 +00:00
|
|
|
/// Keep track of whether we're accepting normal user input or a pasted string.
|
2018-09-09 14:09:45 +00:00
|
|
|
enum InputMode {
|
|
|
|
Normal,
|
2020-05-29 12:35:29 +00:00
|
|
|
Paste(Vec<u8>),
|
2018-09-09 14:09:45 +00:00
|
|
|
}
|
|
|
|
|
2019-11-05 06:32:27 +00:00
|
|
|
#[derive(Debug)]
|
2020-02-04 13:52:12 +00:00
|
|
|
/// Main process sends commands to the input thread.
|
2019-11-05 06:32:27 +00:00
|
|
|
pub enum InputCommand {
|
2020-02-04 13:52:12 +00:00
|
|
|
/// Exit thread
|
2019-11-05 06:32:27 +00:00
|
|
|
Kill,
|
|
|
|
}
|
|
|
|
|
2023-04-30 16:39:41 +00:00
|
|
|
/// The thread function that listens for user input and forwards it to the main
|
|
|
|
/// event loop.
|
2023-05-30 16:16:50 +00:00
|
|
|
///
|
|
|
|
/// If we fork (for example start `$EDITOR`) we want the `input-thread` to stop
|
|
|
|
/// reading from stdin. The best way I came up with right now is to send a
|
|
|
|
/// signal to the thread that is read in the first input in stdin after the
|
|
|
|
/// fork, and then the thread kills itself. The parent process spawns a new
|
|
|
|
/// input-thread when the child returns.
|
|
|
|
///
|
2023-06-04 18:07:26 +00:00
|
|
|
/// The main loop uses [`crate::state::State::try_wait_on_child`] to check if
|
|
|
|
/// child has exited.
|
2018-08-06 13:53:23 +00:00
|
|
|
pub fn get_events(
|
2020-05-29 12:35:29 +00:00
|
|
|
mut closure: impl FnMut((Key, Vec<u8>)),
|
2019-11-05 06:32:27 +00:00
|
|
|
rx: &Receiver<InputCommand>,
|
2020-05-29 12:35:29 +00:00
|
|
|
new_command_fd: RawFd,
|
2020-07-21 16:22:56 +00:00
|
|
|
working: std::sync::Arc<()>,
|
2020-07-05 12:28:55 +00:00
|
|
|
) {
|
2019-11-05 06:32:27 +00:00
|
|
|
let stdin = std::io::stdin();
|
2020-05-29 12:35:29 +00:00
|
|
|
let stdin_fd = PollFd::new(std::io::stdin().as_raw_fd(), PollFlags::POLLIN);
|
|
|
|
let new_command_pollfd = nix::poll::PollFd::new(new_command_fd, nix::poll::PollFlags::POLLIN);
|
2018-09-09 14:09:45 +00:00
|
|
|
let mut input_mode = InputMode::Normal;
|
|
|
|
let mut paste_buf = String::with_capacity(256);
|
2020-05-29 12:35:29 +00:00
|
|
|
let mut stdin_iter = stdin.events_and_raw();
|
|
|
|
'poll_while: while let Ok(_n_raw) = poll(&mut [new_command_pollfd, stdin_fd], -1) {
|
2019-09-09 09:53:39 +00:00
|
|
|
select! {
|
2020-05-29 12:35:29 +00:00
|
|
|
default => {
|
|
|
|
if stdin_fd.revents().is_some() {
|
2022-08-25 12:17:18 +00:00
|
|
|
'stdin_while: for c in stdin_iter.by_ref() {
|
2020-05-29 12:35:29 +00:00
|
|
|
match (c, &mut input_mode) {
|
|
|
|
(Ok((TermionEvent::Key(k), bytes)), InputMode::Normal) => {
|
|
|
|
closure((Key::from(k), bytes));
|
2020-06-26 17:35:04 +00:00
|
|
|
continue 'poll_while;
|
2020-05-29 12:35:29 +00:00
|
|
|
}
|
|
|
|
(
|
|
|
|
Ok((TermionEvent::Key(TermionKey::Char(k)), ref mut bytes)), InputMode::Paste(ref mut buf),
|
|
|
|
) => {
|
|
|
|
paste_buf.push(k);
|
2022-08-25 12:17:18 +00:00
|
|
|
let bytes = std::mem::take(bytes);
|
2020-05-29 12:35:29 +00:00
|
|
|
buf.extend(bytes.into_iter());
|
2020-06-26 17:35:04 +00:00
|
|
|
continue 'stdin_while;
|
2020-05-29 12:35:29 +00:00
|
|
|
}
|
|
|
|
(Ok((TermionEvent::Unsupported(ref k), _)), _) if k.as_slice() == BRACKET_PASTE_START => {
|
|
|
|
input_mode = InputMode::Paste(Vec::new());
|
2020-06-26 17:35:04 +00:00
|
|
|
continue 'stdin_while;
|
2020-05-29 12:35:29 +00:00
|
|
|
}
|
|
|
|
(Ok((TermionEvent::Unsupported(ref k), _)), InputMode::Paste(ref mut buf))
|
|
|
|
if k.as_slice() == BRACKET_PASTE_END =>
|
|
|
|
{
|
2022-08-25 12:17:18 +00:00
|
|
|
let buf = std::mem::take(buf);
|
2020-05-29 12:35:29 +00:00
|
|
|
input_mode = InputMode::Normal;
|
|
|
|
let ret = Key::from(&paste_buf);
|
|
|
|
paste_buf.clear();
|
|
|
|
closure((ret, buf));
|
2020-06-26 17:35:04 +00:00
|
|
|
continue 'poll_while;
|
2020-05-29 12:35:29 +00:00
|
|
|
}
|
2020-10-14 09:50:38 +00:00
|
|
|
(Ok((TermionEvent::Mouse(mev), bytes)), InputMode::Normal) => {
|
2023-06-09 17:31:20 +00:00
|
|
|
closure((Key::Mouse(mev.into()), bytes));
|
2020-10-14 09:50:38 +00:00
|
|
|
continue 'poll_while;
|
|
|
|
}
|
2020-06-26 17:35:04 +00:00
|
|
|
_ => {
|
|
|
|
continue 'poll_while;
|
|
|
|
} // Mouse events or errors.
|
2020-05-29 12:35:29 +00:00
|
|
|
}
|
2019-11-05 06:32:27 +00:00
|
|
|
}
|
2018-08-06 13:53:23 +00:00
|
|
|
}
|
2020-05-29 12:35:29 +00:00
|
|
|
},
|
2019-11-05 06:32:27 +00:00
|
|
|
recv(rx) -> cmd => {
|
2020-05-29 12:35:29 +00:00
|
|
|
use nix::sys::time::TimeValLike;
|
|
|
|
let mut buf = [0;2];
|
|
|
|
let mut read_fd_set = nix::sys::select::FdSet::new();
|
|
|
|
read_fd_set.insert(new_command_fd);
|
|
|
|
let mut error_fd_set = nix::sys::select::FdSet::new();
|
|
|
|
error_fd_set.insert(new_command_fd);
|
|
|
|
let timeval: nix::sys::time::TimeSpec = nix::sys::time::TimeSpec::seconds(2);
|
|
|
|
if nix::sys::select::pselect(None, Some(&mut read_fd_set), None, Some(&mut error_fd_set), Some(&timeval), None).is_err() || error_fd_set.highest() == Some(new_command_fd) || read_fd_set.highest() != Some(new_command_fd) {
|
|
|
|
continue 'poll_while;
|
|
|
|
};
|
|
|
|
let _ = nix::unistd::read(new_command_fd, buf.as_mut());
|
2019-11-05 06:32:27 +00:00
|
|
|
match cmd.unwrap() {
|
|
|
|
InputCommand::Kill => return,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
2020-07-21 16:22:56 +00:00
|
|
|
drop(working);
|
2019-11-05 06:32:27 +00:00
|
|
|
}
|
2019-03-03 12:24:15 +00:00
|
|
|
|
|
|
|
impl<'de> Deserialize<'de> for Key {
|
|
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
2019-03-14 10:19:25 +00:00
|
|
|
where
|
|
|
|
D: Deserializer<'de>,
|
|
|
|
{
|
|
|
|
struct KeyVisitor;
|
2019-03-03 12:24:15 +00:00
|
|
|
|
2019-03-14 10:19:25 +00:00
|
|
|
impl<'de> Visitor<'de> for KeyVisitor {
|
|
|
|
type Value = Key;
|
2019-03-03 12:24:15 +00:00
|
|
|
|
2019-03-14 10:19:25 +00:00
|
|
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
2019-11-26 23:44:26 +00:00
|
|
|
formatter
|
|
|
|
.write_str("a valid key value. Please consult the manual for valid key inputs.")
|
2019-03-03 12:24:15 +00:00
|
|
|
}
|
|
|
|
|
2019-03-14 10:19:25 +00:00
|
|
|
fn visit_str<E>(self, value: &str) -> Result<Key, E>
|
|
|
|
where
|
|
|
|
E: de::Error,
|
|
|
|
{
|
|
|
|
match value {
|
2019-11-26 23:44:26 +00:00
|
|
|
"Backspace" | "backspace" => Ok(Key::Backspace),
|
|
|
|
"Left" | "left" => Ok(Key::Left),
|
|
|
|
"Right" | "right" => Ok(Key::Right),
|
|
|
|
"Up" | "up" => Ok(Key::Up),
|
|
|
|
"Down" | "down" => Ok(Key::Down),
|
|
|
|
"Home" | "home" => Ok(Key::Home),
|
|
|
|
"End" | "end" => Ok(Key::End),
|
|
|
|
"PageUp" | "pageup" => Ok(Key::PageUp),
|
|
|
|
"PageDown" | "pagedown" => Ok(Key::PageDown),
|
|
|
|
"Delete" | "delete" => Ok(Key::Delete),
|
|
|
|
"Insert" | "insert" => Ok(Key::Insert),
|
|
|
|
"Enter" | "enter" => Ok(Key::Char('\n')),
|
|
|
|
"Tab" | "tab" => Ok(Key::Char('\t')),
|
|
|
|
"Esc" | "esc" => Ok(Key::Esc),
|
2022-08-25 12:17:18 +00:00
|
|
|
s if s.len() == 1 => Ok(Key::Char(s.chars().next().unwrap())),
|
|
|
|
s if s.starts_with('F') && (s.len() == 2 || s.len() == 3) => {
|
2019-11-26 23:44:26 +00:00
|
|
|
use std::str::FromStr;
|
|
|
|
|
|
|
|
if let Ok(n) = u8::from_str(&s[1..]) {
|
2022-08-25 12:17:18 +00:00
|
|
|
if (1..=12).contains(&n) {
|
2019-11-26 23:44:26 +00:00
|
|
|
return Ok(Key::F(n));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Err(de::Error::custom(format!(
|
2023-04-30 16:39:41 +00:00
|
|
|
"`{}` should be a number 1 <= n <= 12 instead.",
|
|
|
|
&s[1..]
|
2019-11-26 23:44:26 +00:00
|
|
|
)))
|
|
|
|
}
|
2022-08-25 12:17:18 +00:00
|
|
|
s if s.starts_with("M-") && s.len() == 3 => {
|
2019-11-26 23:44:26 +00:00
|
|
|
let c = s.as_bytes()[2] as char;
|
|
|
|
|
|
|
|
if c.is_lowercase() || c.is_numeric() {
|
|
|
|
return Ok(Key::Alt(c));
|
|
|
|
}
|
|
|
|
|
|
|
|
Err(de::Error::custom(format!(
|
2023-04-30 16:39:41 +00:00
|
|
|
"`{}` should be a lowercase and alphanumeric character instead.",
|
|
|
|
&s[2..]
|
2019-11-26 23:44:26 +00:00
|
|
|
)))
|
|
|
|
}
|
2022-08-25 12:17:18 +00:00
|
|
|
s if s.starts_with("C-") && s.len() == 3 => {
|
2019-11-26 23:44:26 +00:00
|
|
|
let c = s.as_bytes()[2] as char;
|
|
|
|
|
|
|
|
if c.is_lowercase() || c.is_numeric() {
|
|
|
|
return Ok(Key::Ctrl(c));
|
|
|
|
}
|
|
|
|
Err(de::Error::custom(format!(
|
2023-04-30 16:39:41 +00:00
|
|
|
"`{}` should be a lowercase and alphanumeric character instead.",
|
|
|
|
&s[2..]
|
2019-11-26 23:44:26 +00:00
|
|
|
)))
|
|
|
|
}
|
|
|
|
_ => Err(de::Error::custom(format!(
|
2023-04-30 16:39:41 +00:00
|
|
|
"Cannot derive shortcut from `{}`. Please consult the manual for valid \
|
|
|
|
key inputs.",
|
|
|
|
value
|
2019-11-26 23:44:26 +00:00
|
|
|
))),
|
2019-03-14 10:19:25 +00:00
|
|
|
}
|
|
|
|
}
|
2019-03-03 12:24:15 +00:00
|
|
|
}
|
2019-03-14 10:19:25 +00:00
|
|
|
|
|
|
|
deserializer.deserialize_identifier(KeyVisitor)
|
|
|
|
}
|
2019-03-03 12:24:15 +00:00
|
|
|
}
|
2019-08-25 18:44:19 +00:00
|
|
|
|
|
|
|
impl Serialize for Key {
|
|
|
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
|
|
where
|
|
|
|
S: Serializer,
|
|
|
|
{
|
|
|
|
match self {
|
|
|
|
Key::Backspace => serializer.serialize_str("Backspace"),
|
|
|
|
Key::Left => serializer.serialize_str("Left"),
|
|
|
|
Key::Right => serializer.serialize_str("Right"),
|
|
|
|
Key::Up => serializer.serialize_str("Up"),
|
|
|
|
Key::Down => serializer.serialize_str("Down"),
|
|
|
|
Key::Home => serializer.serialize_str("Home"),
|
|
|
|
Key::End => serializer.serialize_str("End"),
|
|
|
|
Key::PageUp => serializer.serialize_str("PageUp"),
|
|
|
|
Key::PageDown => serializer.serialize_str("PageDown"),
|
|
|
|
Key::Delete => serializer.serialize_str("Delete"),
|
|
|
|
Key::Insert => serializer.serialize_str("Insert"),
|
|
|
|
Key::Esc => serializer.serialize_str("Esc"),
|
2019-11-26 23:44:26 +00:00
|
|
|
Key::Char('\n') => serializer.serialize_str("Enter"),
|
|
|
|
Key::Char('\t') => serializer.serialize_str("Tab"),
|
2019-08-25 18:44:19 +00:00
|
|
|
Key::Char(c) => serializer.serialize_char(*c),
|
|
|
|
Key::F(n) => serializer.serialize_str(&format!("F{}", n)),
|
|
|
|
Key::Alt(c) => serializer.serialize_str(&format!("M-{}", c)),
|
|
|
|
Key::Ctrl(c) => serializer.serialize_str(&format!("C-{}", c)),
|
2019-11-26 23:44:26 +00:00
|
|
|
Key::Null => serializer.serialize_str("Null"),
|
2023-06-09 17:31:20 +00:00
|
|
|
Key::Mouse(mev) => mev.serialize(serializer),
|
2019-11-26 23:44:26 +00:00
|
|
|
Key::Paste(s) => serializer.serialize_str(s),
|
2019-08-25 18:44:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-11-26 23:44:26 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_key_serde() {
|
2022-09-11 12:19:40 +00:00
|
|
|
#[derive(Debug, Deserialize, PartialEq, Eq)]
|
2019-11-26 23:44:26 +00:00
|
|
|
struct V {
|
|
|
|
k: Key,
|
|
|
|
}
|
|
|
|
|
|
|
|
macro_rules! test_key {
|
|
|
|
($s:literal, ok $v:expr) => {
|
|
|
|
assert_eq!(
|
|
|
|
toml::from_str::<V>(std::concat!("k = \"", $s, "\"")),
|
|
|
|
Ok(V { k: $v })
|
|
|
|
);
|
|
|
|
};
|
|
|
|
($s:literal, err $v:literal) => {
|
|
|
|
assert_eq!(
|
|
|
|
toml::from_str::<V>(std::concat!("k = \"", $s, "\""))
|
|
|
|
.unwrap_err()
|
|
|
|
.to_string(),
|
|
|
|
$v.to_string()
|
|
|
|
);
|
|
|
|
};
|
|
|
|
}
|
|
|
|
test_key!("Backspace", ok Key::Backspace);
|
|
|
|
test_key!("Left", ok Key::Left );
|
|
|
|
test_key!("Right", ok Key::Right);
|
|
|
|
test_key!("Up", ok Key::Up );
|
|
|
|
test_key!("Down", ok Key::Down );
|
|
|
|
test_key!("Home", ok Key::Home );
|
|
|
|
test_key!("End", ok Key::End );
|
|
|
|
test_key!("PageUp", ok Key::PageUp );
|
|
|
|
test_key!("PageDown", ok Key::PageDown );
|
|
|
|
test_key!("Delete", ok Key::Delete );
|
|
|
|
test_key!("Insert", ok Key::Insert );
|
|
|
|
test_key!("Enter", ok Key::Char('\n') );
|
|
|
|
test_key!("Tab", ok Key::Char('\t') );
|
|
|
|
test_key!("k", ok Key::Char('k') );
|
|
|
|
test_key!("1", ok Key::Char('1') );
|
|
|
|
test_key!("Esc", ok Key::Esc );
|
|
|
|
test_key!("C-a", ok Key::Ctrl('a') );
|
|
|
|
test_key!("C-1", ok Key::Ctrl('1') );
|
|
|
|
test_key!("M-a", ok Key::Alt('a') );
|
|
|
|
test_key!("F1", ok Key::F(1) );
|
|
|
|
test_key!("F12", ok Key::F(12) );
|
|
|
|
test_key!("C-V", err "`V` should be a lowercase and alphanumeric character instead. for key `k` at line 1 column 5");
|
|
|
|
test_key!("M-V", err "`V` should be a lowercase and alphanumeric character instead. for key `k` at line 1 column 5");
|
|
|
|
test_key!("F13", err "`13` should be a number 1 <= n <= 12 instead. for key `k` at line 1 column 5");
|
|
|
|
test_key!("Fc", err "`c` should be a number 1 <= n <= 12 instead. for key `k` at line 1 column 5");
|
|
|
|
test_key!("adsfsf", err "Cannot derive shortcut from `adsfsf`. Please consult the manual for valid key inputs. for key `k` at line 1 column 5");
|
|
|
|
}
|