2020-01-25 12:11:19 +01:00
use super ::todolist ::TodoList ;
2020-01-25 22:15:41 +01:00
use super ::timesheet ::TimeSheet ;
2020-01-25 12:11:19 +01:00
use std ::default ::Default ;
2020-01-25 18:05:35 +01:00
use std ::io ::{ self , Write } ;
2020-01-25 12:11:19 +01:00
use termion ::event ::Key ;
use termion ::input ::TermRead ;
use tui ::backend ::TermionBackend ;
2020-01-25 18:05:35 +01:00
use tui ::layout ::* ;
2020-01-25 12:11:19 +01:00
use tui ::style ::{ Color , Style } ;
use tui ::widgets ::* ;
type Terminal = tui ::Terminal < TermionBackend < termion ::raw ::RawTerminal < io ::Stdout > > > ;
2020-01-25 12:22:36 +01:00
const JSON_PATH : & str = " tracc.json " ;
2020-01-25 12:11:19 +01:00
pub enum Mode {
Insert ,
Normal ,
2020-01-23 23:05:40 +01:00
}
2020-01-25 22:15:41 +01:00
#[ derive(PartialEq) ]
enum Focus {
Top ,
Bottom ,
}
2020-01-25 12:11:19 +01:00
pub struct Tracc {
todos : TodoList ,
2020-01-25 22:15:41 +01:00
times : TimeSheet ,
2020-01-25 12:11:19 +01:00
terminal : Terminal ,
input_mode : Mode ,
2020-01-25 22:15:41 +01:00
focus : Focus ,
2020-01-24 00:02:32 +01:00
}
2020-01-23 23:05:40 +01:00
impl Tracc {
2020-01-25 12:11:19 +01:00
pub fn new ( terminal : Terminal ) -> Self {
2020-01-23 23:05:40 +01:00
Self {
2020-01-25 12:22:36 +01:00
todos : TodoList ::open_or_create ( JSON_PATH ) ,
2020-01-25 22:15:41 +01:00
times : TimeSheet ::new ( ) ,
2020-01-25 12:11:19 +01:00
terminal ,
input_mode : Mode ::Normal ,
2020-01-25 22:15:41 +01:00
focus : Focus ::Top ,
2020-01-24 11:07:37 +01:00
}
2020-01-23 23:05:40 +01:00
}
2020-01-25 12:11:19 +01:00
pub fn run ( & mut self ) -> Result < ( ) , io ::Error > {
let mut inputs = io ::stdin ( ) . keys ( ) ;
loop {
2020-01-25 22:15:41 +01:00
self . refresh ( ) ? ;
2020-01-25 12:11:19 +01:00
// I need to find a better way to handle inputs. This is awful.
let input = inputs . next ( ) . unwrap ( ) ? ;
match self . input_mode {
Mode ::Normal = > match input {
2020-01-25 12:22:36 +01:00
Key ::Char ( 'q' ) = > break ,
2020-01-25 12:11:19 +01:00
Key ::Char ( 'j' ) = > self . todos . selection_down ( ) ,
Key ::Char ( 'k' ) = > self . todos . selection_up ( ) ,
Key ::Char ( 'o' ) = > {
2020-01-27 12:06:05 +01:00
self . todos . insert ( Default ::default ( ) , None ) ;
2020-01-25 12:11:19 +01:00
self . set_mode ( Mode ::Insert ) ? ;
}
Key ::Char ( 'a' ) | Key ::Char ( 'A' ) = > self . set_mode ( Mode ::Insert ) ? ,
Key ::Char ( ' ' ) = > self . todos . toggle_current ( ) ,
// dd
Key ::Char ( 'd' ) = > {
if let Key ::Char ( 'd' ) = inputs . next ( ) . unwrap ( ) ? {
2020-01-27 12:06:05 +01:00
self . todos . remove_current ( )
2020-01-25 12:11:19 +01:00
}
}
2020-01-27 12:06:05 +01:00
Key ::Char ( 'p' ) = > self . todos . paste ( ) ,
2020-01-25 22:15:41 +01:00
Key ::Char ( '\t' ) = > {
self . focus = match self . focus {
Focus ::Top = > Focus ::Bottom ,
Focus ::Bottom = > Focus ::Top ,
}
}
2020-01-25 12:11:19 +01:00
_ = > ( ) ,
} ,
Mode ::Insert = > match input {
Key ::Char ( '\n' ) | Key ::Esc = > self . set_mode ( Mode ::Normal ) ? ,
2020-01-27 12:06:05 +01:00
Key ::Backspace = > self . todos . backspace ( ) ,
2020-01-25 12:11:19 +01:00
Key ::Char ( x ) = > self . todos . append_to_current ( x ) ,
_ = > ( ) ,
} ,
} ;
2020-01-24 11:07:37 +01:00
}
2020-01-25 12:22:36 +01:00
self . terminal . clear ( ) ? ;
persist_todos ( & self . todos , JSON_PATH ) ;
2020-01-25 12:11:19 +01:00
Ok ( ( ) )
2020-01-23 23:05:40 +01:00
}
2020-01-25 12:11:19 +01:00
fn set_mode ( & mut self , mode : Mode ) -> Result < ( ) , io ::Error > {
2020-01-23 23:05:40 +01:00
match mode {
2020-01-25 12:11:19 +01:00
Mode ::Insert = > self . terminal . show_cursor ( ) ? ,
2020-01-23 23:05:40 +01:00
Mode ::Normal = > {
2020-01-25 12:11:19 +01:00
self . todos . normal_mode ( ) ;
self . terminal . hide_cursor ( ) ?
2020-01-24 00:02:32 +01:00
}
2020-01-23 23:05:40 +01:00
} ;
2020-01-25 12:11:19 +01:00
self . input_mode = mode ;
2020-01-23 23:05:40 +01:00
Ok ( ( ) )
}
2020-01-25 22:15:41 +01:00
fn refresh ( & mut self ) -> Result < ( ) , io ::Error > {
fn selectable_list < ' a , C : AsRef < str > > (
title : & ' a str ,
content : & ' a [ C ] ,
selected : Option < usize > ,
) -> SelectableList < ' a > {
SelectableList ::default ( )
. block (
Block ::default ( )
. title ( title )
. borders ( Borders ::TOP | Borders ::RIGHT | Borders ::LEFT ) ,
)
. items ( content )
. select ( selected . into ( ) )
. highlight_style ( Style ::default ( ) . fg ( Color ::LightGreen ) )
. highlight_symbol ( " > " )
}
let printable_todos = self . todos . printable ( ) ;
let top_focus = Some ( self . todos . selected ) . filter ( | _ | self . focus = = Focus ::Top ) ;
let printable_times = self . times . printable ( ) ;
let bottom_focus = Some ( self . times . selected ) . filter ( | _ | self . focus = = Focus ::Bottom ) ;
2020-01-25 18:05:35 +01:00
2020-01-25 22:15:41 +01:00
self . terminal . draw ( | mut frame | {
let size = frame . size ( ) ;
let chunks = Layout ::default ( )
. direction ( Direction ::Vertical )
. constraints (
[
Constraint ::Percentage ( 42 ) ,
Constraint ::Percentage ( 42 ) ,
Constraint ::Percentage ( 16 ) ,
]
. as_ref ( ) ,
)
. split ( size ) ;
selectable_list ( " t r a c c " , & printable_todos , top_focus )
. render ( & mut frame , chunks [ 0 ] ) ;
selectable_list ( " 🕑 " , & printable_times , bottom_focus )
. render ( & mut frame , chunks [ 1 ] ) ;
Paragraph ::new ( [ Text ::raw ( " Sum for today: 1:12 " ) ] . iter ( ) )
. block ( Block ::default ( ) . borders ( Borders ::ALL ) )
. render ( & mut frame , chunks [ 2 ] ) ;
} ) ? ;
Ok ( ( ) )
}
2020-01-23 23:05:40 +01:00
}
2020-01-25 12:22:36 +01:00
fn persist_todos ( todos : & TodoList , path : & str ) {
let string = serde_json ::to_string ( & todos . todos ) . unwrap ( ) ;
std ::fs ::OpenOptions ::new ( )
. create ( true )
. write ( true )
. truncate ( true )
. open ( path )
. ok ( )
. or_else ( | | panic! ( " Can’t save todos to JSON. Dumping raw data: \n {} " , string ) )
. map ( | mut f | f . write ( string . as_bytes ( ) ) ) ;
}
2020-01-27 12:06:05 +01:00
pub trait ListView < T : std ::fmt ::Display > {
fn printable ( & self ) -> Vec < String > ;
fn selection_up ( & mut self ) ;
fn selection_down ( & mut self ) ;
fn insert < P > ( & mut self , todo : T , position : P ) where P : Into < Option < usize > > ;
fn paste ( & mut self ) ;
fn remove_current ( & mut self ) ;
fn backspace ( & mut self ) ;
fn append_to_current ( & mut self , chr : char ) ;
fn normal_mode ( & mut self ) ;
}