2018-07-22 18:02:44 +03:00
use super ::* ;
2018-07-23 15:40:13 +03:00
use linkify ::{ LinkFinder , Link } ;
use std ::process ::{ Command , Stdio } ;
2018-07-22 18:02:44 +03:00
2018-07-25 19:05:42 +03:00
use mime_apps ::query_default_app ;
2018-07-22 18:02:44 +03:00
2018-07-23 15:40:13 +03:00
#[ derive(PartialEq, Debug) ]
enum ViewMode {
Normal ,
Url ,
2018-07-26 00:07:00 +03:00
Attachment ( usize ) ,
2018-07-25 22:37:28 +03:00
// Raw,
}
impl ViewMode {
fn is_attachment ( & self ) -> bool {
match self {
ViewMode ::Attachment ( _ ) = > true ,
_ = > false ,
}
}
2018-07-23 15:40:13 +03:00
}
2018-07-22 23:11:07 +03:00
/// Contains an Envelope view, with sticky headers, a pager for the body, and subviews for more
/// menus
2018-07-22 18:02:44 +03:00
pub struct MailView {
coordinates : ( usize , usize , usize ) ,
pager : Option < Pager > ,
subview : Option < Box < MailView > > ,
dirty : bool ,
2018-07-23 15:40:13 +03:00
mode : ViewMode ,
cmd_buf : String ,
2018-07-22 18:02:44 +03:00
}
impl MailView {
pub fn new ( coordinates : ( usize , usize , usize ) , pager : Option < Pager > , subview : Option < Box < MailView > > ) -> Self {
MailView {
coordinates : coordinates ,
pager : pager ,
subview : subview ,
dirty : true ,
2018-07-23 15:40:13 +03:00
mode : ViewMode ::Normal ,
cmd_buf : String ::with_capacity ( 4 ) ,
2018-07-22 18:02:44 +03:00
}
}
}
impl Component for MailView {
fn draw ( & mut self , grid : & mut CellBuffer , area : Area , context : & mut Context ) {
let upper_left = upper_left! ( area ) ;
let bottom_right = bottom_right! ( area ) ;
let ( envelope_idx , y ) : ( usize , usize ) = {
let threaded = context . accounts [ self . coordinates . 0 ] . runtime_settings . threaded ;
let mailbox = & mut context . accounts [ self . coordinates . 0 ] [ self . coordinates . 1 ] . as_ref ( ) . unwrap ( ) . as_ref ( ) . unwrap ( ) ;
let envelope_idx : usize = if threaded {
mailbox . threaded_mail ( self . coordinates . 2 )
} else {
self . coordinates . 2
} ;
let envelope : & Envelope = & mailbox . collection [ envelope_idx ] ;
let ( x , y ) = write_string_to_grid ( & format! ( " Date: {} " , envelope . date_as_str ( ) ) ,
2018-07-23 15:40:13 +03:00
grid ,
Color ::Byte ( 33 ) ,
Color ::Default ,
area ,
true ) ;
2018-07-22 18:02:44 +03:00
for x in x ..= get_x ( bottom_right ) {
grid [ ( x , y ) ] . set_ch ( ' ' ) ;
grid [ ( x , y ) ] . set_bg ( Color ::Default ) ;
grid [ ( x , y ) ] . set_fg ( Color ::Default ) ;
}
let ( x , y ) = write_string_to_grid ( & format! ( " From: {} " , envelope . from ( ) ) ,
grid ,
Color ::Byte ( 33 ) ,
Color ::Default ,
( set_y ( upper_left , y + 1 ) , bottom_right ) ,
true ) ;
for x in x ..= get_x ( bottom_right ) {
grid [ ( x , y ) ] . set_ch ( ' ' ) ;
grid [ ( x , y ) ] . set_bg ( Color ::Default ) ;
grid [ ( x , y ) ] . set_fg ( Color ::Default ) ;
}
let ( x , y ) = write_string_to_grid ( & format! ( " To: {} " , envelope . to ( ) ) ,
grid ,
Color ::Byte ( 33 ) ,
Color ::Default ,
( set_y ( upper_left , y + 1 ) , bottom_right ) ,
true ) ;
for x in x ..= get_x ( bottom_right ) {
grid [ ( x , y ) ] . set_ch ( ' ' ) ;
grid [ ( x , y ) ] . set_bg ( Color ::Default ) ;
grid [ ( x , y ) ] . set_fg ( Color ::Default ) ;
}
let ( x , y ) = write_string_to_grid ( & format! ( " Subject: {} " , envelope . subject ( ) ) ,
grid ,
Color ::Byte ( 33 ) ,
Color ::Default ,
( set_y ( upper_left , y + 1 ) , bottom_right ) ,
true ) ;
for x in x ..= get_x ( bottom_right ) {
grid [ ( x , y ) ] . set_ch ( ' ' ) ;
grid [ ( x , y ) ] . set_bg ( Color ::Default ) ;
grid [ ( x , y ) ] . set_fg ( Color ::Default ) ;
}
let ( x , y ) = write_string_to_grid ( & format! ( " Message-ID: {} " , envelope . message_id_raw ( ) ) ,
grid ,
Color ::Byte ( 33 ) ,
Color ::Default ,
( set_y ( upper_left , y + 1 ) , bottom_right ) ,
true ) ;
for x in x ..= get_x ( bottom_right ) {
grid [ ( x , y ) ] . set_ch ( ' ' ) ;
grid [ ( x , y ) ] . set_bg ( Color ::Default ) ;
grid [ ( x , y ) ] . set_fg ( Color ::Default ) ;
}
clear_area ( grid ,
( set_y ( upper_left , y + 1 ) , set_y ( bottom_right , y + 2 ) ) ) ;
context . dirty_areas . push_back ( ( upper_left , set_y ( bottom_right , y + 1 ) ) ) ;
( envelope_idx , y + 1 )
} ;
2018-07-23 15:40:13 +03:00
2018-07-22 18:02:44 +03:00
if self . dirty {
2018-07-23 22:21:41 +03:00
let buf = {
2018-07-23 15:40:13 +03:00
let mailbox_idx = self . coordinates ; // coordinates are mailbox idxs
let mailbox = & mut context . accounts [ mailbox_idx . 0 ] [ mailbox_idx . 1 ] . as_ref ( ) . unwrap ( ) . as_ref ( ) . unwrap ( ) ;
let envelope : & Envelope = & mailbox . collection [ envelope_idx ] ;
2018-07-23 22:21:41 +03:00
let finder = LinkFinder ::new ( ) ;
2018-07-23 15:40:13 +03:00
let mut text = match self . mode {
ViewMode ::Url = > {
2018-07-25 22:37:28 +03:00
let mut t = envelope . body ( ) . text ( ) . to_string ( ) ;
2018-07-23 15:40:13 +03:00
for ( lidx , l ) in finder . links ( & envelope . body ( ) . text ( ) ) . enumerate ( ) {
t . insert_str ( l . start ( ) + ( lidx * 3 ) , & format! ( " [ {} ] " , lidx ) ) ;
}
2018-07-25 22:37:28 +03:00
if envelope . body ( ) . count_attachments ( ) > 1 {
t = envelope . body ( ) . attachments ( ) . iter ( ) . enumerate ( ) . fold ( t , | mut s , ( idx , a ) | { s . push_str ( & format! ( " [ {} ] {} \n \n " , idx , a ) ) ; s } ) ;
}
t
} ,
ViewMode ::Attachment ( aidx ) = > {
let attachments = envelope . body ( ) . attachments ( ) ;
let mut ret = format! ( " Viewing attachment. Press `r` to return \n " ) ;
ret . push_str ( & attachments [ aidx ] . text ( ) ) ;
ret
} ,
_ = > {
let mut t = envelope . body ( ) . text ( ) . to_string ( ) ;
if envelope . body ( ) . count_attachments ( ) > 1 {
t = envelope . body ( ) . attachments ( ) . iter ( ) . enumerate ( ) . fold ( t , | mut s , ( idx , a ) | { s . push_str ( & format! ( " [ {} ] {} \n \n " , idx , a ) ) ; s } ) ;
}
2018-07-23 15:40:13 +03:00
t
} ,
} ;
2018-07-23 22:21:41 +03:00
let mut buf = CellBuffer ::from ( & text ) ;
match self . mode {
ViewMode ::Url = > {
2018-07-24 11:34:44 +03:00
// URL indexes must be colored (ugh..)
let lines : Vec < & str > = text . split ( '\n' ) . collect ( ) ;
let mut shift = 0 ;
2018-07-24 20:20:32 +03:00
for r in lines . iter ( ) {
for l in finder . links ( & r ) {
2018-07-24 11:34:44 +03:00
buf [ ( l . start ( ) + shift - 1 , 0 ) ] . set_fg ( Color ::Byte ( 226 ) ) ;
buf [ ( l . start ( ) + shift - 2 , 0 ) ] . set_fg ( Color ::Byte ( 226 ) ) ;
buf [ ( l . start ( ) + shift - 3 , 0 ) ] . set_fg ( Color ::Byte ( 226 ) ) ;
2018-07-23 22:21:41 +03:00
}
2018-07-24 11:34:44 +03:00
// Each Cell represents one char so next line will be:
shift + = r . chars ( ) . count ( ) + 1 ;
2018-07-23 22:21:41 +03:00
}
} ,
_ = > { } ,
}
2018-07-25 22:37:28 +03:00
buf
} ;
let cursor_pos = if self . mode . is_attachment ( ) {
Some ( 0 )
} else {
self . pager . as_mut ( ) . map ( | p | p . cursor_pos ( ) )
2018-07-23 15:40:13 +03:00
} ;
2018-07-22 23:11:07 +03:00
// TODO: pass string instead of envelope
2018-07-24 11:34:44 +03:00
self . pager = Some ( Pager ::from_buf ( buf , cursor_pos ) ) ;
2018-07-22 18:02:44 +03:00
self . dirty = false ;
}
self . pager . as_mut ( ) . map ( | p | p . draw ( grid , ( set_y ( upper_left , y + 1 ) , bottom_right ) , context ) ) ;
}
2018-07-25 22:37:28 +03:00
2018-07-22 18:02:44 +03:00
fn process_event ( & mut self , event : & UIEvent , context : & mut Context ) {
2018-07-23 15:40:13 +03:00
match event . event_type {
2018-07-24 13:28:15 +03:00
UIEventType ::Input ( Key ::Esc ) = > {
2018-07-25 22:37:28 +03:00
self . cmd_buf . clear ( ) ;
2018-07-24 13:28:15 +03:00
} ,
2018-07-23 15:40:13 +03:00
UIEventType ::Input ( Key ::Char ( c ) ) if c > = '0' & & c < = '9' = > { //TODO:this should be an Action
2018-07-24 16:55:00 +03:00
self . cmd_buf . push ( c ) ;
} ,
2018-07-25 22:37:28 +03:00
UIEventType ::Input ( Key ::Char ( 'r' ) ) if self . mode . is_attachment ( ) = > { //TODO:one quit shortcut?
self . mode = ViewMode ::Normal ;
self . dirty = true ;
} ,
2018-07-24 16:55:00 +03:00
UIEventType ::Input ( Key ::Char ( 'a' ) ) if self . cmd_buf . len ( ) > 0 & & self . mode = = ViewMode ::Normal = > { //TODO:this should be an Action
let lidx = self . cmd_buf . parse ::< usize > ( ) . unwrap ( ) ;
self . cmd_buf . clear ( ) ;
2018-07-24 20:20:32 +03:00
{
2018-07-24 16:55:00 +03:00
let threaded = context . accounts [ self . coordinates . 0 ] . runtime_settings . threaded ;
let mailbox = & mut context . accounts [ self . coordinates . 0 ] [ self . coordinates . 1 ] . as_ref ( ) . unwrap ( ) . as_ref ( ) . unwrap ( ) ;
let envelope_idx : usize = if threaded {
mailbox . threaded_mail ( self . coordinates . 2 )
} else {
self . coordinates . 2
} ;
let envelope : & Envelope = & mailbox . collection [ envelope_idx ] ;
if let Some ( u ) = envelope . body ( ) . attachments ( ) . get ( lidx ) {
2018-07-25 22:37:28 +03:00
match u . content_type ( ) . 0 {
ContentType ::Text = > {
self . mode = ViewMode ::Attachment ( lidx ) ;
self . dirty = true ;
} ,
ContentType ::Multipart { .. } = > {
2018-07-26 00:07:00 +03:00
context . replies . push_back ( UIEvent { id : 0 , event_type : UIEventType ::StatusNotification ( format! ( " Multipart attachments are not supported yet. " ) ) } ) ;
return ;
2018-07-25 22:37:28 +03:00
} ,
ContentType ::Unsupported { .. } = > {
let attachment_type = u . mime_type ( ) ;
2018-07-26 00:07:00 +03:00
let binary = query_default_app ( & attachment_type ) ;
if let Ok ( binary ) = binary {
let mut p = create_temp_file ( & decode ( u ) , None ) ;
Command ::new ( & binary )
. arg ( p . path ( ) )
. stdin ( Stdio ::piped ( ) )
. stdout ( Stdio ::piped ( ) )
. spawn ( )
. expect ( & format! ( " Failed to start {} " , binary . display ( ) ) ) ;
} else {
context . replies . push_back ( UIEvent { id : 0 , event_type : UIEventType ::StatusNotification ( format! ( " Couldn't find a default application for type {} " , attachment_type ) ) } ) ;
return ;
}
2018-07-25 22:37:28 +03:00
} ,
}
2018-07-24 16:55:00 +03:00
} else {
context . replies . push_back ( UIEvent { id : 0 , event_type : UIEventType ::StatusNotification ( format! ( " Attachment ` {} ` not found. " , lidx ) ) } ) ;
return ;
}
} ;
2018-07-23 15:40:13 +03:00
} ,
UIEventType ::Input ( Key ::Char ( 'g' ) ) if self . cmd_buf . len ( ) > 0 & & self . mode = = ViewMode ::Url = > { //TODO:this should be an Action
let lidx = self . cmd_buf . parse ::< usize > ( ) . unwrap ( ) ;
self . cmd_buf . clear ( ) ;
let url = {
let threaded = context . accounts [ self . coordinates . 0 ] . runtime_settings . threaded ;
let mailbox = & mut context . accounts [ self . coordinates . 0 ] [ self . coordinates . 1 ] . as_ref ( ) . unwrap ( ) . as_ref ( ) . unwrap ( ) ;
let envelope_idx : usize = if threaded {
mailbox . threaded_mail ( self . coordinates . 2 )
} else {
self . coordinates . 2
} ;
let envelope : & Envelope = & mailbox . collection [ envelope_idx ] ;
let finder = LinkFinder ::new ( ) ;
let mut t = envelope . body ( ) . text ( ) . to_string ( ) ;
let links : Vec < Link > = finder . links ( & t ) . collect ( ) ;
2018-07-23 16:29:22 +03:00
if let Some ( u ) = links . get ( lidx ) {
u . as_str ( ) . to_string ( )
} else {
context . replies . push_back ( UIEvent { id : 0 , event_type : UIEventType ::StatusNotification ( format! ( " Link ` {} ` not found. " , lidx ) ) } ) ;
return ;
}
2018-07-23 15:40:13 +03:00
} ;
2018-07-24 20:20:32 +03:00
Command ::new ( " xdg-open " )
2018-07-23 15:40:13 +03:00
. arg ( url )
. stdin ( Stdio ::piped ( ) )
. stdout ( Stdio ::piped ( ) )
. spawn ( )
. expect ( " Failed to start xdg_open " ) ;
} ,
UIEventType ::Input ( Key ::Char ( 'u' ) ) = > { //TODO:this should be an Action
match self . mode {
ViewMode ::Normal = > { self . mode = ViewMode ::Url } ,
ViewMode ::Url = > { self . mode = ViewMode ::Normal } ,
2018-07-25 22:37:28 +03:00
_ = > { } ,
2018-07-23 15:40:13 +03:00
}
self . dirty = true ;
} ,
_ = > { } ,
}
2018-07-22 18:02:44 +03:00
if let Some ( ref mut sub ) = self . subview {
sub . process_event ( event , context ) ;
2018-07-23 15:40:13 +03:00
2018-07-22 18:02:44 +03:00
} else {
if let Some ( ref mut p ) = self . pager {
p . process_event ( event , context ) ;
}
}
}
fn is_dirty ( & self ) -> bool {
self . dirty | | self . pager . as_ref ( ) . map ( | p | p . is_dirty ( ) ) . unwrap_or ( false ) | |
2018-07-23 15:40:13 +03:00
self . subview . as_ref ( ) . map ( | p | p . is_dirty ( ) ) . unwrap_or ( false )
2018-07-22 18:02:44 +03:00
}
}