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", "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]] [[package]]
name = "directories" name = "directories"
version = "2.0.2" version = "2.0.2"
@ -67,6 +92,21 @@ dependencies = [
"wasi", "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]] [[package]]
name = "libc" name = "libc"
version = "0.2.94" version = "0.2.94"
@ -74,10 +114,78 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e"
[[package]] [[package]]
name = "numtoa" name = "lock_api"
version = "0.1.0" version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index" 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]] [[package]]
name = "proc-macro2" name = "proc-macro2"
@ -106,15 +214,6 @@ dependencies = [
"bitflags", "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]] [[package]]
name = "redox_users" name = "redox_users"
version = "0.4.0" version = "0.4.0"
@ -127,14 +226,20 @@ dependencies = [
[[package]] [[package]]
name = "rpn_rs" name = "rpn_rs"
version = "0.2.0" version = "0.3.0"
dependencies = [ dependencies = [
"confy", "confy",
"crossterm",
"serde", "serde",
"termion",
"tui", "tui",
] ]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "serde" name = "serde"
version = "1.0.126" version = "1.0.126"
@ -155,6 +260,32 @@ dependencies = [
"syn", "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]] [[package]]
name = "syn" name = "syn"
version = "1.0.72" version = "1.0.72"
@ -166,18 +297,6 @@ dependencies = [
"unicode-xid", "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]] [[package]]
name = "toml" name = "toml"
version = "0.5.8" version = "0.5.8"
@ -195,7 +314,7 @@ checksum = "9ced152a8e9295a5b168adc254074525c17ac4a83c90b2716274cc38118bddc9"
dependencies = [ dependencies = [
"bitflags", "bitflags",
"cassowary", "cassowary",
"termion", "crossterm",
"unicode-segmentation", "unicode-segmentation",
"unicode-width", "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 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
tui = "0.14" # Linux only
termion = "1.5" # 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"]} serde = {version = "1.0", features = ["derive"]}
confy = "0.4.0" 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=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 BUILD --build-arg TOOLCHAIN=armv7-unknown-linux-musleabi --build-arg STRIP_CMD=arm-linux-gnueabi-strip +build
# TODO: Cross compile to windows # 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::constants::{CalculatorDisplayMode, CalculatorState, RegisterState};
use calc::errors::CalculatorResult; use calc::errors::CalculatorResult;
use calc::Calculator; 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::cmp;
use std::convert::TryFrom; 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 std::{error::Error, io};
use termion::{event::Key, raw::IntoRawMode, screen::AlternateScreen}; // use termion::{event::Key, raw::IntoRawMode, screen::AlternateScreen};
use tui::{ use tui::{
backend::TermionBackend, backend::CrosstermBackend,
layout::{Constraint, Direction, Layout}, layout::{Constraint, Direction, Layout},
style::{Modifier, Style}, style::{Modifier, Style},
terminal::Frame, terminal::Frame,
@ -20,7 +30,9 @@ use tui::{
widgets::{Block, Borders, Clear, List, ListItem, Paragraph}, widgets::{Block, Borders, Clear, List, ListItem, Paragraph},
Terminal, Terminal,
}; };
use util::event::{Event, Events}; use util::event::Event;
const TICK_RATE: Duration = Duration::from_millis(250);
struct Dimensions { struct Dimensions {
width: u16, width: u16,
@ -58,15 +70,36 @@ impl Default for App {
} }
fn main() -> Result<(), Box<dyn Error>> { fn main() -> Result<(), Box<dyn Error>> {
let stdout = io::stdout().into_raw_mode()?; enable_raw_mode()?;
// let stdout = MouseTerminal::from(stdout);
let stdout = AlternateScreen::from(stdout); let mut stdout = stdout();
let backend = TermionBackend::new(stdout); execute!(stdout, EnterAlternateScreen, DisableMouseCapture)?;
let backend = CrosstermBackend::new(stdout);
let mut terminal = Terminal::new(backend)?; let mut terminal = Terminal::new(backend)?;
let events = Events::new(); let (tx, rx) = mpsc::channel();
let mut app = App::default(); 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 { 'outer: loop {
terminal.draw(|f| { terminal.draw(|f| {
let chunks = Layout::default() let chunks = Layout::default()
@ -270,8 +303,8 @@ fn main() -> Result<(), Box<dyn Error>> {
} }
})?; })?;
if let Event::Input(key) = events.next()? { if let Event::Input(key) = rx.recv()? {
app.error_msg = match handle_key(&mut app, &events, key) { app.error_msg = match handle_key(&mut app, key) {
// Exit the program // Exit the program
Ok(CalculatorResponse::Quit) => break 'outer, Ok(CalculatorResponse::Quit) => break 'outer,
Ok(CalculatorResponse::Continue) => None, Ok(CalculatorResponse::Continue) => None,
@ -280,10 +313,10 @@ fn main() -> Result<(), Box<dyn Error>> {
} }
// Slurp events without a redraw // Slurp events without a redraw
for e in events.try_iter() { for e in rx.try_iter() {
match e { match e {
Event::Input(key) => { 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 // Exit the program
Ok(CalculatorResponse::Quit) => break 'outer, Ok(CalculatorResponse::Quit) => break 'outer,
Ok(CalculatorResponse::Continue) => None, 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()) 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()) { match (&app.state, app.calculator.get_state()) {
(AppState::Calculator, CalculatorState::Normal) => match key { (AppState::Calculator, CalculatorState::Normal) => match key {
Key::Char('q') => { KeyEvent {
code: KeyCode::Char('q'),
modifiers: KeyModifiers::NONE,
} => {
return Ok(CalculatorResponse::Quit); return Ok(CalculatorResponse::Quit);
} }
Key::Ctrl('s') => { KeyEvent {
code: KeyCode::Char('s'),
modifiers: KeyModifiers::CONTROL,
} => {
app.calculator.save_config()?; app.calculator.save_config()?;
} }
Key::Ctrl('l') => { KeyEvent {
code: KeyCode::Char('l'),
modifiers: KeyModifiers::CONTROL,
} => {
app.calculator = Calculator::load_config()?; app.calculator = Calculator::load_config()?;
} }
Key::Char('h') => { KeyEvent {
code: KeyCode::Char('h'),
modifiers: KeyModifiers::NONE,
} => {
app.state = AppState::Help; 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(' ')?; app.calculator.take_input(' ')?;
} }
Key::Right => { KeyEvent {
code: KeyCode::Right,
modifiers: KeyModifiers::NONE,
} => {
app.calculator.take_input('>')?; app.calculator.take_input('>')?;
} }
Key::Down => { KeyEvent {
code: KeyCode::Down,
modifiers: KeyModifiers::NONE,
} => {
app.calculator.edit()?; app.calculator.edit()?;
} }
Key::Backspace => { KeyEvent {
code: KeyCode::Backspace,
modifiers: KeyModifiers::NONE,
} => {
app.calculator.backspace()?; app.calculator.backspace()?;
} }
Key::Char(c) => { KeyEvent {
code: KeyCode::Char(c),
modifiers: KeyModifiers::NONE,
} => {
app.calculator.take_input(c)?; app.calculator.take_input(c)?;
} }
_ => {} _ => {}
}, },
(AppState::Help, _) => match key { (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.state = AppState::Calculator;
app.calculator.cancel()?; 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::WaitingForSetting)
| (AppState::Calculator, CalculatorState::WaitingForRegister(_)) | (AppState::Calculator, CalculatorState::WaitingForRegister(_))
| (AppState::Calculator, CalculatorState::WaitingForMacro) => match key { | (AppState::Calculator, CalculatorState::WaitingForMacro) => match key {
Key::Esc => { KeyEvent {
code: KeyCode::Esc,
modifiers: KeyModifiers::NONE,
} => {
app.state = AppState::Calculator; app.state = AppState::Calculator;
app.calculator.cancel()?; app.calculator.cancel()?;
} }
Key::Char(c) => { KeyEvent {
code: KeyCode::Char(c),
modifiers: KeyModifiers::NONE,
} => {
app.calculator.take_input(c)?; 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 block = Block::default().title(c.title).borders(Borders::ALL);
let dimensions = c.size(); let dimensions = c.size();
let popup_layout = Layout::default() let popup_layout = Layout::default()

View File

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