2020-04-19 20:11:28 +02:00
use super ::layout ;
use super ::listview ::ListView ;
2020-04-19 19:04:27 +02:00
use super ::timesheet ::TimeSheet ;
2020-01-25 12:11:19 +01:00
use super ::todolist ::TodoList ;
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 ;
use tui ::widgets ::* ;
type Terminal = tui ::Terminal < TermionBackend < termion ::raw ::RawTerminal < io ::Stdout > > > ;
2020-04-13 23:56:25 +02:00
const JSON_PATH_TIME : & str = " tracc_time.json " ;
const JSON_PATH_TODO : & str = " tracc_todo.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-04-13 23:56:25 +02:00
todos : TodoList ::open_or_create ( JSON_PATH_TODO ) ,
times : TimeSheet ::open_or_create ( JSON_PATH_TIME ) ,
2020-01-25 12:11:19 +01:00
terminal ,
input_mode : Mode ::Normal ,
2021-09-01 15:35:58 +02:00
focus : Focus ::Bottom ,
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 > {
2020-04-18 18:15:23 +02:00
macro_rules ! with_focused {
( $action : expr $(, $arg : expr ) * ) = > {
match self . focus {
Focus ::Top = > $action ( & mut self . todos , $( $arg , ) * ) ,
Focus ::Bottom = > $action ( & mut self . times , $( $arg , ) * ) ,
}
} ;
2021-12-06 18:53:49 +01:00
}
2020-04-18 18:15:23 +02:00
2020-01-25 12:11:19 +01:00
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-04-18 18:15:23 +02:00
Key ::Char ( 'j' ) = > with_focused! ( ListView ::selection_down ) ,
Key ::Char ( 'k' ) = > with_focused! ( ListView ::selection_up ) ,
2021-12-06 18:53:49 +01:00
Key ::Char ( 'G' ) = > with_focused! ( ListView ::selection_first ) ,
// gg
Key ::Char ( 'g' ) = > {
if let Some ( Ok ( Key ::Char ( 'g' ) ) ) = inputs . next ( ) {
with_focused! ( ListView ::selection_last ) ;
}
}
2020-01-25 12:11:19 +01:00
Key ::Char ( 'o' ) = > {
2020-04-18 18:15:23 +02:00
with_focused! ( ListView ::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 ) ? ,
2020-04-27 12:34:58 +02:00
Key ::Char ( ' ' ) if self . focus = = Focus ::Top = > self . todos . toggle_current ( ) ,
2020-06-23 10:07:25 +02:00
// Subtract only 1 minute because the number is truncated to the next multiple
// of 5 afterwards, so this is effectively a -5.
// See https://git.kageru.moe/kageru/tracc/issues/8
Key ::Char ( '-' ) if self . focus = = Focus ::Bottom = > self . times . shift_current ( - 1 ) ,
2020-04-27 16:24:33 +02:00
Key ::Char ( '+' ) if self . focus = = Focus ::Bottom = > self . times . shift_current ( 5 ) ,
2020-04-20 11:12:45 +02:00
// dd
Key ::Char ( 'd' ) = > {
if let Some ( Ok ( Key ::Char ( 'd' ) ) ) = inputs . next ( ) {
with_focused! ( ListView ::remove_current ) ;
}
}
2020-04-23 14:06:45 +02:00
// yy
Key ::Char ( 'y' ) = > {
if let Some ( Ok ( Key ::Char ( 'y' ) ) ) = inputs . next ( ) {
with_focused! ( ListView ::yank ) ;
}
}
2020-04-18 18:15:23 +02:00
Key ::Char ( 'p' ) = > with_focused! ( ListView ::paste ) ,
2020-01-25 12:11:19 +01:00
_ = > ( ) ,
} ,
Mode ::Insert = > match input {
Key ::Char ( '\n' ) | Key ::Esc = > self . set_mode ( Mode ::Normal ) ? ,
2020-04-18 18:15:23 +02:00
Key ::Backspace = > with_focused! ( ListView ::backspace ) ,
Key ::Char ( x ) = > with_focused! ( ListView ::append_to_current , x ) ,
2020-01-25 12:11:19 +01:00
_ = > ( ) ,
} ,
} ;
2020-01-24 11:07:37 +01:00
}
2020-01-25 12:22:36 +01:00
self . terminal . clear ( ) ? ;
2020-04-13 23:56:25 +02:00
persist_state ( & self . todos , & self . times ) ;
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 ( ) ;
2020-04-13 23:56:25 +02:00
self . times . normal_mode ( ) ;
2020-04-19 19:22:06 +02:00
self . terminal . hide_cursor ( ) ? ;
2020-05-27 22:38:38 +02:00
persist_state ( & self . todos , & self . times ) ;
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 > {
2020-04-19 20:11:28 +02:00
let summary_content = [ Text ::raw ( format! (
2021-09-01 15:37:03 +02:00
" Sum for today: {} \n {} \n \n {} " ,
2020-04-19 20:11:28 +02:00
self . times . sum_as_str ( ) ,
2021-09-01 15:37:03 +02:00
self . times . pause_time ( ) ,
2020-04-19 20:11:28 +02:00
self . times . time_by_tasks ( )
) ) ] ;
let mut summary = Paragraph ::new ( summary_content . iter ( ) )
. wrap ( true )
. block ( Block ::default ( ) . borders ( Borders ::ALL ) ) ;
let todos = self . todos . printable ( ) ;
let mut todolist = layout ::selectable_list (
" t r a c c " ,
& todos ,
Some ( self . todos . selected ) . filter ( | _ | self . focus = = Focus ::Top ) ,
) ;
let times = self . times . printable ( ) ;
let mut timelist = layout ::selectable_list (
" 🕑 " ,
& times ,
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 | {
2020-04-19 20:11:28 +02:00
let chunks = layout ::layout ( frame . size ( ) ) ;
todolist . render ( & mut frame , chunks [ 0 ] ) ;
timelist . render ( & mut frame , chunks [ 1 ] ) ;
summary . render ( & mut frame , chunks [ 2 ] ) ;
2020-01-25 22:15:41 +01:00
} ) ? ;
Ok ( ( ) )
}
2020-01-23 23:05:40 +01:00
}
2020-01-25 12:22:36 +01:00
2020-04-13 23:56:25 +02:00
fn persist_state ( todos : & TodoList , times : & TimeSheet ) {
fn write ( path : & str , content : String ) {
std ::fs ::OpenOptions ::new ( )
. create ( true )
. write ( true )
. truncate ( true )
. open ( path )
. ok ( )
. or_else ( | | panic! ( " Can’t save state to JSON. Dumping raw data: \n {} " , content ) )
. map ( | mut f | f . write ( content . as_bytes ( ) ) ) ;
}
let todos = serde_json ::to_string ( & todos . todos ) . unwrap ( ) ;
write ( JSON_PATH_TODO , todos ) ;
let times = serde_json ::to_string ( & times . times ) . unwrap ( ) ;
write ( JSON_PATH_TIME , times ) ;
2020-01-25 12:22:36 +01:00
}