Rustfmt.
This commit is contained in:
parent
b10886508c
commit
35a8739f27
10 changed files with 511 additions and 493 deletions
162
src/alarm.rs
162
src/alarm.rs
|
@ -1,20 +1,19 @@
|
|||
use std::io::Write;
|
||||
use std::process::{Command, Stdio, Child};
|
||||
use std::io::BufRead;
|
||||
use termion::{color, cursor, style};
|
||||
use termion::raw::RawTerminal;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use crate::Config;
|
||||
use crate::clock::Clock;
|
||||
use crate::consts::{COLOR, LABEL_SIZE_LIMIT};
|
||||
use crate::layout::{Layout, Position};
|
||||
use crate::utils::*;
|
||||
use crate::consts::{COLOR, LABEL_SIZE_LIMIT};
|
||||
use crate::Config;
|
||||
use std::io::BufRead;
|
||||
use std::io::Write;
|
||||
use std::process::{Child, Command, Stdio};
|
||||
use termion::raw::RawTerminal;
|
||||
use termion::{color, cursor, style};
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
// Delimiter between time and label. Remember to update usage information in
|
||||
// consts.rs when changing this.
|
||||
const DELIMITER: char = '/';
|
||||
|
||||
|
||||
pub struct Countdown {
|
||||
pub value: u32,
|
||||
position: Option<Position>,
|
||||
|
@ -42,17 +41,17 @@ impl Countdown {
|
|||
}
|
||||
|
||||
// Draw countdown.
|
||||
pub fn draw<W: Write>(&self, stdout: &mut RawTerminal<W>)
|
||||
-> Result<(), std::io::Error>
|
||||
{
|
||||
pub fn draw<W: Write>(&self, stdout: &mut RawTerminal<W>) -> Result<(), std::io::Error> {
|
||||
if let Some(pos) = &self.position {
|
||||
if self.value < 3600 {
|
||||
// Show minutes and seconds.
|
||||
write!(stdout,
|
||||
write!(
|
||||
stdout,
|
||||
"{}(-{:02}:{:02})",
|
||||
cursor::Goto(pos.col, pos.line),
|
||||
(self.value / 60) % 60,
|
||||
self.value % 60)?;
|
||||
self.value % 60
|
||||
)?;
|
||||
if self.value == 3599 {
|
||||
// Write three additional spaces after switching from hour display to
|
||||
// minute display.
|
||||
|
@ -60,29 +59,22 @@ impl Countdown {
|
|||
}
|
||||
} else {
|
||||
// Show hours, minutes and seconds.
|
||||
write!(stdout,
|
||||
write!(
|
||||
stdout,
|
||||
"{}(-{:02}:{:02}:{:02})",
|
||||
cursor::Goto(pos.col, pos.line),
|
||||
self.value / 3600,
|
||||
(self.value / 60) % 60,
|
||||
self.value % 60)?;
|
||||
self.value % 60
|
||||
)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn place(
|
||||
&mut self,
|
||||
layout: &Layout,
|
||||
alarm: &Alarm,
|
||||
offset: usize,
|
||||
index: usize,
|
||||
) {
|
||||
pub fn place(&mut self, layout: &Layout, alarm: &Alarm, offset: usize, index: usize) {
|
||||
// Compute position.
|
||||
let mut col =
|
||||
layout.roster.col
|
||||
+ 3
|
||||
+ UnicodeWidthStr::width(alarm.label.as_str()) as u16;
|
||||
let mut col = layout.roster.col + 3 + UnicodeWidthStr::width(alarm.label.as_str()) as u16;
|
||||
let mut line = layout.roster.line + index as u16;
|
||||
|
||||
// Compensate for "hidden" items in the alarm roster.
|
||||
|
@ -176,8 +168,12 @@ impl AlarmRoster {
|
|||
}
|
||||
|
||||
// Skip if time is out of boundaries.
|
||||
if time == 0 { return Err("Evaluates to zero.") };
|
||||
if time >= 24 * 60 * 60 { return Err("Values >24h not supported.") };
|
||||
if time == 0 {
|
||||
return Err("Evaluates to zero.");
|
||||
};
|
||||
if time >= 24 * 60 * 60 {
|
||||
return Err("Values >24h not supported.");
|
||||
};
|
||||
// Filter out double entries.
|
||||
if self.list.iter().any(|a| a.time == time) {
|
||||
return Err("Already exists.");
|
||||
|
@ -208,7 +204,11 @@ impl AlarmRoster {
|
|||
|
||||
// Offset ceiling according to layout information.
|
||||
fn adjust_offset(&mut self, layout: &Layout) {
|
||||
self.offset = self.offset.min(self.list.len().saturating_sub(layout.roster_height as usize));
|
||||
self.offset = self.offset.min(
|
||||
self.list
|
||||
.len()
|
||||
.saturating_sub(layout.roster_height as usize),
|
||||
);
|
||||
}
|
||||
|
||||
// Check for active alarms.
|
||||
|
@ -217,30 +217,38 @@ impl AlarmRoster {
|
|||
}
|
||||
|
||||
pub fn scroll_up(&mut self, layout: &Layout) {
|
||||
let excess = self.list.len().saturating_sub(layout.roster_height as usize);
|
||||
let excess = self
|
||||
.list
|
||||
.len()
|
||||
.saturating_sub(layout.roster_height as usize);
|
||||
self.offset = excess.min(self.offset.saturating_sub(1));
|
||||
}
|
||||
|
||||
pub fn scroll_down(&mut self, layout: &Layout) {
|
||||
let excess = self.list.len().saturating_sub(layout.roster_height as usize);
|
||||
let excess = self
|
||||
.list
|
||||
.len()
|
||||
.saturating_sub(layout.roster_height as usize);
|
||||
self.offset = excess.min(self.offset.saturating_add(1));
|
||||
}
|
||||
|
||||
// Find and process exceeded alarms.
|
||||
pub fn check(&mut self,
|
||||
pub fn check(
|
||||
&mut self,
|
||||
clock: &mut Clock,
|
||||
layout: &Layout,
|
||||
countdown: &mut Countdown,
|
||||
force_redraw: bool,
|
||||
) -> Option<&Alarm>
|
||||
{
|
||||
) -> Option<&Alarm> {
|
||||
let mut ret = None;
|
||||
|
||||
for (index, alarm) in self.list.iter_mut()
|
||||
for (index, alarm) in self
|
||||
.list
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
// Ignore alarms marked exceeded.
|
||||
.filter(|(_, a)| !a.exceeded) {
|
||||
|
||||
.filter(|(_, a)| !a.exceeded)
|
||||
{
|
||||
if alarm.time <= clock.elapsed {
|
||||
// Found alarm to raise.
|
||||
alarm.exceeded = true;
|
||||
|
@ -267,35 +275,41 @@ impl AlarmRoster {
|
|||
stdout: &mut RawTerminal<W>,
|
||||
layout: &mut Layout,
|
||||
config: &Config,
|
||||
) -> Result<(), std::io::Error>
|
||||
{
|
||||
) -> Result<(), std::io::Error> {
|
||||
// Match offset to layout.
|
||||
self.adjust_offset(&layout);
|
||||
|
||||
for (i, alarm) in self.list.iter()
|
||||
.skip(self.offset)
|
||||
.enumerate()
|
||||
{
|
||||
for (i, alarm) in self.list.iter().skip(self.offset).enumerate() {
|
||||
// Add 1 to compensate for the line "[...]".
|
||||
let line = layout.roster.line + i as u16;
|
||||
|
||||
if self.offset > 0 && i == 0 {
|
||||
// Indicate hidden items at top.
|
||||
write!(stdout,
|
||||
write!(
|
||||
stdout,
|
||||
"{}{}{}{}",
|
||||
cursor::Goto(layout.roster.col, line),
|
||||
style::Faint,
|
||||
if config.fancy { "╶╴▲╶╴" } else { "[ ^ ]" },
|
||||
if config.fancy {
|
||||
"╶╴▲╶╴"
|
||||
} else {
|
||||
"[ ^ ]"
|
||||
},
|
||||
style::Reset,
|
||||
)?;
|
||||
continue;
|
||||
} else if i == layout.roster_height as usize {
|
||||
// Indicate hidden items at bottom.
|
||||
write!(stdout,
|
||||
write!(
|
||||
stdout,
|
||||
"{}{}{}{}",
|
||||
cursor::Goto(layout.roster.col, line),
|
||||
style::Faint,
|
||||
if config.fancy { "╶╴▼╶╴" } else { "[ v ]" },
|
||||
if config.fancy {
|
||||
"╶╴▼╶╴"
|
||||
} else {
|
||||
"[ v ]"
|
||||
},
|
||||
style::Reset,
|
||||
)?;
|
||||
break;
|
||||
|
@ -303,7 +317,8 @@ impl AlarmRoster {
|
|||
|
||||
match alarm.exceeded {
|
||||
true if config.fancy => {
|
||||
write!(stdout,
|
||||
write!(
|
||||
stdout,
|
||||
"{}{}{}{} {} {}🭬{}{}",
|
||||
cursor::Goto(layout.roster.col, line),
|
||||
color::Fg(COLOR[alarm.color_index]),
|
||||
|
@ -314,18 +329,20 @@ impl AlarmRoster {
|
|||
color::Fg(color::Reset),
|
||||
style::Reset,
|
||||
)?;
|
||||
},
|
||||
}
|
||||
false if config.fancy => {
|
||||
write!(stdout,
|
||||
write!(
|
||||
stdout,
|
||||
"{}{}█🭬{}{}",
|
||||
cursor::Goto(layout.roster.col, line),
|
||||
color::Fg(COLOR[alarm.color_index]),
|
||||
color::Fg(color::Reset),
|
||||
&alarm.label,
|
||||
)?;
|
||||
},
|
||||
}
|
||||
true => {
|
||||
write!(stdout,
|
||||
write!(
|
||||
stdout,
|
||||
"{}{}{}{} {} {}{}",
|
||||
cursor::Goto(layout.roster.col, line),
|
||||
color::Fg(COLOR[alarm.color_index]),
|
||||
|
@ -335,16 +352,17 @@ impl AlarmRoster {
|
|||
color::Fg(color::Reset),
|
||||
style::Reset,
|
||||
)?;
|
||||
},
|
||||
}
|
||||
false => {
|
||||
write!(stdout,
|
||||
write!(
|
||||
stdout,
|
||||
"{}{} {} {}",
|
||||
cursor::Goto(layout.roster.col, line),
|
||||
color::Bg(COLOR[alarm.color_index]),
|
||||
color::Bg(color::Reset),
|
||||
&alarm.label,
|
||||
)?;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
@ -355,10 +373,16 @@ impl AlarmRoster {
|
|||
let mut width: u16 = 0;
|
||||
for alarm in &self.list {
|
||||
let length = UnicodeWidthStr::width(alarm.label.as_str()) as u16;
|
||||
if length > width { width = length };
|
||||
if length > width {
|
||||
width = length
|
||||
};
|
||||
}
|
||||
// Actual width is 4 columns wider if it's not 0.
|
||||
if width == 0 { 0 } else { width.saturating_add(4) }
|
||||
if width == 0 {
|
||||
0
|
||||
} else {
|
||||
width.saturating_add(4)
|
||||
}
|
||||
}
|
||||
|
||||
// Reset every alarm.
|
||||
|
@ -383,19 +407,14 @@ impl AlarmRoster {
|
|||
}
|
||||
|
||||
// Read alarm times from stdin.
|
||||
pub fn from_stdin(&mut self, stdin: std::io::Stdin)
|
||||
-> Result<(), String>
|
||||
{
|
||||
pub fn from_stdin(&mut self, stdin: std::io::Stdin) -> Result<(), String> {
|
||||
for line in stdin.lock().lines() {
|
||||
match line {
|
||||
Ok(line)
|
||||
if !line.starts_with('#')
|
||||
&& !line.trim().is_empty()
|
||||
=> {
|
||||
Ok(line) if !line.starts_with('#') && !line.trim().is_empty() => {
|
||||
if let Err(e) = self.add(&line) {
|
||||
return Err(format!("Value \"{}\": {}", line, e));
|
||||
}
|
||||
},
|
||||
}
|
||||
Ok(_) => (), // Discard comments and empty lines.
|
||||
Err(e) => return Err(e.to_string()),
|
||||
}
|
||||
|
@ -409,7 +428,12 @@ pub fn exec_command(command: &Vec<String>, elapsed: u32, label: &String) -> Opti
|
|||
let time = if elapsed < 3600 {
|
||||
format!("{:02}:{:02}", elapsed / 60, elapsed % 60)
|
||||
} else {
|
||||
format!("{:02}:{:02}:{:02}", elapsed /3600, (elapsed / 60) % 60, elapsed % 60)
|
||||
format!(
|
||||
"{:02}:{:02}:{:02}",
|
||||
elapsed / 3600,
|
||||
(elapsed / 60) % 60,
|
||||
elapsed % 60
|
||||
)
|
||||
};
|
||||
|
||||
let mut args: Vec<String> = Vec::new();
|
||||
|
@ -423,7 +447,8 @@ pub fn exec_command(command: &Vec<String>, elapsed: u32, label: &String) -> Opti
|
|||
.args(args)
|
||||
.stdout(Stdio::null())
|
||||
.stdin(Stdio::null())
|
||||
.spawn() {
|
||||
.spawn()
|
||||
{
|
||||
Ok(child) => Some(child),
|
||||
Err(error) => {
|
||||
eprintln!("Error: Could not execute command. ({})", error);
|
||||
|
@ -431,4 +456,3 @@ pub fn exec_command(command: &Vec<String>, elapsed: u32, label: &String) -> Opti
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
extern crate unicode_segmentation;
|
||||
|
||||
use std::io::Write;
|
||||
use termion::{clear, cursor, color};
|
||||
use termion::raw::RawTerminal;
|
||||
use crate::layout::Layout;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
use std::io::Write;
|
||||
use termion::raw::RawTerminal;
|
||||
use termion::{clear, color, cursor};
|
||||
use unicode_segmentation::UnicodeSegmentation;
|
||||
use unicode_width::UnicodeWidthStr;
|
||||
|
||||
const PROMPT: &str = "Add alarm: ";
|
||||
|
||||
|
||||
// Input buffer.
|
||||
pub struct Buffer {
|
||||
content: String,
|
||||
|
@ -50,8 +49,7 @@ impl Buffer {
|
|||
pub fn strip_word(&mut self) {
|
||||
// Reset error message.
|
||||
self.message = None;
|
||||
let iter = UnicodeSegmentation::split_word_bound_indices(
|
||||
self.content.as_str().trim_end());
|
||||
let iter = UnicodeSegmentation::split_word_bound_indices(self.content.as_str().trim_end());
|
||||
|
||||
if let Some((index, _)) = iter.last() {
|
||||
self.content.truncate(index);
|
||||
|
@ -78,54 +76,55 @@ impl Buffer {
|
|||
&mut self,
|
||||
stdout: &mut RawTerminal<W>,
|
||||
layout: &mut Layout,
|
||||
) -> Result<(), std::io::Error>
|
||||
{
|
||||
) -> Result<(), std::io::Error> {
|
||||
// Write error message if present and return.
|
||||
if let Some(msg) = self.message {
|
||||
write!(stdout,
|
||||
write!(
|
||||
stdout,
|
||||
"{}{}{}{}{}{}{}",
|
||||
cursor::Hide,
|
||||
cursor::Goto( layout.buffer.col, layout.buffer.line),
|
||||
cursor::Goto(layout.buffer.col, layout.buffer.line),
|
||||
clear::CurrentLine,
|
||||
PROMPT,
|
||||
color::Fg(color::LightRed),
|
||||
&msg,
|
||||
color::Fg(color::Reset))?;
|
||||
color::Fg(color::Reset)
|
||||
)?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
if self.content.is_empty() {
|
||||
// Clear buffer display.
|
||||
write!(stdout,
|
||||
write!(
|
||||
stdout,
|
||||
"{}{}{}",
|
||||
cursor::Goto(layout.buffer.col, layout.buffer.line),
|
||||
clear::CurrentLine,
|
||||
cursor::Hide)?;
|
||||
cursor::Hide
|
||||
)?;
|
||||
} else {
|
||||
// Check if buffer exceeds limits.
|
||||
while UnicodeWidthStr::width(self.content.as_str())
|
||||
+ UnicodeWidthStr::width(PROMPT)
|
||||
while UnicodeWidthStr::width(self.content.as_str()) + UnicodeWidthStr::width(PROMPT)
|
||||
> layout.width as usize
|
||||
{
|
||||
self.content.pop();
|
||||
}
|
||||
|
||||
write!(stdout,
|
||||
write!(
|
||||
stdout,
|
||||
"{}{}{}{}{}",
|
||||
cursor::Goto(layout.buffer.col, layout.buffer.line),
|
||||
clear::CurrentLine,
|
||||
PROMPT,
|
||||
cursor::Show,
|
||||
&self.content)?;
|
||||
&self.content
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Draw error message at input buffer position.
|
||||
pub fn message(
|
||||
&mut self,
|
||||
msg: &'static str,
|
||||
) {
|
||||
pub fn message(&mut self, msg: &'static str) {
|
||||
self.message = Some(msg);
|
||||
}
|
||||
}
|
||||
|
|
72
src/clock.rs
72
src/clock.rs
|
@ -1,12 +1,12 @@
|
|||
pub mod font;
|
||||
|
||||
use std::time;
|
||||
use std::io::Write;
|
||||
use termion::{color, cursor, style};
|
||||
use termion::raw::RawTerminal;
|
||||
use crate::consts::COLOR;
|
||||
use crate::Config;
|
||||
use crate::layout::{Layout, Position};
|
||||
use crate::Config;
|
||||
use std::io::Write;
|
||||
use std::time;
|
||||
use termion::raw::RawTerminal;
|
||||
use termion::{color, cursor, style};
|
||||
|
||||
enum Pause {
|
||||
Instant(time::Instant),
|
||||
|
@ -60,10 +60,10 @@ impl Clock {
|
|||
if let Some(start) = self.start.checked_add(delay.elapsed()) {
|
||||
self.start = start;
|
||||
}
|
||||
},
|
||||
}
|
||||
Pause::Time((secs, _days)) => {
|
||||
self.start = time::Instant::now() - time::Duration::from_secs(secs as u64);
|
||||
},
|
||||
}
|
||||
Pause::None => (), // O_o
|
||||
}
|
||||
|
||||
|
@ -126,8 +126,7 @@ impl Clock {
|
|||
mut stdout: &mut RawTerminal<W>,
|
||||
layout: &Layout,
|
||||
force_redraw: bool,
|
||||
) -> Result<(), std::io::Error>
|
||||
{
|
||||
) -> Result<(), std::io::Error> {
|
||||
// Setup style and color if appropriate.
|
||||
if self.paused {
|
||||
write!(stdout, "{}", style::Faint)?;
|
||||
|
@ -140,17 +139,10 @@ impl Clock {
|
|||
if force_redraw || self.elapsed % 3600 == 0 {
|
||||
// Draw hours if necessary.
|
||||
if self.elapsed >= 3600 {
|
||||
self.draw_digit_pair(
|
||||
&mut stdout,
|
||||
self.elapsed / 3600,
|
||||
&layout.clock_hr,
|
||||
)?;
|
||||
self.draw_digit_pair(&mut stdout, self.elapsed / 3600, &layout.clock_hr)?;
|
||||
|
||||
// Draw colon.
|
||||
self.draw_colon(
|
||||
&mut stdout,
|
||||
&layout.clock_colon1,
|
||||
)?;
|
||||
self.draw_colon(&mut stdout, &layout.clock_colon1)?;
|
||||
}
|
||||
|
||||
// Draw days.
|
||||
|
@ -161,12 +153,10 @@ impl Clock {
|
|||
if self.days == 1 { "DAY" } else { "DAYS" },
|
||||
);
|
||||
|
||||
write!(stdout,
|
||||
write!(
|
||||
stdout,
|
||||
"{}{:>11}",
|
||||
cursor::Goto(
|
||||
layout.clock_days.col,
|
||||
layout.clock_days.line,
|
||||
),
|
||||
cursor::Goto(layout.clock_days.col, layout.clock_days.line,),
|
||||
day_count,
|
||||
)?;
|
||||
}
|
||||
|
@ -174,35 +164,20 @@ impl Clock {
|
|||
|
||||
// Draw minutes if necessary. Once every minute or on request.
|
||||
if force_redraw || self.elapsed % 60 == 0 {
|
||||
self.draw_digit_pair(
|
||||
&mut stdout,
|
||||
(self.elapsed % 3600) / 60,
|
||||
&layout.clock_min,
|
||||
)?;
|
||||
self.draw_digit_pair(&mut stdout, (self.elapsed % 3600) / 60, &layout.clock_min)?;
|
||||
}
|
||||
|
||||
// Draw colon if necessary.
|
||||
if force_redraw {
|
||||
self.draw_colon(
|
||||
&mut stdout,
|
||||
&layout.clock_colon0,
|
||||
)?;
|
||||
self.draw_colon(&mut stdout, &layout.clock_colon0)?;
|
||||
}
|
||||
|
||||
// Draw seconds.
|
||||
self.draw_digit_pair(
|
||||
&mut stdout,
|
||||
self.elapsed % 60,
|
||||
&layout.clock_sec,
|
||||
)?;
|
||||
self.draw_digit_pair(&mut stdout, self.elapsed % 60, &layout.clock_sec)?;
|
||||
|
||||
// Reset color and style.
|
||||
if self.paused || self.color_index != None {
|
||||
write!(stdout,
|
||||
"{}{}",
|
||||
style::NoFaint,
|
||||
color::Fg(color::Reset),
|
||||
)?;
|
||||
write!(stdout, "{}{}", style::NoFaint, color::Fg(color::Reset),)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -212,13 +187,13 @@ impl Clock {
|
|||
stdout: &mut RawTerminal<W>,
|
||||
value: u32,
|
||||
pos: &Position,
|
||||
) -> Result<(), std::io::Error>
|
||||
{
|
||||
) -> Result<(), std::io::Error> {
|
||||
let left = self.font.digits[value as usize / 10].iter();
|
||||
let right = self.font.digits[value as usize % 10].iter();
|
||||
|
||||
for (i, (left, right)) in left.zip(right).enumerate() {
|
||||
write!(stdout,
|
||||
write!(
|
||||
stdout,
|
||||
"{}{} {}",
|
||||
cursor::Goto(pos.col, pos.line + i as u16),
|
||||
left,
|
||||
|
@ -233,9 +208,9 @@ impl Clock {
|
|||
&self,
|
||||
stdout: &mut RawTerminal<W>,
|
||||
pos: &Position,
|
||||
) -> Result<(), std::io::Error>
|
||||
{
|
||||
write!(stdout,
|
||||
) -> Result<(), std::io::Error> {
|
||||
write!(
|
||||
stdout,
|
||||
"{}{}{}{}",
|
||||
cursor::Goto(pos.col, pos.line + 1),
|
||||
self.font.dots.0,
|
||||
|
@ -245,4 +220,3 @@ impl Clock {
|
|||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
const DIGIT_HEIGHT: u16 = 5;
|
||||
|
||||
pub struct Font {
|
||||
|
@ -12,230 +11,262 @@ pub const NORMAL: Font = Font {
|
|||
height: DIGIT_HEIGHT,
|
||||
width: 5,
|
||||
dots: ('■', '■'),
|
||||
digits: [[
|
||||
// 0
|
||||
"█▀▀▀█",
|
||||
"█ █",
|
||||
"█ █",
|
||||
"█ █",
|
||||
"█▄▄▄█",
|
||||
], [
|
||||
// 1
|
||||
" ▀█ ",
|
||||
" █ ",
|
||||
" █ ",
|
||||
" █ ",
|
||||
" █ "
|
||||
], [
|
||||
// 2
|
||||
"▀▀▀▀█",
|
||||
" █",
|
||||
"█▀▀▀▀",
|
||||
"█ ",
|
||||
"█▄▄▄▄"
|
||||
], [
|
||||
// 3
|
||||
"▀▀▀▀█",
|
||||
" █",
|
||||
" ▀▀▀█",
|
||||
" █",
|
||||
"▄▄▄▄█"
|
||||
], [
|
||||
// 4
|
||||
"█ ",
|
||||
"█ █ ",
|
||||
"▀▀▀█▀",
|
||||
" █ ",
|
||||
" █ "
|
||||
], [
|
||||
// 5
|
||||
"█▀▀▀▀",
|
||||
"█ ",
|
||||
"▀▀▀▀█",
|
||||
" █",
|
||||
"▄▄▄▄█"
|
||||
], [
|
||||
// 6
|
||||
"█ ",
|
||||
"█ ",
|
||||
"█▀▀▀█",
|
||||
"█ █",
|
||||
"█▄▄▄█"
|
||||
], [
|
||||
// 7
|
||||
"▀▀▀▀█",
|
||||
" █",
|
||||
" █ ",
|
||||
" █ ",
|
||||
" █ ",
|
||||
], [
|
||||
// 8
|
||||
"█▀▀▀█",
|
||||
"█ █",
|
||||
"█▀▀▀█",
|
||||
"█ █",
|
||||
"█▄▄▄█"
|
||||
], [
|
||||
// 9
|
||||
"█▀▀▀█",
|
||||
"█ █",
|
||||
"▀▀▀▀█",
|
||||
" █",
|
||||
" █"
|
||||
]],
|
||||
digits: [
|
||||
[
|
||||
// 0
|
||||
"█▀▀▀█",
|
||||
"█ █",
|
||||
"█ █",
|
||||
"█ █",
|
||||
"█▄▄▄█",
|
||||
],
|
||||
[
|
||||
// 1
|
||||
" ▀█ ",
|
||||
" █ ",
|
||||
" █ ",
|
||||
" █ ",
|
||||
" █ ",
|
||||
],
|
||||
[
|
||||
// 2
|
||||
"▀▀▀▀█",
|
||||
" █",
|
||||
"█▀▀▀▀",
|
||||
"█ ",
|
||||
"█▄▄▄▄",
|
||||
],
|
||||
[
|
||||
// 3
|
||||
"▀▀▀▀█",
|
||||
" █",
|
||||
" ▀▀▀█",
|
||||
" █",
|
||||
"▄▄▄▄█",
|
||||
],
|
||||
[
|
||||
// 4
|
||||
"█ ",
|
||||
"█ █ ",
|
||||
"▀▀▀█▀",
|
||||
" █ ",
|
||||
" █ ",
|
||||
],
|
||||
[
|
||||
// 5
|
||||
"█▀▀▀▀",
|
||||
"█ ",
|
||||
"▀▀▀▀█",
|
||||
" █",
|
||||
"▄▄▄▄█",
|
||||
],
|
||||
[
|
||||
// 6
|
||||
"█ ",
|
||||
"█ ",
|
||||
"█▀▀▀█",
|
||||
"█ █",
|
||||
"█▄▄▄█",
|
||||
],
|
||||
[
|
||||
// 7
|
||||
"▀▀▀▀█",
|
||||
" █",
|
||||
" █ ",
|
||||
" █ ",
|
||||
" █ ",
|
||||
],
|
||||
[
|
||||
// 8
|
||||
"█▀▀▀█",
|
||||
"█ █",
|
||||
"█▀▀▀█",
|
||||
"█ █",
|
||||
"█▄▄▄█",
|
||||
],
|
||||
[
|
||||
// 9
|
||||
"█▀▀▀█",
|
||||
"█ █",
|
||||
"▀▀▀▀█",
|
||||
" █",
|
||||
" █",
|
||||
],
|
||||
],
|
||||
};
|
||||
|
||||
pub const PLAIN: Font = Font {
|
||||
height: DIGIT_HEIGHT,
|
||||
width: 5,
|
||||
dots: ('█', '█'),
|
||||
digits: [[
|
||||
// 0
|
||||
"█████",
|
||||
"█ █",
|
||||
"█ █",
|
||||
"█ █",
|
||||
"█████"
|
||||
], [
|
||||
// 1
|
||||
" ██ ",
|
||||
" █ ",
|
||||
" █ ",
|
||||
" █ ",
|
||||
" █ "
|
||||
], [
|
||||
// 2
|
||||
"█████",
|
||||
" █",
|
||||
"█████",
|
||||
"█ ",
|
||||
"█████"
|
||||
], [
|
||||
// 3
|
||||
"█████",
|
||||
" █",
|
||||
" ████",
|
||||
" █",
|
||||
"█████"
|
||||
], [
|
||||
// 4
|
||||
"█ ",
|
||||
"█ █ ",
|
||||
"█████",
|
||||
" █ ",
|
||||
" █ "
|
||||
], [
|
||||
// 5
|
||||
"█████",
|
||||
"█ ",
|
||||
"█████",
|
||||
" █",
|
||||
"█████"
|
||||
], [
|
||||
// 6
|
||||
"█ ",
|
||||
"█ ",
|
||||
"█████",
|
||||
"█ █",
|
||||
"█████"
|
||||
], [
|
||||
// 7
|
||||
"█████",
|
||||
" █",
|
||||
" █ ",
|
||||
" █ ",
|
||||
" █ "
|
||||
], [
|
||||
// 8
|
||||
"█████",
|
||||
"█ █",
|
||||
"█████",
|
||||
"█ █",
|
||||
"█████"
|
||||
], [
|
||||
// 9
|
||||
"█████",
|
||||
"█ █",
|
||||
"█████",
|
||||
" █",
|
||||
" █"
|
||||
]],
|
||||
digits: [
|
||||
[
|
||||
// 0
|
||||
"█████",
|
||||
"█ █",
|
||||
"█ █",
|
||||
"█ █",
|
||||
"█████",
|
||||
],
|
||||
[
|
||||
// 1
|
||||
" ██ ",
|
||||
" █ ",
|
||||
" █ ",
|
||||
" █ ",
|
||||
" █ ",
|
||||
],
|
||||
[
|
||||
// 2
|
||||
"█████",
|
||||
" █",
|
||||
"█████",
|
||||
"█ ",
|
||||
"█████",
|
||||
],
|
||||
[
|
||||
// 3
|
||||
"█████",
|
||||
" █",
|
||||
" ████",
|
||||
" █",
|
||||
"█████",
|
||||
],
|
||||
[
|
||||
// 4
|
||||
"█ ",
|
||||
"█ █ ",
|
||||
"█████",
|
||||
" █ ",
|
||||
" █ ",
|
||||
],
|
||||
[
|
||||
// 5
|
||||
"█████",
|
||||
"█ ",
|
||||
"█████",
|
||||
" █",
|
||||
"█████",
|
||||
],
|
||||
[
|
||||
// 6
|
||||
"█ ",
|
||||
"█ ",
|
||||
"█████",
|
||||
"█ █",
|
||||
"█████",
|
||||
],
|
||||
[
|
||||
// 7
|
||||
"█████",
|
||||
" █",
|
||||
" █ ",
|
||||
" █ ",
|
||||
" █ ",
|
||||
],
|
||||
[
|
||||
// 8
|
||||
"█████",
|
||||
"█ █",
|
||||
"█████",
|
||||
"█ █",
|
||||
"█████",
|
||||
],
|
||||
[
|
||||
// 9
|
||||
"█████",
|
||||
"█ █",
|
||||
"█████",
|
||||
" █",
|
||||
" █",
|
||||
],
|
||||
],
|
||||
};
|
||||
|
||||
pub const CHROME: Font = Font {
|
||||
height: DIGIT_HEIGHT,
|
||||
width: 5,
|
||||
dots: ('▄', '🮏'),
|
||||
digits: [[
|
||||
// 0
|
||||
"█▀▀▀█",
|
||||
"█ █",
|
||||
"▀ ▀",
|
||||
"🮐 🮐",
|
||||
"🮐🮏🮏🮏🮐",
|
||||
], [
|
||||
// 1
|
||||
" ▀█ ",
|
||||
" █ ",
|
||||
" ▀ ",
|
||||
" 🮐 ",
|
||||
" 🮐 "
|
||||
], [
|
||||
// 2
|
||||
"▀▀▀▀█",
|
||||
" █",
|
||||
"▀▀▀▀▀",
|
||||
"🮐 ",
|
||||
"🮐🮏🮏🮏🮏"
|
||||
], [
|
||||
// 3
|
||||
"▀▀▀▀█",
|
||||
" █",
|
||||
" ▀▀▀▀",
|
||||
" 🮐",
|
||||
"🮏🮏🮏🮏🮐"
|
||||
], [
|
||||
// 4
|
||||
"█ ",
|
||||
"█ █ ",
|
||||
"▀▀▀▀▀",
|
||||
" 🮐 ",
|
||||
" 🮐 "
|
||||
], [
|
||||
// 5
|
||||
"█▀▀▀▀",
|
||||
"█ ",
|
||||
"▀▀▀▀▀",
|
||||
" 🮐",
|
||||
"🮏🮏🮏🮏🮐"
|
||||
], [
|
||||
// 6
|
||||
"█ ",
|
||||
"█ ",
|
||||
"▀▀▀▀▀",
|
||||
"🮐 🮐",
|
||||
"🮐🮏🮏🮏🮐"
|
||||
], [
|
||||
// 7
|
||||
"▀▀▀▀█",
|
||||
" █",
|
||||
" ▀ ",
|
||||
" 🮐 ",
|
||||
" 🮐 ",
|
||||
], [
|
||||
// 8
|
||||
"█▀▀▀█",
|
||||
"█ █",
|
||||
"▀▀▀▀▀",
|
||||
"🮐 🮐",
|
||||
"🮐🮏🮏🮏🮐"
|
||||
], [
|
||||
// 9
|
||||
"█▀▀▀█",
|
||||
"█ █",
|
||||
"▀▀▀▀▀",
|
||||
" 🮐",
|
||||
" 🮐"
|
||||
]],
|
||||
digits: [
|
||||
[
|
||||
// 0
|
||||
"█▀▀▀█",
|
||||
"█ █",
|
||||
"▀ ▀",
|
||||
"🮐 🮐",
|
||||
"🮐🮏🮏🮏🮐",
|
||||
],
|
||||
[
|
||||
// 1
|
||||
" ▀█ ",
|
||||
" █ ",
|
||||
" ▀ ",
|
||||
" 🮐 ",
|
||||
" 🮐 ",
|
||||
],
|
||||
[
|
||||
// 2
|
||||
"▀▀▀▀█",
|
||||
" █",
|
||||
"▀▀▀▀▀",
|
||||
"🮐 ",
|
||||
"🮐🮏🮏🮏🮏",
|
||||
],
|
||||
[
|
||||
// 3
|
||||
"▀▀▀▀█",
|
||||
" █",
|
||||
" ▀▀▀▀",
|
||||
" 🮐",
|
||||
"🮏🮏🮏🮏🮐",
|
||||
],
|
||||
[
|
||||
// 4
|
||||
"█ ",
|
||||
"█ █ ",
|
||||
"▀▀▀▀▀",
|
||||
" 🮐 ",
|
||||
" 🮐 ",
|
||||
],
|
||||
[
|
||||
// 5
|
||||
"█▀▀▀▀",
|
||||
"█ ",
|
||||
"▀▀▀▀▀",
|
||||
" 🮐",
|
||||
"🮏🮏🮏🮏🮐",
|
||||
],
|
||||
[
|
||||
// 6
|
||||
"█ ",
|
||||
"█ ",
|
||||
"▀▀▀▀▀",
|
||||
"🮐 🮐",
|
||||
"🮐🮏🮏🮏🮐",
|
||||
],
|
||||
[
|
||||
// 7
|
||||
"▀▀▀▀█",
|
||||
" █",
|
||||
" ▀ ",
|
||||
" 🮐 ",
|
||||
" 🮐 ",
|
||||
],
|
||||
[
|
||||
// 8
|
||||
"█▀▀▀█",
|
||||
"█ █",
|
||||
"▀▀▀▀▀",
|
||||
"🮐 🮐",
|
||||
"🮐🮏🮏🮏🮐",
|
||||
],
|
||||
[
|
||||
// 9
|
||||
"█▀▀▀█",
|
||||
"█ █",
|
||||
"▀▀▀▀▀",
|
||||
" 🮐",
|
||||
" 🮐",
|
||||
],
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
pub const COLOR: [&dyn termion::color::Color; 6] = [
|
||||
&termion::color::LightGreen,
|
||||
&termion::color::LightYellow,
|
||||
|
@ -14,8 +13,10 @@ pub const LABEL_SIZE_LIMIT: usize = 32;
|
|||
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]]
|
||||
pub const USAGE: &str = concat!(
|
||||
"USAGE: ",
|
||||
env!("CARGO_PKG_NAME"),
|
||||
" [-h|-v] [-e|--exec COMMAND] [-p] [-q] [ALARM[/LABEL]]
|
||||
|
||||
PARAMETERS:
|
||||
[ALARM TIME[/LABEL]] Any number of alarm times (HH:MM:SS) with optional
|
||||
|
@ -32,14 +33,13 @@ OPTIONS:
|
|||
-q, --quit Quit program after last alarm.
|
||||
|
||||
SIGNALS: <SIGUSR1> Reset clock.
|
||||
<SIGUSR2> Pause or un-pause clock.");
|
||||
<SIGUSR2> Pause or un-pause clock."
|
||||
);
|
||||
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 =
|
||||
"[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 =
|
||||
"Format: HH:MM:SS/LABEL [ENTER] Accept [ESC] Cancel [CTR-C] Quit";
|
||||
pub const MENUBAR_PAUSED: &str =
|
||||
"[SPACE] Continue [r] Reset [UP]/[DOWN] Set clock";
|
||||
"Format: HH:MM:SS/LABEL [ENTER] Accept [ESC] Cancel [CTR-C] Quit";
|
||||
pub const MENUBAR_PAUSED: &str = "[SPACE] Continue [r] Reset [UP]/[DOWN] Set clock";
|
||||
}
|
||||
|
||||
|
|
|
@ -8,13 +8,13 @@ pub struct Position {
|
|||
impl Position {
|
||||
// Terminal positions are 1-based.
|
||||
pub fn new() -> Position {
|
||||
Position {col: 1, line: 1}
|
||||
Position { col: 1, line: 1 }
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Layout {
|
||||
pub force_redraw: bool, // Redraw elements on screen.
|
||||
force_recalc: bool, // Recalculate position of elements.
|
||||
force_recalc: bool, // Recalculate position of elements.
|
||||
pub width: u16,
|
||||
pub height: u16,
|
||||
clock_width: u16,
|
||||
|
@ -48,7 +48,7 @@ impl Layout {
|
|||
clock_colon1: Position::new(),
|
||||
clock_hr: Position::new(),
|
||||
clock_days: Position::new(),
|
||||
roster: Position {col: 1, line: 3},
|
||||
roster: Position { col: 1, line: 3 },
|
||||
roster_width: 0,
|
||||
roster_height: 0,
|
||||
buffer: Position::new(),
|
||||
|
@ -56,9 +56,7 @@ impl Layout {
|
|||
}
|
||||
|
||||
// Update layout. Returns true when changes were made.
|
||||
pub fn update(&mut self, clock: &Clock, force: bool)
|
||||
-> Result<bool, std::io::Error>
|
||||
{
|
||||
pub fn update(&mut self, clock: &Clock, force: bool) -> Result<bool, std::io::Error> {
|
||||
if self.force_recalc || force {
|
||||
self.force_recalc = false;
|
||||
let (width, height) = termion::terminal_size()?;
|
||||
|
@ -79,13 +77,7 @@ impl Layout {
|
|||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub fn test_update(
|
||||
&mut self,
|
||||
clock: &Clock,
|
||||
width: u16,
|
||||
height: u16,
|
||||
roster_width: u16,
|
||||
) {
|
||||
pub fn test_update(&mut self, clock: &Clock, width: u16, height: u16, roster_width: u16) {
|
||||
self.width = width;
|
||||
self.height = height;
|
||||
self.clock_width = clock.get_width();
|
||||
|
@ -104,7 +96,9 @@ impl Layout {
|
|||
// terminal.
|
||||
fn compute(&mut self, display_hours: bool) {
|
||||
// Prevent integer overflow at very low screen sizes.
|
||||
if self.width < self.clock_width || self.height < self.clock_height { return; }
|
||||
if self.width < self.clock_width || self.height < self.clock_height {
|
||||
return;
|
||||
}
|
||||
|
||||
let middle: u16 = self.height / 2 - 1;
|
||||
|
||||
|
@ -163,4 +157,3 @@ impl Layout {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
197
src/lib.rs
197
src/lib.rs
|
@ -1,36 +1,34 @@
|
|||
extern crate termion;
|
||||
extern crate signal_hook;
|
||||
extern crate termion;
|
||||
pub mod alarm;
|
||||
mod buffer;
|
||||
pub mod clock;
|
||||
pub mod consts;
|
||||
pub mod layout;
|
||||
pub mod utils;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
pub mod utils;
|
||||
|
||||
use std::{env, process, thread, time};
|
||||
use std::io::Write;
|
||||
pub use alarm::AlarmRoster;
|
||||
use alarm::{exec_command, Countdown};
|
||||
use buffer::Buffer;
|
||||
use clock::{font, Clock};
|
||||
pub use consts::ui::*;
|
||||
use layout::Layout;
|
||||
use signal_hook::consts::signal::*;
|
||||
use signal_hook::iterator::Signals;
|
||||
use signal_hook::low_level;
|
||||
use termion::{clear, cursor, style};
|
||||
use termion::raw::{IntoRawMode, RawTerminal};
|
||||
use std::io::Write;
|
||||
use std::{env, process, thread, time};
|
||||
use termion::event::Key;
|
||||
use termion::input::TermRead;
|
||||
use buffer::Buffer;
|
||||
use clock::{Clock, font};
|
||||
use layout::Layout;
|
||||
use alarm::{Countdown, exec_command};
|
||||
pub use alarm::AlarmRoster;
|
||||
pub use consts::ui::*;
|
||||
|
||||
use termion::raw::{IntoRawMode, RawTerminal};
|
||||
use termion::{clear, cursor, style};
|
||||
|
||||
pub fn run(
|
||||
config: Config,
|
||||
mut alarm_roster: AlarmRoster,
|
||||
) -> Result<Option<process::Child>, std::io::Error>
|
||||
{
|
||||
) -> Result<Option<process::Child>, std::io::Error> {
|
||||
let mut layout = Layout::new();
|
||||
// Initialise roster_width.
|
||||
layout.set_roster_width(alarm_roster.width());
|
||||
|
@ -47,13 +45,7 @@ pub fn run(
|
|||
|
||||
// Register signals.
|
||||
let mut signals = Signals::new(&[
|
||||
SIGTSTP,
|
||||
SIGCONT,
|
||||
SIGWINCH,
|
||||
SIGTERM,
|
||||
SIGINT,
|
||||
SIGUSR1,
|
||||
SIGUSR2,
|
||||
SIGTSTP, SIGCONT, SIGWINCH, SIGTERM, SIGINT, SIGUSR1, SIGUSR2,
|
||||
])?;
|
||||
|
||||
// Main loop entry.
|
||||
|
@ -67,7 +59,7 @@ pub fn run(
|
|||
SIGCONT => {
|
||||
restore_after_suspend(&mut stdout)?;
|
||||
force_redraw = true;
|
||||
},
|
||||
}
|
||||
SIGWINCH => layout.schedule_recalc(),
|
||||
// Exit main loop on SIGTERM and SIGINT.
|
||||
SIGTERM | SIGINT => break 'outer,
|
||||
|
@ -77,12 +69,12 @@ pub fn run(
|
|||
alarm_roster.reset_all();
|
||||
layout.schedule_recalc();
|
||||
force_redraw = true;
|
||||
},
|
||||
}
|
||||
// (Un-)Pause clock on SIGUSR2.
|
||||
SIGUSR2 => {
|
||||
clock.toggle();
|
||||
force_redraw = true;
|
||||
},
|
||||
}
|
||||
// We didn't register anything else.
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
@ -90,16 +82,16 @@ pub fn run(
|
|||
|
||||
// Update elapsed time.
|
||||
let elapsed = if clock.paused {
|
||||
clock.elapsed
|
||||
} else {
|
||||
// Should never overflow as we reestablish a new "start"
|
||||
// instant every 24 hours.
|
||||
clock.start.elapsed().as_secs() as u32
|
||||
};
|
||||
clock.elapsed
|
||||
} else {
|
||||
// Should never overflow as we reestablish a new "start"
|
||||
// instant every 24 hours.
|
||||
clock.start.elapsed().as_secs() as u32
|
||||
};
|
||||
|
||||
// Conditional inner loop. Runs once every second or when explicitly
|
||||
// requested.
|
||||
if elapsed != clock.elapsed || force_redraw {
|
||||
if elapsed != clock.elapsed || force_redraw {
|
||||
// Update clock. Advance one day after 24 hours.
|
||||
if elapsed < 24 * 60 * 60 {
|
||||
clock.elapsed = elapsed;
|
||||
|
@ -127,25 +119,25 @@ pub fn run(
|
|||
Ok(Some(status)) if status.success() => child = None,
|
||||
// Abnormal exit.
|
||||
Ok(Some(status)) => {
|
||||
eprintln!("Spawned process terminated with non-zero exit status. ({})", status);
|
||||
eprintln!(
|
||||
"Spawned process terminated with non-zero exit status. ({})",
|
||||
status
|
||||
);
|
||||
child = None;
|
||||
},
|
||||
}
|
||||
// Process is still running.
|
||||
Ok(None) => (),
|
||||
// Other error.
|
||||
Err(error) => {
|
||||
eprintln!("Error executing command. ({})", error);
|
||||
child = None;
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for exceeded alarms.
|
||||
if let Some(alarm) = alarm_roster.check(
|
||||
&mut clock,
|
||||
&layout,
|
||||
&mut countdown,
|
||||
force_redraw)
|
||||
if let Some(alarm) =
|
||||
alarm_roster.check(&mut clock, &layout, &mut countdown, force_redraw)
|
||||
{
|
||||
// Do not react to exceeded alarms if the clock is paused.
|
||||
if !clock.paused {
|
||||
|
@ -158,13 +150,17 @@ pub fn run(
|
|||
// Run command if configured and no command is running.
|
||||
Some(ref command) if child.is_none() => {
|
||||
child = exec_command(command, alarm.time, &alarm.label);
|
||||
},
|
||||
}
|
||||
// Last command is still running.
|
||||
Some(_) => eprintln!("Not executing command, as its predecessor is still running"),
|
||||
Some(_) => {
|
||||
eprintln!("Not executing command, as its predecessor is still running")
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
// Quit if configured.
|
||||
if config.quit && alarm_roster.idle() { break };
|
||||
if config.quit && alarm_roster.idle() {
|
||||
break;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,7 +168,8 @@ pub fn run(
|
|||
// requested.
|
||||
if force_redraw {
|
||||
// Write menu at the top.
|
||||
write!(stdout,
|
||||
write!(
|
||||
stdout,
|
||||
"{}{}{}{}{}",
|
||||
cursor::Goto(1, 1),
|
||||
style::Faint,
|
||||
|
@ -189,7 +186,8 @@ pub fn run(
|
|||
_ => " ",
|
||||
},
|
||||
clear::AfterCursor,
|
||||
style::NoFaint)?;
|
||||
style::NoFaint
|
||||
)?;
|
||||
|
||||
// Redraw list of alarms.
|
||||
alarm_roster.draw(&mut stdout, &mut layout, &config)?;
|
||||
|
@ -234,13 +232,13 @@ pub fn run(
|
|||
buffer.visible = false;
|
||||
force_redraw = true;
|
||||
}
|
||||
},
|
||||
}
|
||||
// Escape and ^U clear input buffer.
|
||||
Key::Esc | Key::Ctrl('u') => {
|
||||
buffer.reset();
|
||||
buffer.visible = false;
|
||||
force_redraw = true;
|
||||
},
|
||||
}
|
||||
// ^W removes last word.
|
||||
Key::Ctrl('w') => {
|
||||
buffer.strip_word();
|
||||
|
@ -248,7 +246,7 @@ pub fn run(
|
|||
buffer.visible = false;
|
||||
force_redraw = true;
|
||||
}
|
||||
},
|
||||
}
|
||||
// Backspace.
|
||||
Key::Backspace => {
|
||||
// Delete last char in buffer.
|
||||
|
@ -257,7 +255,7 @@ pub fn run(
|
|||
buffer.visible = false;
|
||||
force_redraw = true;
|
||||
}
|
||||
},
|
||||
}
|
||||
// Set clock.
|
||||
Key::Up if clock.paused => {
|
||||
clock.shift(10);
|
||||
|
@ -267,43 +265,43 @@ pub fn run(
|
|||
// here.
|
||||
layout.schedule_recalc();
|
||||
force_redraw = true;
|
||||
},
|
||||
}
|
||||
Key::Down if clock.paused => {
|
||||
clock.shift(-10);
|
||||
alarm_roster.time_travel(&mut clock);
|
||||
layout.schedule_recalc();
|
||||
force_redraw = true;
|
||||
},
|
||||
}
|
||||
// Scroll alarm roster.
|
||||
Key::PageUp => {
|
||||
alarm_roster.scroll_up(&layout);
|
||||
force_redraw = true;
|
||||
},
|
||||
}
|
||||
Key::PageDown => {
|
||||
alarm_roster.scroll_down(&layout);
|
||||
force_redraw = true;
|
||||
},
|
||||
}
|
||||
// Forward every char if in insert mode.
|
||||
Key::Char(c) if buffer.visible => {
|
||||
buffer.push(c);
|
||||
},
|
||||
}
|
||||
// Reset clock on 'r'.
|
||||
Key::Char('r') => {
|
||||
clock.reset();
|
||||
alarm_roster.reset_all();
|
||||
layout.schedule_recalc();
|
||||
force_redraw = true;
|
||||
},
|
||||
}
|
||||
// (Un-)Pause on space.
|
||||
Key::Char(' ') => {
|
||||
clock.toggle();
|
||||
force_redraw = true;
|
||||
},
|
||||
}
|
||||
// Clear clock color on 'c'.
|
||||
Key::Char('c') => {
|
||||
clock.color_index = None;
|
||||
force_redraw = true;
|
||||
},
|
||||
}
|
||||
// Delete last alarm on 'd'.
|
||||
Key::Char('d') => {
|
||||
if alarm_roster.pop().is_some() {
|
||||
|
@ -313,7 +311,7 @@ pub fn run(
|
|||
countdown.reset();
|
||||
force_redraw = true;
|
||||
}
|
||||
},
|
||||
}
|
||||
// Exit on q and ^C.
|
||||
Key::Char('q') | Key::Ctrl('c') => break,
|
||||
// Force redraw on ^R.
|
||||
|
@ -327,7 +325,7 @@ pub fn run(
|
|||
force_redraw = true;
|
||||
// Jump to the start of the main loop.
|
||||
continue;
|
||||
},
|
||||
}
|
||||
Key::Char(c) => {
|
||||
if c.is_ascii_digit() {
|
||||
buffer.push(c);
|
||||
|
@ -336,7 +334,7 @@ pub fn run(
|
|||
} else if !buffer.is_empty() && c == ':' {
|
||||
buffer.push(':');
|
||||
}
|
||||
},
|
||||
}
|
||||
// Any other key.
|
||||
_ => (),
|
||||
}
|
||||
|
@ -347,11 +345,7 @@ pub fn run(
|
|||
}
|
||||
|
||||
// Main loop exited. Clear screen and restore cursor.
|
||||
write!(stdout,
|
||||
"{}{}{}",
|
||||
clear::All,
|
||||
cursor::Restore,
|
||||
cursor::Show)?;
|
||||
write!(stdout, "{}{}{}", clear::All, cursor::Restore, cursor::Show)?;
|
||||
stdout.flush()?;
|
||||
|
||||
Ok(child)
|
||||
|
@ -366,9 +360,7 @@ pub struct Config {
|
|||
|
||||
impl Config {
|
||||
// Parse command line arguments into "config".
|
||||
pub fn new(args: env::Args, alarm_roster: &mut AlarmRoster)
|
||||
-> Result<Config, String>
|
||||
{
|
||||
pub fn new(args: env::Args, alarm_roster: &mut AlarmRoster) -> Result<Config, String> {
|
||||
let mut config = Config {
|
||||
quit: false,
|
||||
fancy: false,
|
||||
|
@ -384,16 +376,16 @@ impl Config {
|
|||
// Print usage information and exit
|
||||
println!("{}", USAGE);
|
||||
process::exit(0);
|
||||
},
|
||||
}
|
||||
"-v" | "--version" => {
|
||||
println!("{} {}", NAME, VERSION);
|
||||
process::exit(0);
|
||||
},
|
||||
}
|
||||
"-p" | "--plain" => config.font = &font::PLAIN,
|
||||
"-f" | "--fancy" => {
|
||||
config.fancy = true;
|
||||
config.font = &font::CHROME;
|
||||
},
|
||||
}
|
||||
"-q" | "--quit" => config.quit = true,
|
||||
"-e" | "--exec" => {
|
||||
if let Some(e) = iter.next() {
|
||||
|
@ -401,19 +393,21 @@ impl Config {
|
|||
} else {
|
||||
return Err(format!("Missing parameter to \"{}\".", arg));
|
||||
}
|
||||
},
|
||||
}
|
||||
any if any.starts_with('-') => {
|
||||
// Unrecognized flag.
|
||||
return Err(format!("Unrecognized option: \"{}\"\nUse \"-h\" or \"--help\" for a list of valid command line options.", any));
|
||||
},
|
||||
}
|
||||
any => {
|
||||
// Alarm to add.
|
||||
if let Err(error) = alarm_roster.add(&String::from(any)) {
|
||||
return Err(format!("Error adding \"{}\" as alarm. ({})", any, error));
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
} else { break; } // All command line parameters processed.
|
||||
} else {
|
||||
break;
|
||||
} // All command line parameters processed.
|
||||
}
|
||||
Ok(config)
|
||||
}
|
||||
|
@ -432,23 +426,27 @@ impl Config {
|
|||
// Next char is escaped. (If not escaped itself.)
|
||||
escaped = true;
|
||||
continue;
|
||||
},
|
||||
}
|
||||
// Keep spaces when escaped or quoted.
|
||||
' ' if escaped || quoted => { &segment.push(' '); },
|
||||
' ' if escaped || quoted => {
|
||||
&segment.push(' ');
|
||||
}
|
||||
// Otherwise end the current segment.
|
||||
' ' => {
|
||||
if !&segment.is_empty() {
|
||||
command.push(segment.clone());
|
||||
&segment.clear();
|
||||
}
|
||||
},
|
||||
}
|
||||
// Quotation marks toggle quote.
|
||||
'"' | '\'' if !escaped => quoted = !quoted,
|
||||
// Carry everything else. Escape if found escaped.
|
||||
_ => {
|
||||
if escaped { &segment.push('\\'); }
|
||||
if escaped {
|
||||
&segment.push('\\');
|
||||
}
|
||||
&segment.push(c);
|
||||
},
|
||||
}
|
||||
}
|
||||
escaped = false;
|
||||
}
|
||||
|
@ -459,19 +457,21 @@ impl Config {
|
|||
}
|
||||
|
||||
// Prepare to suspend execution. Called on SIGTSTP.
|
||||
fn suspend<W: Write>(stdout: &mut RawTerminal<W>)
|
||||
-> Result<(), std::io::Error>
|
||||
{
|
||||
write!(stdout,
|
||||
fn suspend<W: Write>(stdout: &mut RawTerminal<W>) -> Result<(), std::io::Error> {
|
||||
write!(
|
||||
stdout,
|
||||
"{}{}{}",
|
||||
cursor::Goto(1,1),
|
||||
cursor::Goto(1, 1),
|
||||
clear::AfterCursor,
|
||||
cursor::Show)?;
|
||||
cursor::Show
|
||||
)?;
|
||||
stdout.flush()?;
|
||||
stdout.suspend_raw_mode()
|
||||
.unwrap_or_else(|error| {
|
||||
eprintln!("Failed to leave raw terminal mode prior to suspend: {}", error);
|
||||
});
|
||||
stdout.suspend_raw_mode().unwrap_or_else(|error| {
|
||||
eprintln!(
|
||||
"Failed to leave raw terminal mode prior to suspend: {}",
|
||||
error
|
||||
);
|
||||
});
|
||||
|
||||
if let Err(error) = low_level::emulate_default_handler(SIGTSTP as i32) {
|
||||
eprintln!("Error raising SIGTSTP: {}", error);
|
||||
|
@ -482,14 +482,13 @@ fn suspend<W: Write>(stdout: &mut RawTerminal<W>)
|
|||
}
|
||||
|
||||
// Set up terminal after SIGTSTP or SIGSTOP.
|
||||
fn restore_after_suspend<W: Write>(stdout: &mut RawTerminal<W>)
|
||||
-> Result<(), std::io::Error>
|
||||
{
|
||||
stdout.activate_raw_mode()
|
||||
.unwrap_or_else(|error| {
|
||||
eprintln!("Failed to re-enter raw terminal mode after suspend: {}", error);
|
||||
process::exit(1);
|
||||
});
|
||||
fn restore_after_suspend<W: Write>(stdout: &mut RawTerminal<W>) -> Result<(), std::io::Error> {
|
||||
stdout.activate_raw_mode().unwrap_or_else(|error| {
|
||||
eprintln!(
|
||||
"Failed to re-enter raw terminal mode after suspend: {}",
|
||||
error
|
||||
);
|
||||
process::exit(1);
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
21
src/main.rs
21
src/main.rs
|
@ -1,16 +1,14 @@
|
|||
use kitchentimer::{run, AlarmRoster, Config};
|
||||
use std::{env, process};
|
||||
use kitchentimer::{Config, AlarmRoster, run};
|
||||
|
||||
|
||||
fn main() {
|
||||
let args = env::args();
|
||||
let mut alarm_roster = AlarmRoster::new();
|
||||
// Parse command line arguments into config and alarm roster.
|
||||
let config = Config::new(args, &mut alarm_roster)
|
||||
.unwrap_or_else(|e| {
|
||||
eprintln!("{}", e);
|
||||
process::exit(1);
|
||||
});
|
||||
let config = Config::new(args, &mut alarm_roster).unwrap_or_else(|e| {
|
||||
eprintln!("{}", e);
|
||||
process::exit(1);
|
||||
});
|
||||
|
||||
// Read alarm times from stdin if stdin is not a tty.
|
||||
let stdin = std::io::stdin();
|
||||
|
@ -27,12 +25,15 @@ fn main() {
|
|||
Err(error) => {
|
||||
eprintln!("Main loop exited with error: {}", error);
|
||||
process::exit(1);
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
// Wait for remaining spawned process to exit.
|
||||
if let Some(mut child) = child {
|
||||
eprint!("Waiting for spawned process (PID {}) to finish ...", child.id());
|
||||
eprint!(
|
||||
"Waiting for spawned process (PID {}) to finish ...",
|
||||
child.id()
|
||||
);
|
||||
|
||||
match child.wait() {
|
||||
Ok(status) if status.success() => eprintln!(" ok"),
|
||||
|
@ -43,5 +44,3 @@ fn main() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
use crate::layout::Layout;
|
||||
use crate::clock::Clock;
|
||||
use crate::layout::Layout;
|
||||
use crate::Config;
|
||||
|
||||
fn default_config() -> Config {
|
||||
|
|
|
@ -6,8 +6,7 @@ pub fn grapheme_truncate(input: &mut String, limit: usize, ellipse: char) {
|
|||
Some((i, _)) => {
|
||||
input.truncate(i);
|
||||
input.push(ellipse);
|
||||
},
|
||||
}
|
||||
None => (),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue