@ -21,9 +21,12 @@
use super ::* ;
use crate ::terminal ::embed ::EmbedGrid ;
use melib ::Draft ;
use mime_apps ::query_mime_info ;
use nix ::sys ::wait ::WaitStatus ;
use std ::str ::FromStr ;
use std ::sync ::{ Arc , Mutex } ;
#[ derive(Debug, PartialEq) ]
enum Cursor {
@ -32,6 +35,31 @@ enum Cursor {
//Attachments,
}
#[ derive(Debug) ]
enum EmbedStatus {
Stopped ( Arc < Mutex < EmbedGrid > > , File ) ,
Running ( Arc < Mutex < EmbedGrid > > , File ) ,
}
impl std ::ops ::Deref for EmbedStatus {
type Target = Arc < Mutex < EmbedGrid > > ;
fn deref ( & self ) -> & Arc < Mutex < EmbedGrid > > {
use EmbedStatus ::* ;
match self {
Stopped ( ref e , _ ) | Running ( ref e , _ ) = > e ,
}
}
}
impl std ::ops ::DerefMut for EmbedStatus {
fn deref_mut ( & mut self ) -> & mut Arc < Mutex < EmbedGrid > > {
use EmbedStatus ::* ;
match self {
Stopped ( ref mut e , _ ) | Running ( ref mut e , _ ) = > e ,
}
}
}
#[ derive(Debug) ]
pub struct Composer {
reply_context : Option < ( ( usize , usize ) , Box < ThreadView > ) > , // (folder_index, thread_node_index)
@ -44,6 +72,9 @@ pub struct Composer {
form : FormWidget ,
mode : ViewMode ,
embed_area : Area ,
embed : Option < EmbedStatus > ,
sign_mail : ToggleFlag ,
dirty : bool ,
has_changes : bool ,
@ -67,6 +98,8 @@ impl Default for Composer {
sign_mail : ToggleFlag ::Unset ,
dirty : true ,
has_changes : false ,
embed_area : ( ( 0 , 0 ) , ( 0 , 0 ) ) ,
embed : None ,
initialized : false ,
id : ComponentId ::new_v4 ( ) ,
}
@ -77,6 +110,7 @@ impl Default for Composer {
enum ViewMode {
Discard ( Uuid , Selector < char > ) ,
Edit ,
Embed ,
SelectRecipients ( Selector < Address > ) ,
ThreadView ,
}
@ -97,6 +131,14 @@ impl ViewMode {
false
}
}
fn is_embed ( & self ) -> bool {
if let ViewMode ::Embed = self {
true
} else {
false
}
}
}
impl fmt ::Display for Composer {
@ -211,10 +253,9 @@ impl Composer {
fn update_draft ( & mut self ) {
let header_values = self . form . values_mut ( ) ;
let draft_header_map = self . draft . headers_mut ( ) ;
/* avoid extra allocations by updating values instead of inserting */
for ( k , v ) in draft_header_map . iter_mut ( ) {
if let Some ( vn ) = header_values . remove ( k ) {
std ::mem ::swap ( v , & mut vn . into_string ( ) ) ;
if let Some ( ref vn ) = header_values . get ( k ) {
* v = vn . as_str ( ) . to_string ( ) ;
}
}
}
@ -504,8 +545,44 @@ impl Component for Composer {
/* Regardless of view mode, do the following */
self . form . draw ( grid , header_area , context ) ;
if let Some ( ref mut embed_pty ) = self . embed {
let body_area = ( upper_left ! ( header_area ) , bottom_right ! ( body_area ) ) ;
clear_area ( grid , body_area ) ;
match embed_pty {
EmbedStatus ::Running ( _ , _ ) = > {
let mut guard = embed_pty . lock ( ) . unwrap ( ) ;
copy_area (
grid ,
& guard . grid ,
body_area ,
( ( 0 , 0 ) , pos_dec ( guard . terminal_size , ( 1 , 1 ) ) ) ,
) ;
guard . set_terminal_size ( ( width ! ( body_area ) , height ! ( body_area ) ) ) ;
context . dirty_areas . push_back ( area ) ;
self . dirty = false ;
return ;
}
EmbedStatus ::Stopped ( _ , _ ) = > {
write_string_to_grid (
"process has stopped, press 'e' to re-activate" ,
grid ,
Color ::Default ,
Color ::Default ,
Attr ::Default ,
body_area ,
false ,
) ;
context . dirty_areas . push_back ( body_area ) ;
self . dirty = false ;
return ;
}
}
} else {
self . embed_area = ( upper_left ! ( header_area ) , bottom_right ! ( body_area ) ) ;
self . pager . set_dirty ( ) ;
self . pager . draw ( grid , body_area , context ) ;
}
if self . cursor = = Cursor ::Body {
change_colors (
grid ,
@ -529,7 +606,7 @@ impl Component for Composer {
}
match self . mode {
ViewMode ::ThreadView | ViewMode ::Edit => { }
ViewMode ::ThreadView | ViewMode ::Edit | ViewMode ::Embed => { }
ViewMode ::SelectRecipients ( ref mut s ) = > {
s . draw ( grid , center_area ( area , s . content . size ( ) ) , context ) ;
}
@ -685,6 +762,15 @@ impl Component for Composer {
match * event {
UIEvent ::Resize = > {
self . set_dirty ( ) ;
if let Some ( ref mut embed_pty ) = self . embed {
match embed_pty {
EmbedStatus ::Running ( _ , _ ) = > {
let mut guard = embed_pty . lock ( ) . unwrap ( ) ;
guard . grid . clear ( Cell ::default ( ) ) ;
}
_ = > { }
}
}
}
/*
/* Switch e-mail From: field to the `left` configured account. */
@ -744,9 +830,108 @@ impl Component for Composer {
}
return true ;
}
UIEvent ::EmbedInput ( ( Key ::Ctrl ( 'z' ) , _ ) ) = > {
self . embed . as_ref ( ) . unwrap ( ) . lock ( ) . unwrap ( ) . stop ( ) ;
context
. replies
. push_back ( UIEvent ::ChangeMode ( UIMode ::Normal ) ) ;
self . dirty = true ;
}
UIEvent ::EmbedInput ( ( ref k , ref b ) ) = > {
use std ::io ::Write ;
if let Some ( ref mut embed ) = self . embed {
let mut embed_guard = embed . lock ( ) . unwrap ( ) ;
if embed_guard . stdin . write_all ( b ) . is_err ( ) {
match embed_guard . is_active ( ) {
Ok ( WaitStatus ::Exited ( _ , exit_code ) ) = > {
drop ( embed_guard ) ;
if exit_code ! = 0 {
context . replies . push_back ( UIEvent ::Notification (
None ,
format! (
"Subprocess has exited with exit code {}" ,
exit_code
) ,
Some ( NotificationType ::ERROR ) ,
) ) ;
} else if let EmbedStatus ::Running ( _ , f ) = embed {
let result = f . read_to_string ( ) ;
match Draft ::from_str ( result . as_str ( ) ) {
Ok ( mut new_draft ) = > {
std ::mem ::swap (
self . draft . attachments_mut ( ) ,
new_draft . attachments_mut ( ) ,
) ;
if self . draft ! = new_draft {
self . has_changes = true ;
}
self . draft = new_draft ;
}
Err ( _ ) = > {
context . replies . push_back ( UIEvent ::Notification (
None ,
"Could not parse draft headers correctly. The invalid text has been set as the body of your draft" . to_string ( ) ,
Some ( NotificationType ::ERROR ) ,
) ) ;
self . draft . set_body ( result ) ;
self . has_changes = true ;
}
}
self . initialized = false ;
}
self . embed = None ;
self . mode = ViewMode ::Edit ;
context
. replies
. push_back ( UIEvent ::ChangeMode ( UIMode ::Normal ) ) ;
}
Ok ( WaitStatus ::Stopped ( _ , _ ) ) = > {
drop ( embed_guard ) ;
match self . embed . take ( ) {
Some ( EmbedStatus ::Running ( e , f ) )
| Some ( EmbedStatus ::Stopped ( e , f ) ) = > {
self . embed = Some ( EmbedStatus ::Stopped ( e , f ) ) ;
}
_ = > { }
}
self . dirty = true ;
return true ;
}
Ok ( WaitStatus ::Continued ( _ ) ) | Ok ( WaitStatus ::StillAlive ) = > {
context
. replies
. push_back ( UIEvent ::EmbedInput ( ( k . clone ( ) , b . to_vec ( ) ) ) ) ;
return true ;
}
e = > {
context . replies . push_back ( UIEvent ::Notification (
None ,
format! ( "Subprocess has exited with reason {:?}" , e ) ,
Some ( NotificationType ::ERROR ) ,
) ) ;
drop ( embed_guard ) ;
self . embed = None ;
self . mode = ViewMode ::Edit ;
context
. replies
. push_back ( UIEvent ::ChangeMode ( UIMode ::Normal ) ) ;
}
}
}
}
self . set_dirty ( ) ;
return true ;
}
UIEvent ::Input ( Key ::Char ( 'e' ) ) if self . mode . is_embed ( ) = > {
self . embed . as_ref ( ) . unwrap ( ) . lock ( ) . unwrap ( ) . wake_up ( ) ;
context
. replies
. push_back ( UIEvent ::ChangeMode ( UIMode ::Embed ) ) ;
self . set_dirty ( ) ;
return true ;
}
UIEvent ::Input ( Key ::Char ( 'e' ) ) = > {
/* Edit draft in $EDITOR */
use std ::process ::{ Command , Stdio } ;
let settings = & context . settings ;
let editor = if let Some ( editor_cmd ) = settings . composing . editor_cmd . as_ref ( ) {
editor_cmd . to_string ( )
@ -763,10 +948,6 @@ impl Component for Composer {
Ok ( v ) = > v ,
}
} ;
/* Kill input thread so that spawned command can be sole receiver of stdin */
{
context . input_kill ( ) ;
}
/* update Draft's headers based on form values */
self . update_draft ( ) ;
let f = create_temp_file (
@ -776,6 +957,28 @@ impl Component for Composer {
true ,
) ;
if settings . composing . embed {
self . embed = Some ( EmbedStatus ::Running (
crate ::terminal ::embed ::create_pty (
self . embed_area ,
[ editor , f . path ( ) . display ( ) . to_string ( ) ] . join ( " " ) ,
)
. unwrap ( ) ,
f ,
) ) ;
self . dirty = true ;
context
. replies
. push_back ( UIEvent ::ChangeMode ( UIMode ::Embed ) ) ;
self . mode = ViewMode ::Embed ;
return true ;
}
use std ::process ::{ Command , Stdio } ;
/* Kill input thread so that spawned command can be sole receiver of stdin */
{
context . input_kill ( ) ;
}
let parts = split_command ! ( editor ) ;
let ( cmd , args ) = ( parts [ 0 ] , & parts [ 1 .. ] ) ;
if let Err ( e ) = Command ::new ( cmd )
@ -794,15 +997,27 @@ impl Component for Composer {
context . restore_input ( ) ;
return true ;
}
context . replies . push_back ( UIEvent ::Fork ( ForkType ::Finished ) ) ;
let result = f . read_to_string ( ) ;
let mut new_draft = Draft ::from_str ( result . as_str ( ) ) . unwrap ( ) ;
match Draft ::from_str ( result . as_str ( ) ) {
Ok ( mut new_draft ) = > {
std ::mem ::swap ( self . draft . attachments_mut ( ) , new_draft . attachments_mut ( ) ) ;
if self . draft ! = new_draft {
self . has_changes = true ;
}
self . draft = new_draft ;
}
Err ( _ ) = > {
context . replies . push_back ( UIEvent ::Notification (
None ,
"Could not parse draft headers correctly. The invalid text has been set as the body of your draft" . to_string ( ) ,
Some ( NotificationType ::ERROR ) ,
) ) ;
self . draft . set_body ( result ) ;
self . has_changes = true ;
}
}
self . initialized = false ;
context . replies . push_back ( UIEvent ::Fork ( ForkType ::Finished ) ) ;
context . restore_input ( ) ;
self . dirty = true ;
return true ;
@ -866,6 +1081,9 @@ impl Component for Composer {
}
fn is_dirty ( & self ) -> bool {
match self . mode {
ViewMode ::Embed = > true ,
_ = > {
self . dirty
| | self . pager . is_dirty ( )
| | self
@ -875,6 +1093,8 @@ impl Component for Composer {
. unwrap_or ( false )
| | self . form . is_dirty ( )
}
}
}
fn set_dirty ( & mut self ) {
self . dirty = true ;