Move input buffer into it's own module.

This commit is contained in:
shy 2021-04-11 12:44:25 +02:00
parent d95f2faaef
commit ade2c9f4da
6 changed files with 299 additions and 236 deletions

View file

@ -81,7 +81,7 @@ impl AlarmRoster {
} }
// Parse string and add as alarm. // Parse string and add as alarm.
pub fn add(&mut self, input: &String) -> Result<(), &str> { pub fn add(&mut self, input: &String) -> Result<(), &'static str> {
let mut index = 0; let mut index = 0;
let mut time: u32 = 0; let mut time: u32 = 0;
let mut label: String; let mut label: String;

109
src/buffer.rs Normal file
View file

@ -0,0 +1,109 @@
use std::io::Write;
use termion::{clear, cursor, color};
use termion::raw::RawTerminal;
use crate::layout::Layout;
use crate::utils;
pub struct Buffer {
content: String,
// Used for error messages.
message: Option<&'static str>,
pub altered: bool,
}
impl Buffer {
pub fn new() -> Buffer {
Buffer {
content: String::new(),
altered: false,
message: None,
}
}
pub fn read(&mut self) -> &String {
self.altered = false;
&self.content
}
pub fn push(&mut self, value: char) {
self.altered = true;
self.message = None;
self.content.push(value);
}
pub fn pop(&mut self) -> Option<char> {
self.altered = true;
self.message = None;
self.content.pop()
}
pub fn is_empty(&self) -> bool {
self.content.is_empty()
}
// Clear input.
pub fn clear(&mut self) {
self.altered = true;
self.content.clear();
}
// Clear input and message.
pub fn reset(&mut self) {
self.message = None;
self.clear();
}
// Draw input buffer.
pub fn draw<W: Write>(
&self,
stdout: &mut RawTerminal<W>,
layout: &mut Layout,
) -> Result<(), std::io::Error>
{
if let Some(msg) = self.message {
write!(stdout,
"{}{}{}{}{}{}",
cursor::Hide,
cursor::Goto(layout.error.col, layout.error.line),
clear::UntilNewline,
color::Fg(color::LightRed),
&msg,
color::Fg(color::Reset))?;
return Ok(());
}
if !self.content.is_empty() {
write!(stdout,
"{}{}Add alarm: {}{}",
cursor::Goto(layout.buffer.col, layout.buffer.line),
clear::CurrentLine,
cursor::Show,
&self.content)?;
layout.cursor.col =
layout.buffer.col
+ 11
+ utils::unicode_length(&self.content);
// TODO: This would be a much better alternative, but panics because
// of interference with async_reader.
//layout.cursor.col = stdout.cursor_pos()?.0;
} else {
// Clear buffer display.
write!(stdout,
"{}{}{}",
cursor::Goto(layout.buffer.col, layout.buffer.line),
clear::CurrentLine,
cursor::Hide)?;
}
Ok(())
}
// Draw error message at input buffer position.
pub fn message(
&mut self,
msg: &'static str,
) {
self.message = Some(msg);
self.altered = true;
}
}

View file

@ -2,7 +2,8 @@ use std::time;
use std::io::Write; use std::io::Write;
use termion::{color, cursor}; use termion::{color, cursor};
use termion::raw::RawTerminal; use termion::raw::RawTerminal;
use crate::consts::*; use crate::consts::COLOR;
use crate::consts::digits::*;
use crate::layout::{Layout, Position}; use crate::layout::{Layout, Position};

View file

@ -1,6 +1,20 @@
pub const NAME: &str = env!("CARGO_PKG_NAME");
pub const VERSION: &str = env!("CARGO_PKG_VERSION"); pub const COLOR: [&dyn termion::color::Color; 6] = [
pub const USAGE: &str = concat!("USAGE: ", env!("CARGO_PKG_NAME"), &termion::color::Cyan,
&termion::color::Magenta,
&termion::color::Green,
&termion::color::Yellow,
&termion::color::Blue,
&termion::color::Red,
];
// Maximum length of labels.
pub const LABEL_SIZE_LIMIT: usize = 48;
pub mod ui {
pub const NAME: &str = env!("CARGO_PKG_NAME");
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const USAGE: &str = concat!("USAGE: ", env!("CARGO_PKG_NAME"),
" [-h|-v] [-e|--exec COMMAND] [-p] [-q] [ALARM[/LABEL]] " [-h|-v] [-e|--exec COMMAND] [-p] [-q] [ALARM[/LABEL]]
PARAMETERS: PARAMETERS:
@ -18,27 +32,18 @@ OPTIONS:
SIGNALS: <SIGUSR1> Reset clock. SIGNALS: <SIGUSR1> Reset clock.
<SIGUSR2> Pause or un-pause clock."); <SIGUSR2> Pause or un-pause clock.");
pub const MENUBAR: &str = pub const MENUBAR: &str =
"[0-9] Add alarm [d] Delete alarm [SPACE] Pause [r] Reset [c] Clear color [q] Quit"; "[0-9] Add alarm [d] Delete alarm [SPACE] Pause [r] Reset [c] Clear color [q] Quit";
pub const MENUBAR_SHORT: &str = pub const MENUBAR_SHORT: &str =
"[0-9] Add [d] Delete [SPACE] Pause [r] Reset [c] Clear [q] Quit"; "[0-9] Add [d] Delete [SPACE] Pause [r] Reset [c] Clear [q] Quit";
pub const MENUBAR_INS: &str = pub const MENUBAR_INS: &str =
"Format: HH:MM:SS/LABEL [ENTER] Accept [ESC] Cancel [CTR-C] Quit"; "Format: HH:MM:SS/LABEL [ENTER] Accept [ESC] Cancel [CTR-C] Quit";
}
pub const COLOR: [&dyn termion::color::Color; 6] = [ pub mod digits {
&termion::color::Cyan, pub const DIGIT_HEIGHT: u16 = 5;
&termion::color::Magenta, pub const DIGIT_WIDTH: u16 = 5;
&termion::color::Green, pub const DIGITS: [[&str; DIGIT_HEIGHT as usize]; 10] = [
&termion::color::Yellow,
&termion::color::Blue,
&termion::color::Red,
];
// Maximum length of labels.
pub const LABEL_SIZE_LIMIT: usize = 48;
pub const DIGIT_HEIGHT: u16 = 5;
pub const DIGIT_WIDTH: u16 = 5;
pub const DIGITS: [[&str; DIGIT_HEIGHT as usize]; 10] = [
[ [
// 0 // 0
"█▀▀▀█", "█▀▀▀█",
@ -110,9 +115,9 @@ pub const DIGITS: [[&str; DIGIT_HEIGHT as usize]; 10] = [
"", "",
"" ""
] ]
]; ];
pub const DIGITS_PLAIN: [[&str; DIGIT_HEIGHT as usize]; 10] = [ pub const DIGITS_PLAIN: [[&str; DIGIT_HEIGHT as usize]; 10] = [
[ [
// 0 // 0
"█████", "█████",
@ -184,5 +189,6 @@ pub const DIGITS_PLAIN: [[&str; DIGIT_HEIGHT as usize]; 10] = [
"", "",
"" ""
] ]
]; ];
}

View file

@ -1,7 +1,7 @@
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use crate::Config; use crate::Config;
use crate::consts::*; use crate::consts::digits::*;
// If screen size falls below these values we skip computation of new // If screen size falls below these values we skip computation of new
// positions. // positions.

View file

@ -1,5 +1,6 @@
extern crate termion; extern crate termion;
pub mod alarm; pub mod alarm;
mod buffer;
pub mod clock; pub mod clock;
pub mod consts; pub mod consts;
pub mod layout; pub mod layout;
@ -12,17 +13,17 @@ use std::io::Write;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering}; use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use signal_hook::{flag, low_level}; use signal_hook::{flag, low_level};
use termion::{clear, color, cursor, style}; use termion::{clear, cursor, style};
use termion::raw::{IntoRawMode, RawTerminal}; use termion::raw::{IntoRawMode, RawTerminal};
use termion::event::Key; use termion::event::Key;
use termion::input::TermRead; use termion::input::TermRead;
//use termion::cursor::DetectCursorPos; //use termion::cursor::DetectCursorPos;
use utils::*; use buffer::Buffer;
use clock::Clock; use clock::Clock;
use layout::Layout; use layout::Layout;
use alarm::{Countdown, exec_command}; use alarm::{Countdown, exec_command};
pub use alarm::AlarmRoster; pub use alarm::AlarmRoster;
pub use consts::*; pub use consts::ui::*;
const SIGTSTP: usize = signal_hook::consts::SIGTSTP as usize; const SIGTSTP: usize = signal_hook::consts::SIGTSTP as usize;
const SIGWINCH: usize = signal_hook::consts::SIGWINCH as usize; const SIGWINCH: usize = signal_hook::consts::SIGWINCH as usize;
@ -47,11 +48,9 @@ pub fn run(
layout.set_roster_width(alarm_roster.width()); layout.set_roster_width(alarm_roster.width());
let mut clock = Clock::new(); let mut clock = Clock::new();
let mut countdown = Countdown::new(); let mut countdown = Countdown::new();
let mut buffer = String::new(); let mut buffer = Buffer::new();
// State variables. // State variables.
// Request redraw of input buffer.
let mut update_buffer = false;
// Request redraw of menu. // Request redraw of menu.
let mut update_menu = false; let mut update_menu = false;
// Are we in insert mode? // Are we in insert mode?
@ -103,10 +102,9 @@ pub fn run(
} }
// Update input buffer display, if requested. // Update input buffer display, if requested.
if update_buffer { if buffer.altered {
draw_buffer(&mut stdout, &mut layout, &buffer)?; buffer.draw(&mut stdout, &mut layout)?;
stdout.flush()?; stdout.flush()?;
update_buffer = false;
} }
// Update elapsed time. // Update elapsed time.
@ -166,7 +164,7 @@ pub fn run(
alarm_roster.draw(&mut stdout, &mut layout); alarm_roster.draw(&mut stdout, &mut layout);
// Redraw buffer. // Redraw buffer.
draw_buffer(&mut stdout, &mut layout, &buffer)?; buffer.draw(&mut stdout, &mut layout)?;
// Schedule menu redraw. // Schedule menu redraw.
update_menu = true; update_menu = true;
@ -236,9 +234,9 @@ pub fn run(
// Enter. // Enter.
Key::Char('\n') => { Key::Char('\n') => {
if !buffer.is_empty() { if !buffer.is_empty() {
if let Err(e) = alarm_roster.add(&buffer) { if let Err(e) = alarm_roster.add(buffer.read()) {
// Error while processing input buffer. // Error while processing input buffer.
error_msg(&mut stdout, &layout, e)?; buffer.message(e);
} else { } else {
// Input buffer processed without error. // Input buffer processed without error.
layout.set_roster_width(alarm_roster.width()); layout.set_roster_width(alarm_roster.width());
@ -251,11 +249,10 @@ pub fn run(
}, },
// Escape ^W, and ^U clear input buffer. // Escape ^W, and ^U clear input buffer.
Key::Esc | Key::Ctrl('w') | Key::Ctrl('u') => { Key::Esc | Key::Ctrl('w') | Key::Ctrl('u') => {
buffer.clear(); buffer.reset();
insert_mode = false; insert_mode = false;
update_menu = true; update_menu = true;
layout.force_redraw = true; layout.force_redraw = true;
update_buffer = true;
}, },
// Backspace. // Backspace.
Key::Backspace => { Key::Backspace => {
@ -267,12 +264,10 @@ pub fn run(
layout.force_redraw = true; layout.force_redraw = true;
} }
} }
update_buffer = true;
}, },
// Forward every char if in insert mode. // Forward every char if in insert mode.
Key::Char(c) if insert_mode => { Key::Char(c) if insert_mode => {
buffer.push(c); buffer.push(c);
update_buffer = true;
}, },
// Reset clock on 'r'. // Reset clock on 'r'.
Key::Char('r') => { Key::Char('r') => {
@ -320,10 +315,8 @@ pub fn run(
insert_mode = true; insert_mode = true;
update_menu = true; update_menu = true;
layout.force_redraw = true; layout.force_redraw = true;
update_buffer = true;
} else if !buffer.is_empty() && c == ':' { } else if !buffer.is_empty() && c == ':' {
buffer.push(':'); buffer.push(':');
update_buffer = true;
} }
}, },
// Any other key. // Any other key.
@ -331,7 +324,7 @@ pub fn run(
} }
} else { } else {
// Main loop delay. // Main loop delay.
thread::sleep(time::Duration::from_millis(200)); thread::sleep(time::Duration::from_millis(100));
} }
} }
@ -450,52 +443,6 @@ pub fn register_signals(
flag::register(SIGWINCH as i32, Arc::clone(&recalc_flag)).unwrap(); flag::register(SIGWINCH as i32, Arc::clone(&recalc_flag)).unwrap();
} }
// Draw input buffer.
fn draw_buffer<W: Write>(
stdout: &mut RawTerminal<W>,
layout: &mut Layout,
buffer: &String,
) -> Result<(), std::io::Error>
{
if !buffer.is_empty() {
write!(stdout,
"{}{}Add alarm: {}{}",
cursor::Goto(layout.buffer.col, layout.buffer.line),
clear::CurrentLine,
cursor::Show,
buffer)?;
layout.cursor.col = layout.buffer.col + 11 + unicode_length(buffer);
// TODO: This would be a much better alternative, but panics because
// of interference with async_reader.
//layout.cursor.col = stdout.cursor_pos()?.0;
} else {
// Clear buffer display.
write!(stdout,
"{}{}{}",
cursor::Goto(layout.buffer.col, layout.buffer.line),
clear::CurrentLine,
cursor::Hide)?;
}
Ok(())
}
// Draw error message at input buffer position.
fn error_msg<W: Write>(
stdout: &mut RawTerminal<W>,
layout: &Layout,
msg: &str
) -> Result<(), std::io::Error>
{
write!(stdout,
"{}{}{}{}{}",
cursor::Goto(layout.error.col, layout.error.line),
color::Fg(color::LightRed),
msg,
color::Fg(color::Reset),
cursor::Hide)?;
Ok (())
}
// Prepare to suspend execution. Called on SIGTSTP. // Prepare to suspend execution. Called on SIGTSTP.
fn suspend<W: Write>(mut stdout: &mut RawTerminal<W>) fn suspend<W: Write>(mut stdout: &mut RawTerminal<W>)
-> Result<(), std::io::Error> -> Result<(), std::io::Error>