From c0cd93d0a5dfc63ffaadf13e4c4ddeea2a896926 Mon Sep 17 00:00:00 2001 From: Austen Adler Date: Fri, 14 May 2021 17:55:22 -0400 Subject: [PATCH] Migrate termion to crossterm --- Cargo.lock | 173 ++++++++++++++++++++++++++++++++++++------- Cargo.toml | 10 ++- build.earth | 2 +- src/main.rs | 136 +++++++++++++++++++++++++++------- src/util/event.rs | 185 ++++++++++++++++++++++------------------------ 5 files changed, 355 insertions(+), 151 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e057b9..caff2b1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", ] diff --git a/Cargo.toml b/Cargo.toml index 4737fe4..6b61691 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/build.earth b/build.earth index 2dcdd5a..0d5bd85 100644 --- a/build.earth +++ b/build.earth @@ -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 diff --git a/src/main.rs b/src/main.rs index bd1bb89..4e371e5 100644 --- a/src/main.rs +++ b/src/main.rs @@ -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> { - 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> { } })?; - 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> { } // 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> { } } } + 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 { +fn handle_key(app: &mut App, key: KeyEvent) -> CalculatorResult { 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 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(c: ClippyRectangle, f: &mut Frame>) { +fn draw_clippy_rect(c: ClippyRectangle, f: &mut Frame>) { let block = Block::default().title(c.title).borders(Borders::ALL); let dimensions = c.size(); let popup_layout = Layout::default() diff --git a/src/util/event.rs b/src/util/event.rs index a5ca790..765e68a 100644 --- a/src/util/event.rs +++ b/src/util/event.rs @@ -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 { 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>, - tx: mpsc::Sender>, - input_handle: thread::JoinHandle<()>, - ignore_exit_key: Arc, - 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>, +// tx: mpsc::Sender>, +// input_handle: thread::JoinHandle<()>, +// ignore_exit_key: Arc, +// 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, mpsc::RecvError> { - self.rx.recv() - } +// pub fn next(&self) -> Result, mpsc::RecvError> { +// self.rx.recv() +// } - pub fn try_next(&self) -> Result, mpsc::TryRecvError> { - self.rx.try_recv() - } +// pub fn try_iter(&self) -> TryIter> { +// self.rx.try_iter() +// } - pub fn try_iter(&self) -> TryIter> { - 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); +// } +// }