aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJoris2023-01-26 08:12:13 +0100
committerJoris2023-01-26 08:40:13 +0100
commit417d0cac77ff4e48945bbc3086dd6d5c0c3bda6e (patch)
treeb5ff138092623733c1541f79877cd0b5b8f96d73 /src
parent155d8d96574a5ba141767da1af57afde55fccf6f (diff)
downloadflashcards-417d0cac77ff4e48945bbc3086dd6d5c0c3bda6e.tar.gz
flashcards-417d0cac77ff4e48945bbc3086dd6d5c0c3bda6e.tar.bz2
flashcards-417d0cac77ff4e48945bbc3086dd6d5c0c3bda6e.zip
Upgrade dependencies
- Switch to crossterm - add --hide-progress option
Diffstat (limited to 'src')
-rw-r--r--src/gui/message.rs15
-rw-r--r--src/gui/mod.rs43
-rw-r--r--src/gui/question.rs69
-rw-r--r--src/main.rs30
-rw-r--r--src/util/event.rs73
-rw-r--r--src/util/mod.rs1
6 files changed, 86 insertions, 145 deletions
diff --git a/src/gui/message.rs b/src/gui/message.rs
index 29b5d8a..0136f1c 100644
--- a/src/gui/message.rs
+++ b/src/gui/message.rs
@@ -1,7 +1,6 @@
use crate::gui::util;
-use crate::util::event::{Event, Events};
+use crossterm::event::{self, Event, KeyCode, KeyModifiers};
use anyhow::Result;
-use termion::event::Key;
use tui::{
backend::Backend,
layout::{Alignment, Constraint, Direction, Layout},
@@ -11,7 +10,6 @@ use tui::{
pub fn show<B: Backend>(
terminal: &mut Terminal<B>,
- events: &Events,
title: &str,
message: &str,
wait: bool,
@@ -33,14 +31,13 @@ pub fn show<B: Backend>(
})?;
if wait {
- if let Event::Input(key) = events.next()? {
- match key {
- Key::Char('q') | Key::Ctrl('c') => {
- break;
- }
- _ => {}
+ // if crossterm::event::poll(Duration::from_secs(0))? {
+ if let Event::Key(key) = event::read()? {
+ if key.code == KeyCode::Char('q') || key.code == KeyCode::Char('c') && key.modifiers.contains(KeyModifiers::CONTROL) {
+ break;
}
}
+ // }
} else {
break;
}
diff --git a/src/gui/mod.rs b/src/gui/mod.rs
index 358e4b5..1053de1 100644
--- a/src/gui/mod.rs
+++ b/src/gui/mod.rs
@@ -2,31 +2,44 @@ pub mod message;
pub mod question;
pub mod util;
-use crate::{db, space_repetition, util::event::Events, util::time};
+use crate::{db, space_repetition, util::time};
use anyhow::Result;
use rusqlite::Connection;
-use std::io;
-use termion::{raw::IntoRawMode, raw::RawTerminal, screen::AlternateScreen};
-use tui::{backend::TermionBackend, Terminal};
+use std::io::Stdout;
+use tui::{backend::CrosstermBackend, Terminal};
+use crossterm::{terminal};
-pub type Term = Terminal<TermionBackend<AlternateScreen<RawTerminal<io::Stdout>>>>;
+pub type Term = Terminal<CrosstermBackend<Stdout>>;
-pub fn terminal() -> Result<Term> {
- let stdout = io::stdout().into_raw_mode()?;
- let stdout = AlternateScreen::from(stdout);
- let backend = TermionBackend::new(stdout);
+pub fn setup_terminal() -> Result<Term> {
+ terminal::enable_raw_mode()?;
+ let mut stdout = std::io::stdout();
+ crossterm::execute!(stdout, terminal::EnterAlternateScreen)?;
+ let backend = CrosstermBackend::new(stdout);
Ok(Terminal::new(backend)?)
}
-pub fn start(conn: &Connection, term: &mut Term, events: &Events, deck_name: &str) -> Result<()> {
+pub fn restore_terminal(term: &mut Term) -> Result<()> {
+ terminal::disable_raw_mode()?;
+ crossterm::execute!(term.backend_mut(), terminal::LeaveAlternateScreen)?;
+ term.show_cursor()?;
+ Ok(())
+}
+
+pub fn start(
+ conn: &Connection,
+ term: &mut Term,
+ deck_name: &str,
+ hide_progress: bool,
+) -> Result<()> {
let mut answers = 0;
loop {
let now = time::seconds_since_unix_epoch()?;
- let title = title(deck_name, answers, db::count_available(conn).unwrap_or(0));
+ let title = title(deck_name, answers, db::count_available(conn).unwrap_or(0), hide_progress);
match db::pick_random_ready(conn) {
- Some(card) => match question::ask(term, events, &title, &card)? {
+ Some(card) => match question::ask(term, &title, &card)? {
question::Response::Aborted => break,
question::Response::Answered { difficulty } => {
answers += 1;
@@ -45,7 +58,7 @@ pub fn start(conn: &Connection, term: &mut Term, events: &Events, deck_name: &st
}
None => "Aucune carte n’est disponible. Votre deck est-il vide ?".to_string(),
};
- let _ = message::show(term, events, &title, &message, true);
+ let _ = message::show(term, &title, &message, true);
break;
}
}
@@ -54,8 +67,8 @@ pub fn start(conn: &Connection, term: &mut Term, events: &Events, deck_name: &st
Ok(())
}
-fn title(deck_name: &str, answers: i32, available_cards: i32) -> String {
- if answers == 0 && available_cards == 0 {
+fn title(deck_name: &str, answers: i32, available_cards: i32, hide_progress: bool) -> String {
+ if answers == 0 && available_cards == 0 || hide_progress {
deck_name.to_string()
} else if available_cards == 0 {
let from = answers;
diff --git a/src/gui/question.rs b/src/gui/question.rs
index 2aa6e65..73e4a93 100644
--- a/src/gui/question.rs
+++ b/src/gui/question.rs
@@ -1,11 +1,10 @@
use crate::{
gui::util,
model::{difficulty, difficulty::Difficulty, Card},
- util::event::{Event, Events},
util::serialization,
};
use anyhow::Result;
-use termion::event::Key;
+use crossterm::event::{self, Event, KeyCode, KeyModifiers};
use tui::{
backend::Backend,
layout::{Alignment, Constraint, Direction, Layout},
@@ -32,7 +31,6 @@ pub enum Response {
pub fn ask<B: Backend>(
terminal: &mut Terminal<B>,
- events: &Events,
title: &str,
card: &Card,
) -> Result<Response> {
@@ -136,10 +134,10 @@ pub fn ask<B: Backend>(
}
})?;
- if let Event::Input(key) = events.next()? {
+ if let Event::Key(key) = event::read()? {
match state.answer {
- Answer::Write => match key {
- Key::Char('\n') => {
+ Answer::Write => match key.code {
+ KeyCode::Char('\n') => {
let difficulty = if is_correct(&state.input, &card.responses) {
Difficulty::Good
} else {
@@ -147,57 +145,58 @@ pub fn ask<B: Backend>(
};
state.answer = Answer::Difficulty { difficulty }
}
- Key::Char(c) => {
- state.input.push(c);
- if is_correct(&state.input, &card.responses) {
- state.answer = Answer::Difficulty {
- difficulty: Difficulty::Good,
+ KeyCode::Char(c) => {
+ if key.modifiers.contains(KeyModifiers::CONTROL) {
+ if c == 'u' {
+ state.input.clear();
+ } else if c == 'w' {
+ let mut words = state.input.split_whitespace().collect::<Vec<&str>>();
+ if !words.is_empty() {
+ words.truncate(words.len() - 1);
+ let joined_words = words.join(" ");
+ let space = if !words.is_empty() { " " } else { "" };
+ state.input = format!("{joined_words}{space}");
+ }
+ } else if c == 'c' {
+ return Ok(Response::Aborted);
+ }
+ } else {
+ state.input.push(c);
+ if is_correct(&state.input, &card.responses) {
+ state.answer = Answer::Difficulty {
+ difficulty: Difficulty::Good,
+ }
}
}
}
- Key::Backspace => {
+ KeyCode::Backspace => {
state.input.pop();
}
- Key::Ctrl('u') => {
- state.input.clear();
- }
- Key::Ctrl('w') => {
- let mut words = state.input.split_whitespace().collect::<Vec<&str>>();
- if !words.is_empty() {
- words.truncate(words.len() - 1);
- state.input = format!(
- "{}{}",
- words.join(" "),
- if !words.is_empty() { " " } else { "" }
- );
- }
- }
- Key::Ctrl('c') => {
- return Ok(Response::Aborted);
- }
_ => {}
},
Answer::Difficulty {
difficulty: selected,
- } => match key {
- Key::Left => {
+ } => match key.code {
+ KeyCode::Left => {
for d in relative_element(&card.state.difficulties(), &selected, -1).iter()
{
state.answer = Answer::Difficulty { difficulty: *d }
}
}
- Key::Right => {
+ KeyCode::Right => {
for d in relative_element(&card.state.difficulties(), &selected, 1).iter() {
state.answer = Answer::Difficulty { difficulty: *d }
}
}
- Key::Char('\n') => {
+ KeyCode::Enter => {
return Ok(Response::Answered {
difficulty: selected,
})
}
- Key::Ctrl('c') => {
- return Ok(Response::Aborted);
+ KeyCode::Char('c') => {
+ if key.modifiers.contains(KeyModifiers::CONTROL) {
+ return Ok(Response::Aborted);
+ }
}
_ => {}
},
diff --git a/src/main.rs b/src/main.rs
index a791f29..c671d0e 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -6,35 +6,41 @@ mod space_repetition;
mod sync;
mod util;
-use crate::util::event::Events;
use anyhow::Result;
+use clap::Parser;
use std::path::PathBuf;
-use structopt::StructOpt;
-#[derive(StructOpt)]
-#[structopt()]
+#[derive(Parser)]
+#[clap()]
struct Opt {
- #[structopt(long, default_value = "deck.deck")]
+ /// Path to the deck
+ #[clap(long, default_value = "deck.deck")]
deck: String,
+
+ /// Hide current and remaining card counts
+ #[clap(long)]
+ hide_progress: bool,
}
fn main() -> Result<()> {
- let deck_path = Opt::from_args().deck;
+ let args = Opt::parse();
+ let deck_path = args.deck;
let mut conn = db::init(db_path(&deck_path))?;
let deck_name = deck::pp_from_path(&deck_path).unwrap_or_else(|| "Deck".to_string());
sync::run(&mut conn, &deck_path)?;
- let mut term = gui::terminal()?;
- let events = Events::new();
- match gui::start(&conn, &mut term, &events, &deck_name) {
- Ok(()) => Ok(()),
+ let mut term = gui::setup_terminal()?;
+
+ match gui::start(&conn, &mut term, &deck_name, args.hide_progress) {
+ Ok(()) => (),
Err(msg) => {
// Show errors in TUI, otherwise they are hidden
- gui::message::show(&mut term, &events, &deck_name, &format!("{msg}"), true)?;
- Err(msg)
+ gui::message::show(&mut term, &deck_name, &format!("{msg}"), true)?
}
}
+
+ gui::restore_terminal(&mut term)
}
fn db_path(deck_path: &str) -> String {
diff --git a/src/util/event.rs b/src/util/event.rs
deleted file mode 100644
index 379df99..0000000
--- a/src/util/event.rs
+++ /dev/null
@@ -1,73 +0,0 @@
-use std::io;
-use std::sync::mpsc;
-use std::thread;
-use std::time::Duration;
-
-use termion::event::Key;
-use termion::input::TermRead;
-
-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`
-pub struct Events {
- rx: mpsc::Receiver<Event<Key>>,
- input_handle: thread::JoinHandle<()>,
- tick_handle: thread::JoinHandle<()>,
-}
-
-#[derive(Debug, Clone, Copy)]
-pub struct Config {
- pub tick_rate: Duration,
-}
-
-impl Default for Config {
- fn default() -> Config {
- Config {
- tick_rate: Duration::from_millis(250),
- }
- }
-}
-
-impl Events {
- pub fn new() -> Events {
- Events::with_config(Config::default())
- }
-
- pub fn with_config(config: Config) -> Events {
- let (tx, rx) = mpsc::channel();
- let input_handle = {
- let tx = tx.clone();
- thread::spawn(move || {
- let stdin = io::stdin();
- for key in stdin.keys().flatten() {
- if let Err(err) = tx.send(Event::Input(key)) {
- eprintln!("{err}");
- return;
- }
- }
- })
- };
- let tick_handle = {
- thread::spawn(move || loop {
- if let Err(err) = tx.send(Event::Tick) {
- eprintln!("{err}");
- break;
- }
- thread::sleep(config.tick_rate);
- })
- };
- Events {
- rx,
- input_handle,
- tick_handle,
- }
- }
-
- pub fn next(&self) -> Result<Event<Key>, mpsc::RecvError> {
- self.rx.recv()
- }
-}
diff --git a/src/util/mod.rs b/src/util/mod.rs
index c866e61..3444389 100644
--- a/src/util/mod.rs
+++ b/src/util/mod.rs
@@ -1,4 +1,3 @@
#[allow(dead_code)]
-pub mod event;
pub mod serialization;
pub mod time;