Migrate termion to crossterm

This commit is contained in:
Austen Adler 2021-05-14 17:55:22 -04:00
parent b47d41b615
commit c0cd93d0a5
5 changed files with 355 additions and 151 deletions

173
Cargo.lock generated
View File

@ -35,6 +35,31 @@ dependencies = [
"toml",
]
[[package]]
name = "crossterm"
version = "0.18.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e86d73f2a0b407b5768d10a8c720cf5d2df49a9efc10ca09176d201ead4b7fb"
dependencies = [
"bitflags",
"crossterm_winapi",
"lazy_static",
"libc",
"mio",
"parking_lot",
"signal-hook",
"winapi",
]
[[package]]
name = "crossterm_winapi"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2265c3f8e080075d9b6417aa72293fc71662f34b4af2612d8d1b074d29510db"
dependencies = [
"winapi",
]
[[package]]
name = "directories"
version = "2.0.2"
@ -67,6 +92,21 @@ dependencies = [
"wasi",
]
[[package]]
name = "instant"
version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.94"
@ -74,10 +114,78 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
[[package]]
name = "numtoa"
version = "0.1.0"
name = "lock_api"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
checksum = "0382880606dff6d15c9476c416d18690b72742aa7b605bb6dd6ec9030fbf07eb"
dependencies = [
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710"
dependencies = [
"cfg-if 1.0.0",
]
[[package]]
name = "mio"
version = "0.7.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf80d3e903b34e0bd7282b218398aec54e082c840d9baf8339e0080a0c542956"
dependencies = [
"libc",
"log",
"miow",
"ntapi",
"winapi",
]
[[package]]
name = "miow"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21"
dependencies = [
"winapi",
]
[[package]]
name = "ntapi"
version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f6bb902e437b6d86e03cce10a7e2af662292c5dfef23b65899ea3ac9354ad44"
dependencies = [
"winapi",
]
[[package]]
name = "parking_lot"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d7744ac029df22dca6284efe4e898991d28e3085c706c972bcd7da4a27a15eb"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7a782938e745763fe6907fc6ba86946d72f49fe7e21de074e08128a99fb018"
dependencies = [
"cfg-if 1.0.0",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi",
]
[[package]]
name = "proc-macro2"
@ -106,15 +214,6 @@ dependencies = [
"bitflags",
]
[[package]]
name = "redox_termios"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
dependencies = [
"redox_syscall",
]
[[package]]
name = "redox_users"
version = "0.4.0"
@ -127,14 +226,20 @@ dependencies = [
[[package]]
name = "rpn_rs"
version = "0.2.0"
version = "0.3.0"
dependencies = [
"confy",
"crossterm",
"serde",
"termion",
"tui",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.126"
@ -155,6 +260,32 @@ dependencies = [
"syn",
]
[[package]]
name = "signal-hook"
version = "0.1.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7e31d442c16f047a671b5a71e2161d6e68814012b7f5379d269ebd915fac2729"
dependencies = [
"libc",
"mio",
"signal-hook-registry",
]
[[package]]
name = "signal-hook-registry"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16f1d0fef1604ba8f7a073c7e701f213e056707210e9020af4528e0101ce11a6"
dependencies = [
"libc",
]
[[package]]
name = "smallvec"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
[[package]]
name = "syn"
version = "1.0.72"
@ -166,18 +297,6 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "termion"
version = "1.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
dependencies = [
"libc",
"numtoa",
"redox_syscall",
"redox_termios",
]
[[package]]
name = "toml"
version = "0.5.8"
@ -195,7 +314,7 @@ checksum = "9ced152a8e9295a5b168adc254074525c17ac4a83c90b2716274cc38118bddc9"
dependencies = [
"bitflags",
"cassowary",
"termion",
"crossterm",
"unicode-segmentation",
"unicode-width",
]

View File

@ -10,7 +10,13 @@ categories = ["command-line-utilities"]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tui = "0.14"
termion = "1.5"
# Linux only
# tui = "0.14"
# termion = "1.5"
# Windows support
crossterm = "0.18"
tui = { version = "0.14", default-features = false, features = ['crossterm'] }
serde = {version = "1.0", features = ["derive"]}
confy = "0.4.0"

View File

@ -44,4 +44,4 @@ all:
BUILD --build-arg TOOLCHAIN=arm-unknown-linux-musleabi --build-arg STRIP_CMD=arm-linux-gnueabi-strip +build
BUILD --build-arg TOOLCHAIN=armv7-unknown-linux-musleabi --build-arg STRIP_CMD=arm-linux-gnueabi-strip +build
# TODO: Cross compile to windows
# BUILD --build-arg TOOLCHAIN=x86_64-pc-windows-gnu +build
BUILD --build-arg TOOLCHAIN=x86_64-pc-windows-gnu +build

View File

@ -7,12 +7,22 @@ mod util;
use calc::constants::{CalculatorDisplayMode, CalculatorState, RegisterState};
use calc::errors::CalculatorResult;
use calc::Calculator;
use crossterm::{
event::{self, DisableMouseCapture, Event as CEvent, KeyCode, KeyEvent, KeyModifiers},
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use io::stdout;
use std::cmp;
use std::convert::TryFrom;
use std::io::Write;
use std::sync::mpsc;
use std::thread;
use std::time::{Duration, Instant};
use std::{error::Error, io};
use termion::{event::Key, raw::IntoRawMode, screen::AlternateScreen};
// use termion::{event::Key, raw::IntoRawMode, screen::AlternateScreen};
use tui::{
backend::TermionBackend,
backend::CrosstermBackend,
layout::{Constraint, Direction, Layout},
style::{Modifier, Style},
terminal::Frame,
@ -20,7 +30,9 @@ use tui::{
widgets::{Block, Borders, Clear, List, ListItem, Paragraph},
Terminal,
};
use util::event::{Event, Events};
use util::event::Event;
const TICK_RATE: Duration = Duration::from_millis(250);
struct Dimensions {
width: u16,
@ -58,15 +70,36 @@ impl Default for App {
}
fn main() -> Result<(), Box<dyn Error>> {
let stdout = io::stdout().into_raw_mode()?;
// let stdout = MouseTerminal::from(stdout);
let stdout = AlternateScreen::from(stdout);
let backend = TermionBackend::new(stdout);
enable_raw_mode()?;
let mut stdout = stdout();
execute!(stdout, EnterAlternateScreen, DisableMouseCapture)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?;
let events = Events::new();
let (tx, rx) = mpsc::channel();
let mut app = App::default();
thread::spawn(move || {
let mut last_tick = Instant::now();
loop {
// poll for tick rate duration, if no events, sent tick event.
let timeout = TICK_RATE
.checked_sub(last_tick.elapsed())
.unwrap_or_else(|| Duration::from_secs(0));
if event::poll(timeout).unwrap() {
if let CEvent::Key(key) = event::read().unwrap() {
tx.send(Event::Input(key)).unwrap();
}
}
if last_tick.elapsed() >= TICK_RATE {
tx.send(Event::Tick).unwrap();
last_tick = Instant::now();
}
}
});
'outer: loop {
terminal.draw(|f| {
let chunks = Layout::default()
@ -270,8 +303,8 @@ fn main() -> Result<(), Box<dyn Error>> {
}
})?;
if let Event::Input(key) = events.next()? {
app.error_msg = match handle_key(&mut app, &events, key) {
if let Event::Input(key) = rx.recv()? {
app.error_msg = match handle_key(&mut app, key) {
// Exit the program
Ok(CalculatorResponse::Quit) => break 'outer,
Ok(CalculatorResponse::Continue) => None,
@ -280,10 +313,10 @@ fn main() -> Result<(), Box<dyn Error>> {
}
// Slurp events without a redraw
for e in events.try_iter() {
for e in rx.try_iter() {
match e {
Event::Input(key) => {
app.error_msg = match handle_key(&mut app, &events, key) {
app.error_msg = match handle_key(&mut app, key) {
// Exit the program
Ok(CalculatorResponse::Quit) => break 'outer,
Ok(CalculatorResponse::Continue) => None,
@ -294,43 +327,88 @@ fn main() -> Result<(), Box<dyn Error>> {
}
}
}
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
app.calculator.close().map_err(|e| e.into())
}
fn handle_key(app: &mut App, events: &Events, key: Key) -> CalculatorResult<CalculatorResponse> {
fn handle_key(app: &mut App, key: KeyEvent) -> CalculatorResult<CalculatorResponse> {
match (&app.state, app.calculator.get_state()) {
(AppState::Calculator, CalculatorState::Normal) => match key {
Key::Char('q') => {
KeyEvent {
code: KeyCode::Char('q'),
modifiers: KeyModifiers::NONE,
} => {
return Ok(CalculatorResponse::Quit);
}
Key::Ctrl('s') => {
KeyEvent {
code: KeyCode::Char('s'),
modifiers: KeyModifiers::CONTROL,
} => {
app.calculator.save_config()?;
}
Key::Ctrl('l') => {
KeyEvent {
code: KeyCode::Char('l'),
modifiers: KeyModifiers::CONTROL,
} => {
app.calculator = Calculator::load_config()?;
}
Key::Char('h') => {
KeyEvent {
code: KeyCode::Char('h'),
modifiers: KeyModifiers::NONE,
} => {
app.state = AppState::Help;
}
Key::Char('\n') | Key::Char(' ') => {
KeyEvent {
code: KeyCode::Char('\n'),
modifiers: KeyModifiers::NONE,
}
| KeyEvent {
code: KeyCode::Char(' '),
modifiers: KeyModifiers::NONE,
} => {
app.calculator.take_input(' ')?;
}
Key::Right => {
KeyEvent {
code: KeyCode::Right,
modifiers: KeyModifiers::NONE,
} => {
app.calculator.take_input('>')?;
}
Key::Down => {
KeyEvent {
code: KeyCode::Down,
modifiers: KeyModifiers::NONE,
} => {
app.calculator.edit()?;
}
Key::Backspace => {
KeyEvent {
code: KeyCode::Backspace,
modifiers: KeyModifiers::NONE,
} => {
app.calculator.backspace()?;
}
Key::Char(c) => {
KeyEvent {
code: KeyCode::Char(c),
modifiers: KeyModifiers::NONE,
} => {
app.calculator.take_input(c)?;
}
_ => {}
},
(AppState::Help, _) => match key {
Key::Esc | Key::Char('q') => {
KeyEvent {
code: KeyCode::Esc,
modifiers: KeyModifiers::NONE,
}
| KeyEvent {
code: KeyCode::Char('q'),
modifiers: KeyModifiers::NONE,
} => {
app.state = AppState::Calculator;
app.calculator.cancel()?;
}
@ -340,11 +418,17 @@ fn handle_key(app: &mut App, events: &Events, key: Key) -> CalculatorResult<Calc
| (AppState::Calculator, CalculatorState::WaitingForSetting)
| (AppState::Calculator, CalculatorState::WaitingForRegister(_))
| (AppState::Calculator, CalculatorState::WaitingForMacro) => match key {
Key::Esc => {
KeyEvent {
code: KeyCode::Esc,
modifiers: KeyModifiers::NONE,
} => {
app.state = AppState::Calculator;
app.calculator.cancel()?;
}
Key::Char(c) => {
KeyEvent {
code: KeyCode::Char(c),
modifiers: KeyModifiers::NONE,
} => {
app.calculator.take_input(c)?;
}
_ => {}
@ -371,7 +455,7 @@ impl ClippyRectangle<'_> {
}
}
fn draw_clippy_rect<T: std::io::Write>(c: ClippyRectangle, f: &mut Frame<TermionBackend<T>>) {
fn draw_clippy_rect<T: std::io::Write>(c: ClippyRectangle, f: &mut Frame<CrosstermBackend<T>>) {
let block = Block::default().title(c.title).borders(Borders::ALL);
let dimensions = c.size();
let popup_layout = Layout::default()

View File

@ -1,110 +1,105 @@
use std::io;
use std::sync::mpsc;
use std::sync::mpsc::TryIter;
use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use std::thread;
use std::time::Duration;
// use std::io;
// use std::sync::mpsc;
// use std::sync::mpsc::TryIter;
// use std::sync::{
// atomic::{AtomicBool, Ordering},
// Arc,
// };
// use std::thread;
// use std::time::Duration;
use termion::event::Key;
use termion::input::TermRead;
// use crossterm::event::{KeyEvent, KeyCode, KeyModifiers};
pub enum Event<I> {
Input(I),
Tick,
}
/// A small event handler that wrap termion input and tick events. Each event
/// type is handled in its own thread and returned to a common `Receiver`
#[allow(dead_code)]
pub struct Events {
rx: mpsc::Receiver<Event<Key>>,
tx: mpsc::Sender<Event<Key>>,
input_handle: thread::JoinHandle<()>,
ignore_exit_key: Arc<AtomicBool>,
tick_handle: thread::JoinHandle<()>,
}
// /// A small event handler that wrap termion input and tick events. Each event
// /// type is handled in its own thread and returned to a common `Receiver`
// #[allow(dead_code)]
// pub struct Events {
// rx: mpsc::Receiver<Event<KeyEvent>>,
// tx: mpsc::Sender<Event<KeyEvent>>,
// input_handle: thread::JoinHandle<()>,
// ignore_exit_key: Arc<AtomicBool>,
// tick_handle: thread::JoinHandle<()>,
// }
#[derive(Debug, Clone, Copy)]
pub struct Config {
pub exit_key: Key,
pub tick_rate: Duration,
}
// #[derive(Debug, Clone, Copy)]
// pub struct Config {
// pub exit_key: KeyEvent,
// pub tick_rate: Duration,
// }
impl Default for Config {
fn default() -> Self {
Config {
exit_key: Key::Char('q'),
tick_rate: Duration::from_millis(250),
}
}
}
// impl Default for Config {
// fn default() -> Self {
// Config {
// exit_key: KeyEvent::new(KeyCode::Char('q'), KeyModifiers::NONE),
// tick_rate: Duration::from_millis(250),
// }
// }
// }
impl Events {
pub fn new() -> Events {
Events::with_config(Config::default())
}
// impl Events {
// pub fn new() -> Events {
// Events::with_config(Config::default())
// }
pub fn with_config(config: Config) -> Events {
let (tx, rx) = mpsc::channel();
let mac_tx = tx.clone();
let ignore_exit_key = Arc::new(AtomicBool::new(true));
let input_handle = {
let tx = tx.clone();
let ignore_exit_key = ignore_exit_key.clone();
thread::spawn(move || {
let stdin = io::stdin();
for evt in stdin.keys() {
if let Ok(key) = evt {
if let Err(err) = tx.send(Event::Input(key)) {
eprintln!("{}", err);
return;
}
if !ignore_exit_key.load(Ordering::Relaxed) && key == config.exit_key {
return;
}
}
}
})
};
let tick_handle = {
thread::spawn(move || loop {
if tx.send(Event::Tick).is_err() {
break;
}
thread::sleep(config.tick_rate);
})
};
Events {
rx,
tx: mac_tx,
ignore_exit_key,
input_handle,
tick_handle,
}
}
// pub fn with_config(config: Config) -> Events {
// let (tx, rx) = mpsc::channel();
// let mac_tx = tx.clone();
// let ignore_exit_key = Arc::new(AtomicBool::new(true));
// let input_handle = {
// let tx = tx.clone();
// let ignore_exit_key = ignore_exit_key.clone();
// thread::spawn(move || {
// let stdin = io::stdin();
// for evt in stdin.keys() {
// if let Ok(key) = evt {
// if let Err(err) = tx.send(Event::Input(key)) {
// eprintln!("{}", err);
// return;
// }
// if !ignore_exit_key.load(Ordering::Relaxed) && key == config.exit_key {
// return;
// }
// }
// }
// })
// };
// let tick_handle = {
// thread::spawn(move || loop {
// if tx.send(Event::Tick).is_err() {
// break;
// }
// thread::sleep(config.tick_rate);
// })
// };
// Events {
// rx,
// tx: mac_tx,
// ignore_exit_key,
// input_handle,
// tick_handle,
// }
// }
pub fn next(&self) -> Result<Event<Key>, mpsc::RecvError> {
self.rx.recv()
}
// pub fn next(&self) -> Result<Event<KeyEvent>, mpsc::RecvError> {
// self.rx.recv()
// }
pub fn try_next(&self) -> Result<Event<Key>, mpsc::TryRecvError> {
self.rx.try_recv()
}
// pub fn try_iter(&self) -> TryIter<Event<KeyEvent>> {
// self.rx.try_iter()
// }
pub fn try_iter(&self) -> TryIter<Event<Key>> {
self.rx.try_iter()
}
// #[allow(dead_code)]
// pub fn disable_exit_key(&mut self) {
// self.ignore_exit_key.store(true, Ordering::Relaxed);
// }
#[allow(dead_code)]
pub fn disable_exit_key(&mut self) {
self.ignore_exit_key.store(true, Ordering::Relaxed);
}
#[allow(dead_code)]
pub fn enable_exit_key(&mut self) {
self.ignore_exit_key.store(false, Ordering::Relaxed);
}
}
// #[allow(dead_code)]
// pub fn enable_exit_key(&mut self) {
// self.ignore_exit_key.store(false, Ordering::Relaxed);
// }
// }