parent
f6caf993ae
commit
b94687cdb0
|
@ -22,12 +22,14 @@ pub struct Draft {
|
|||
impl Default for Draft {
|
||||
fn default() -> Self {
|
||||
let mut headers = FnvHashMap::with_capacity_and_hasher(8, Default::default());
|
||||
headers.insert("From".into(), "x".into());
|
||||
headers.insert("To".into(), "x".into());
|
||||
headers.insert("From".into(), "".into());
|
||||
headers.insert("To".into(), "".into());
|
||||
headers.insert("Cc".into(), "".into());
|
||||
headers.insert("Bcc".into(), "".into());
|
||||
|
||||
let now: DateTime<Local> = Local::now();
|
||||
headers.insert("Date".into(), now.to_rfc2822());
|
||||
headers.insert("Subject".into(), "x".into());
|
||||
headers.insert("Subject".into(), "".into());
|
||||
headers.insert("Message-ID".into(), random::gen_message_id());
|
||||
headers.insert("User-Agent".into(), "meli".into());
|
||||
Draft {
|
||||
|
@ -58,7 +60,7 @@ impl Draft {
|
|||
pub fn to_string(&self) -> Result<String> {
|
||||
let mut ret = String::new();
|
||||
|
||||
let headers = &["Date", "From", "To", "Subject", "Message-ID"];
|
||||
let headers = &["Date", "From", "To", "Cc", "Bcc", "Subject", "Message-ID"];
|
||||
for k in headers {
|
||||
ret.extend(format!("{}: {}\n", k, &self.headers[*k]).chars());
|
||||
}
|
||||
|
@ -131,6 +133,8 @@ fn ignore_header(header: &[u8]) -> bool {
|
|||
b"Reply-to" => false,
|
||||
b"Cc" => false,
|
||||
b"Bcc" => false,
|
||||
b"In-Reply-To" => false,
|
||||
b"References" => false,
|
||||
h if h.starts_with(b"X-") => false,
|
||||
_ => true,
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ use std::collections::hash_map::DefaultHasher;
|
|||
use std::fmt;
|
||||
use std::hash::Hasher;
|
||||
use std::option::Option;
|
||||
use std::str;
|
||||
use std::string::String;
|
||||
|
||||
use chrono;
|
||||
|
@ -181,6 +182,16 @@ fn test_strbuilder() {
|
|||
);
|
||||
}
|
||||
|
||||
impl fmt::Display for MessageID {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
if self.val().is_ascii() {
|
||||
write!(f, "{}", unsafe { str::from_utf8_unchecked(self.val()) })
|
||||
} else {
|
||||
write!(f, "{}", String::from_utf8_lossy(self.val()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for MessageID {
|
||||
fn eq(&self, other: &MessageID) -> bool {
|
||||
self.raw() == other.raw()
|
||||
|
@ -261,6 +272,8 @@ pub struct Envelope {
|
|||
date: String,
|
||||
from: Vec<Address>,
|
||||
to: Vec<Address>,
|
||||
cc: Vec<Address>,
|
||||
bcc: Vec<Address>,
|
||||
body: Option<Attachment>,
|
||||
subject: Option<Vec<u8>>,
|
||||
message_id: Option<MessageID>,
|
||||
|
@ -281,6 +294,8 @@ impl Envelope {
|
|||
date: String::new(),
|
||||
from: Vec::new(),
|
||||
to: Vec::new(),
|
||||
cc: Vec::new(),
|
||||
bcc: Vec::new(),
|
||||
body: None,
|
||||
subject: None,
|
||||
message_id: None,
|
||||
|
@ -342,6 +357,22 @@ impl Envelope {
|
|||
Vec::new()
|
||||
};
|
||||
self.set_to(value);
|
||||
} else if name.eq_ignore_ascii_case(b"cc") {
|
||||
let parse_result = parser::rfc2822address_list(value);
|
||||
let value = if parse_result.is_done() {
|
||||
parse_result.to_full_result().unwrap()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
self.set_cc(value);
|
||||
} else if name.eq_ignore_ascii_case(b"bcc") {
|
||||
let parse_result = parser::rfc2822address_list(value);
|
||||
let value = if parse_result.is_done() {
|
||||
parse_result.to_full_result().unwrap()
|
||||
} else {
|
||||
Vec::new()
|
||||
};
|
||||
self.set_bcc(value);
|
||||
} else if name.eq_ignore_ascii_case(b"from") {
|
||||
let parse_result = parser::rfc2822address_list(value);
|
||||
let value = if parse_result.is_done() {
|
||||
|
@ -424,7 +455,14 @@ impl Envelope {
|
|||
pub fn from(&self) -> &Vec<Address> {
|
||||
&self.from
|
||||
}
|
||||
|
||||
pub fn field_bcc_to_string(&self) -> String {
|
||||
let _strings: Vec<String> = self.bcc.iter().map(|a| format!("{}", a)).collect();
|
||||
_strings.join(", ")
|
||||
}
|
||||
pub fn field_cc_to_string(&self) -> String {
|
||||
let _strings: Vec<String> = self.cc.iter().map(|a| format!("{}", a)).collect();
|
||||
_strings.join(", ")
|
||||
}
|
||||
pub fn field_from_to_string(&self) -> String {
|
||||
let _strings: Vec<String> = self.from.iter().map(|a| format!("{}", a)).collect();
|
||||
_strings.join(", ")
|
||||
|
@ -530,6 +568,12 @@ impl Envelope {
|
|||
fn set_date(&mut self, new_val: &[u8]) -> () {
|
||||
self.date = String::from_utf8_lossy(new_val).into_owned();
|
||||
}
|
||||
fn set_bcc(&mut self, new_val: Vec<Address>) -> () {
|
||||
self.from = new_val;
|
||||
}
|
||||
fn set_cc(&mut self, new_val: Vec<Address>) -> () {
|
||||
self.from = new_val;
|
||||
}
|
||||
fn set_from(&mut self, new_val: Vec<Address>) -> () {
|
||||
self.from = new_val;
|
||||
}
|
||||
|
|
|
@ -103,6 +103,21 @@ pub struct Container {
|
|||
show_subject: bool,
|
||||
}
|
||||
|
||||
impl Default for Container {
|
||||
fn default() -> Container {
|
||||
Container {
|
||||
id: 0,
|
||||
message: None,
|
||||
parent: None,
|
||||
first_child: None,
|
||||
next_sibling: None,
|
||||
date: UnixTimestamp::default(),
|
||||
indentation: 0,
|
||||
show_subject: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct ContainerTree {
|
||||
id: usize,
|
||||
|
@ -513,12 +528,8 @@ fn build_collection(
|
|||
threads.push(Container {
|
||||
message: Some(i),
|
||||
id: x_index,
|
||||
parent: None,
|
||||
first_child: None,
|
||||
next_sibling: None,
|
||||
date: x.date(),
|
||||
indentation: 0,
|
||||
show_subject: true,
|
||||
..Default::default()
|
||||
});
|
||||
x.set_thread(x_index);
|
||||
id_table.insert(m_id, x_index);
|
||||
|
@ -565,12 +576,9 @@ fn build_collection(
|
|||
threads.push(Container {
|
||||
message: None,
|
||||
id: idx,
|
||||
parent: None,
|
||||
first_child: Some(curr_ref),
|
||||
next_sibling: None,
|
||||
date: x.date(),
|
||||
indentation: 0,
|
||||
show_subject: true,
|
||||
..Default::default()
|
||||
});
|
||||
if threads[curr_ref].parent.is_none() {
|
||||
threads[curr_ref].parent = Some(idx);
|
||||
|
@ -661,11 +669,8 @@ pub fn build_threads(
|
|||
message: Some(idx),
|
||||
id: tidx,
|
||||
parent: Some(p),
|
||||
first_child: None,
|
||||
next_sibling: None,
|
||||
date: x.date(),
|
||||
indentation: 0,
|
||||
show_subject: true,
|
||||
..Default::default()
|
||||
});
|
||||
id_table.insert(Cow::from(m_id.into_owned()), tidx);
|
||||
x.set_thread(tidx);
|
||||
|
|
24
src/bin.rs
24
src/bin.rs
|
@ -61,25 +61,18 @@ fn main() {
|
|||
let receiver = state.receiver();
|
||||
|
||||
/* Register some reasonably useful interfaces */
|
||||
let menu = Entity {
|
||||
component: Box::new(AccountMenu::new(&state.context.accounts)),
|
||||
};
|
||||
let menu = Entity::from(Box::new(AccountMenu::new(&state.context.accounts)));
|
||||
let listing = CompactListing::new();
|
||||
let b = Entity {
|
||||
component: Box::new(listing),
|
||||
};
|
||||
let b = Entity::from(Box::new(listing));
|
||||
let mut tabs = Box::new(Tabbed::new(vec![Box::new(VSplit::new(menu, b, 90, true))]));
|
||||
tabs.add_component(Box::new(Composer::default()));
|
||||
let window = Entity { component: tabs };
|
||||
let window = Entity::from(tabs);
|
||||
|
||||
let status_bar = Entity {
|
||||
component: Box::new(StatusBar::new(window)),
|
||||
};
|
||||
let status_bar = Entity::from(Box::new(StatusBar::new(window)));
|
||||
state.register_entity(status_bar);
|
||||
|
||||
let xdg_notifications = Entity {
|
||||
component: Box::new(ui::components::notifications::XDGNotifications {}),
|
||||
};
|
||||
let xdg_notifications =
|
||||
Entity::from(Box::new(ui::components::notifications::XDGNotifications {}));
|
||||
state.register_entity(xdg_notifications);
|
||||
|
||||
/* Keep track of the input mode. See ui::UIMode for details */
|
||||
|
@ -203,8 +196,7 @@ fn main() {
|
|||
match state.try_wait_on_child() {
|
||||
Some(true) => {
|
||||
state.restore_input();
|
||||
state.mode = UIMode::Normal;
|
||||
state.render();
|
||||
state.switch_to_alternate_screen();
|
||||
}
|
||||
Some(false) => {
|
||||
use std::{thread, time};
|
||||
|
@ -214,6 +206,8 @@ fn main() {
|
|||
continue 'reap;
|
||||
}
|
||||
None => {
|
||||
state.mode = UIMode::Normal;
|
||||
state.render();
|
||||
break 'reap;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,49 +25,148 @@ use melib::Draft;
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct Composer {
|
||||
mode: ViewMode,
|
||||
pager: Pager,
|
||||
|
||||
draft: Draft,
|
||||
reply_context: Option<((usize, usize), Box<ThreadView>)>, // (folder_index, container_index)
|
||||
account_cursor: usize,
|
||||
|
||||
pager: Pager,
|
||||
draft: Draft,
|
||||
|
||||
mode: ViewMode,
|
||||
dirty: bool,
|
||||
initialized: bool,
|
||||
}
|
||||
|
||||
impl Default for Composer {
|
||||
fn default() -> Self {
|
||||
Composer {
|
||||
dirty: true,
|
||||
mode: ViewMode::Overview,
|
||||
reply_context: None,
|
||||
account_cursor: 0,
|
||||
|
||||
pager: Pager::default(),
|
||||
draft: Draft::default(),
|
||||
account_cursor: 0,
|
||||
|
||||
mode: ViewMode::Overview,
|
||||
dirty: true,
|
||||
initialized: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ViewMode {
|
||||
//Compose,
|
||||
Discard(Uuid),
|
||||
Pager,
|
||||
Overview,
|
||||
}
|
||||
|
||||
impl ViewMode {
|
||||
fn is_discard(&self) -> bool {
|
||||
if let ViewMode::Discard(_) = self {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn is_overview(&self) -> bool {
|
||||
if let ViewMode::Overview = self {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn is_pager(&self) -> bool {
|
||||
if let ViewMode::Pager = self {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Composer {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
// TODO display subject/info
|
||||
write!(f, "compose")
|
||||
if self.reply_context.is_some() {
|
||||
write!(f, "reply: {:8}", self.draft.headers()["Subject"])
|
||||
} else {
|
||||
write!(f, "compose")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Composer {
|
||||
fn draw_header_table(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
/*
|
||||
* coordinates: (account index, mailbox index, root set container index)
|
||||
* msg: index of message we reply to in containers
|
||||
* context: current context
|
||||
*/
|
||||
pub fn with_context(coordinates: (usize, usize, usize), msg: usize, context: &Context) -> Self {
|
||||
let mailbox = &context.accounts[coordinates.0][coordinates.1]
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let threads = &mailbox.threads;
|
||||
let containers = &threads.containers();
|
||||
|
||||
let mut ret = Composer::default();
|
||||
|
||||
let p = containers[msg];
|
||||
ret.draft.headers_mut().insert(
|
||||
"Subject".into(),
|
||||
if p.show_subject() {
|
||||
format!(
|
||||
"Re: {}",
|
||||
mailbox.collection[p.message().unwrap()].subject().clone()
|
||||
)
|
||||
} else {
|
||||
mailbox.collection[p.message().unwrap()].subject().into()
|
||||
},
|
||||
);
|
||||
let parent_message = &mailbox.collection[p.message().unwrap()];
|
||||
ret.draft.headers_mut().insert(
|
||||
"References".into(),
|
||||
format!(
|
||||
"{} {}",
|
||||
parent_message
|
||||
.references()
|
||||
.iter()
|
||||
.fold(String::new(), |mut acc, x| {
|
||||
if !acc.is_empty() {
|
||||
acc.push(' ');
|
||||
}
|
||||
acc.push_str(&x.to_string());
|
||||
acc
|
||||
}),
|
||||
parent_message.message_id()
|
||||
),
|
||||
);
|
||||
ret.draft
|
||||
.headers_mut()
|
||||
.insert("In-Reply-To".into(), parent_message.message_id().into());
|
||||
ret.draft
|
||||
.headers_mut()
|
||||
.insert("To".into(), parent_message.field_from_to_string());
|
||||
ret.draft
|
||||
.headers_mut()
|
||||
.insert("Cc".into(), parent_message.field_cc_to_string());
|
||||
|
||||
ret.account_cursor = coordinates.0;
|
||||
ret.reply_context = Some((
|
||||
(coordinates.1, coordinates.2),
|
||||
Box::new(ThreadView::new(coordinates, Some(msg), context)),
|
||||
));
|
||||
ret
|
||||
}
|
||||
|
||||
fn draw_header_table(&mut self, grid: &mut CellBuffer, area: Area) {
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
|
||||
let headers = self.draft.headers();
|
||||
{
|
||||
let (mut x, mut y) = upper_left;
|
||||
for k in &["Date", "From", "To", "Subject"] {
|
||||
for k in &["Date", "From", "To", "Cc", "Bcc", "Subject"] {
|
||||
let update = {
|
||||
let (x, y) = write_string_to_grid(
|
||||
k,
|
||||
|
@ -127,22 +226,37 @@ impl Composer {
|
|||
|
||||
impl Component for Composer {
|
||||
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
|
||||
if self.dirty {
|
||||
self.draft.headers_mut().insert(
|
||||
"From".into(),
|
||||
get_display_name(context, self.account_cursor),
|
||||
);
|
||||
if !self.initialized {
|
||||
clear_area(grid, area);
|
||||
self.initialized = true;
|
||||
}
|
||||
|
||||
let upper_left = upper_left!(area);
|
||||
let bottom_right = bottom_right!(area);
|
||||
|
||||
let upper_left = set_y(upper_left, get_y(upper_left) + 1);
|
||||
let header_height = 5;
|
||||
let width = width!(area);
|
||||
let width = if width!(area) > 80 && self.reply_context.is_some() {
|
||||
width!(area) / 2
|
||||
} else {
|
||||
width!(area)
|
||||
};
|
||||
|
||||
let mid = if width > 80 {
|
||||
let width = width - 80;
|
||||
let mid = width / 2;
|
||||
let mid = if self.reply_context.is_some() {
|
||||
width!(area) / 2 + width / 2
|
||||
} else {
|
||||
width / 2
|
||||
};
|
||||
|
||||
if self.reply_context.is_some() {
|
||||
for i in get_y(upper_left)..=get_y(bottom_right) {
|
||||
set_and_join_box(grid, (mid, i), VERT_BOUNDARY);
|
||||
grid[(mid, i)].set_fg(Color::Default);
|
||||
grid[(mid, i)].set_bg(Color::Default);
|
||||
}
|
||||
}
|
||||
|
||||
if self.dirty {
|
||||
for i in get_y(upper_left)..=get_y(bottom_right) {
|
||||
|
@ -159,6 +273,12 @@ impl Component for Composer {
|
|||
0
|
||||
};
|
||||
|
||||
if width > 80 && self.reply_context.is_some() {
|
||||
let area = (upper_left, set_x(bottom_right, mid - 1));
|
||||
let view = &mut self.reply_context.as_mut().unwrap().1;
|
||||
view.draw(grid, area, context);
|
||||
}
|
||||
|
||||
if self.dirty {
|
||||
for i in get_x(upper_left) + mid + 1..=get_x(upper_left) + mid + 79 {
|
||||
//set_and_join_box(grid, (i, header_height), HORZ_BOUNDARY);
|
||||
|
@ -174,26 +294,62 @@ impl Component for Composer {
|
|||
);
|
||||
|
||||
if self.dirty {
|
||||
context.dirty_areas.push_back(area);
|
||||
self.draft.headers_mut().insert(
|
||||
"From".into(),
|
||||
get_display_name(context, self.account_cursor),
|
||||
);
|
||||
self.dirty = false;
|
||||
}
|
||||
match self.mode {
|
||||
ViewMode::Overview => {
|
||||
self.draw_header_table(grid, header_area, context);
|
||||
self.pager.draw(grid, body_area, context);
|
||||
|
||||
/* Regardless of view mode, do the following */
|
||||
clear_area(grid, header_area);
|
||||
clear_area(grid, body_area);
|
||||
self.draw_header_table(grid, header_area);
|
||||
self.pager.draw(grid, body_area, context);
|
||||
|
||||
if let ViewMode::Discard(_) = self.mode {
|
||||
let mid_x = width!(area) / 2;
|
||||
let mid_y = height!(area) / 2;
|
||||
|
||||
for i in mid_x - 40..=mid_x + 40 {
|
||||
set_and_join_box(grid, (i, mid_y - 11), HORZ_BOUNDARY);
|
||||
|
||||
set_and_join_box(grid, (i, mid_y + 11), HORZ_BOUNDARY);
|
||||
}
|
||||
|
||||
for i in mid_y - 11..=mid_y + 11 {
|
||||
set_and_join_box(grid, (mid_x - 40, i), VERT_BOUNDARY);
|
||||
|
||||
set_and_join_box(grid, (mid_x + 40, i), VERT_BOUNDARY);
|
||||
}
|
||||
}
|
||||
|
||||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
|
||||
fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool {
|
||||
if self.pager.process_event(event, context) {
|
||||
return true;
|
||||
match (&mut self.mode, &mut self.reply_context) {
|
||||
(ViewMode::Pager, _) => {
|
||||
/* Cannot mutably borrow in pattern guard, pah! */
|
||||
if self.pager.process_event(event, context) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
(ViewMode::Overview, Some((_, ref mut view))) => {
|
||||
if view.process_event(event, context) {
|
||||
self.dirty = true;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
match event.event_type {
|
||||
UIEventType::Resize => {
|
||||
self.dirty = true;
|
||||
self.initialized = false;
|
||||
}
|
||||
/* Switch e-mail From: field to the `left` configured account. */
|
||||
UIEventType::Input(Key::Left) => {
|
||||
self.account_cursor = self.account_cursor.saturating_sub(1);
|
||||
self.draft.headers_mut().insert(
|
||||
|
@ -203,6 +359,7 @@ impl Component for Composer {
|
|||
self.dirty = true;
|
||||
return true;
|
||||
}
|
||||
/* Switch e-mail From: field to the `right` configured account. */
|
||||
UIEventType::Input(Key::Right) => {
|
||||
if self.account_cursor + 1 < context.accounts.len() {
|
||||
self.account_cursor += 1;
|
||||
|
@ -214,7 +371,38 @@ impl Component for Composer {
|
|||
}
|
||||
return true;
|
||||
}
|
||||
UIEventType::Input(Key::Char('\n')) => {
|
||||
UIEventType::Input(Key::Char(k)) if self.mode.is_discard() => {
|
||||
match (k, &self.mode) {
|
||||
('y', ViewMode::Discard(u)) => {
|
||||
context.replies.push_back(UIEvent {
|
||||
id: 0,
|
||||
event_type: UIEventType::Action(Tab(Kill(u.clone()))),
|
||||
});
|
||||
return true;
|
||||
}
|
||||
('n', _) => {}
|
||||
_ => {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
self.mode = ViewMode::Overview;
|
||||
self.set_dirty();
|
||||
return true;
|
||||
}
|
||||
/* Switch to Overview mode if we're on Pager mode */
|
||||
UIEventType::Input(Key::Char('o')) if self.mode.is_pager() => {
|
||||
self.mode = ViewMode::Overview;
|
||||
self.set_dirty();
|
||||
return true;
|
||||
}
|
||||
/* Switch to Pager mode if we're on Overview mode */
|
||||
UIEventType::Input(Key::Char('p')) if self.mode.is_overview() => {
|
||||
self.mode = ViewMode::Pager;
|
||||
self.set_dirty();
|
||||
return true;
|
||||
}
|
||||
/* Edit draft in $EDITOR */
|
||||
UIEventType::Input(Key::Char('e')) => {
|
||||
use std::process::{Command, Stdio};
|
||||
/* Kill input thread so that spawned command can be sole receiver of stdin */
|
||||
{
|
||||
|
@ -235,10 +423,15 @@ impl Component for Composer {
|
|||
let result = f.read_to_string();
|
||||
self.draft = Draft::from_str(result.as_str()).unwrap();
|
||||
self.pager.update_from_str(self.draft.body());
|
||||
context.replies.push_back(UIEvent {
|
||||
id: 0,
|
||||
event_type: UIEventType::Fork(ForkType::Finished),
|
||||
});
|
||||
context.restore_input();
|
||||
self.dirty = true;
|
||||
return true;
|
||||
}
|
||||
// TODO: Replace EditDraft with compose tabs
|
||||
UIEventType::Input(Key::Char('m')) => {
|
||||
let mut f =
|
||||
create_temp_file(self.draft.to_string().unwrap().as_str().as_bytes(), None);
|
||||
|
@ -256,11 +449,24 @@ impl Component for Composer {
|
|||
|
||||
fn is_dirty(&self) -> bool {
|
||||
self.dirty || self.pager.is_dirty()
|
||||
|| self
|
||||
.reply_context
|
||||
.as_ref()
|
||||
.map(|(_, p)| p.is_dirty())
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn set_dirty(&mut self) {
|
||||
self.dirty = true;
|
||||
self.initialized = false;
|
||||
self.pager.set_dirty();
|
||||
if let Some((_, ref mut view)) = self.reply_context {
|
||||
view.set_dirty();
|
||||
}
|
||||
}
|
||||
|
||||
fn kill(&mut self, uuid: Uuid) {
|
||||
self.mode = ViewMode::Discard(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -303,6 +303,7 @@ impl Component for CompactListing {
|
|||
if self.length == 0 && self.dirty {
|
||||
clear_area(grid, area);
|
||||
context.dirty_areas.push_back(area);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Render the mail body in a pager */
|
||||
|
@ -312,7 +313,7 @@ impl Component for CompactListing {
|
|||
}
|
||||
return;
|
||||
}
|
||||
self.view = Some(ThreadView::new(self.cursor_pos, context));
|
||||
self.view = Some(ThreadView::new(self.cursor_pos, None, context));
|
||||
self.view.as_mut().unwrap().draw(grid, area, context);
|
||||
self.dirty = false;
|
||||
}
|
||||
|
@ -418,15 +419,6 @@ impl Component for CompactListing {
|
|||
self.dirty = true;
|
||||
}
|
||||
UIEventType::Action(ref action) => match action {
|
||||
Action::PlainListing(PlainListingAction::ToggleThreaded) => {
|
||||
context.accounts[self.cursor_pos.0]
|
||||
.runtime_settings
|
||||
.conf_mut()
|
||||
.toggle_threaded();
|
||||
self.refresh_mailbox(context);
|
||||
self.dirty = true;
|
||||
return true;
|
||||
}
|
||||
Action::ViewMailbox(idx) => {
|
||||
self.new_cursor_pos.1 = *idx;
|
||||
self.dirty = true;
|
||||
|
@ -446,7 +438,8 @@ impl Component for CompactListing {
|
|||
self.dirty = true;
|
||||
self.refresh_mailbox(context);
|
||||
return true;
|
||||
} // _ => {}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
@ -760,7 +760,7 @@ impl Component for PlainListing {
|
|||
self.dirty = true;
|
||||
}
|
||||
UIEventType::Action(ref action) => match action {
|
||||
Action::PlainListing(PlainListingAction::ToggleThreaded) => {
|
||||
Action::Listing(ListingAction::ToggleThreaded) => {
|
||||
context.accounts[self.cursor_pos.0]
|
||||
.runtime_settings
|
||||
.conf_mut()
|
||||
|
@ -788,7 +788,8 @@ impl Component for PlainListing {
|
|||
self.dirty = true;
|
||||
self.refresh_mailbox(context);
|
||||
return true;
|
||||
} // _ => {}
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
|
|
|
@ -70,6 +70,7 @@ impl Component for HtmlView {
|
|||
if self.pager.process_event(event, context) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let UIEventType::Input(Key::Char('v')) = event.event_type {
|
||||
// TODO: Optional filter that removes outgoing resource requests (images and
|
||||
// scripts)
|
||||
|
@ -98,5 +99,7 @@ impl Component for HtmlView {
|
|||
fn is_dirty(&self) -> bool {
|
||||
self.pager.is_dirty()
|
||||
}
|
||||
fn set_dirty(&mut self) {}
|
||||
fn set_dirty(&mut self) {
|
||||
self.pager.set_dirty();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -309,15 +309,19 @@ impl Component for MailView {
|
|||
let body = envelope.body(op);
|
||||
match self.mode {
|
||||
ViewMode::Attachment(aidx) if body.attachments()[aidx].is_html() => {
|
||||
self.pager = None;
|
||||
self.subview = Some(Box::new(HtmlView::new(decode(
|
||||
&body.attachments()[aidx],
|
||||
None,
|
||||
))));
|
||||
self.mode = ViewMode::Subview;
|
||||
}
|
||||
ViewMode::Normal if body.is_html() => {
|
||||
self.subview = Some(Box::new(HtmlView::new(decode(&body, None))));
|
||||
self.pager = None;
|
||||
self.mode = ViewMode::Subview;
|
||||
}
|
||||
ViewMode::Subview => {}
|
||||
_ => {
|
||||
let buf = {
|
||||
let text = self.attachment_to_text(&body);
|
||||
|
@ -330,27 +334,43 @@ impl Component for MailView {
|
|||
self.pager.as_mut().map(|p| p.cursor_pos())
|
||||
};
|
||||
self.pager = Some(Pager::from_buf(buf.split_newlines(), cursor_pos));
|
||||
self.subview = None;
|
||||
}
|
||||
};
|
||||
self.dirty = false;
|
||||
}
|
||||
if let Some(s) = self.subview.as_mut() {
|
||||
s.draw(grid, (set_y(upper_left, y + 1), bottom_right), context);
|
||||
} else if let Some(p) = self.pager.as_mut() {
|
||||
p.draw(grid, (set_y(upper_left, y + 1), bottom_right), context);
|
||||
match self.mode {
|
||||
ViewMode::Subview => {
|
||||
if let Some(s) = self.subview.as_mut() {
|
||||
s.draw(grid, (set_y(upper_left, y + 1), bottom_right), context);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if let Some(p) = self.pager.as_mut() {
|
||||
p.draw(grid, (set_y(upper_left, y + 1), bottom_right), context);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool {
|
||||
if let Some(ref mut sub) = self.subview {
|
||||
if sub.process_event(event, context) {
|
||||
return true;
|
||||
match self.mode {
|
||||
ViewMode::Subview => {
|
||||
if let Some(s) = self.subview.as_mut() {
|
||||
if s.process_event(event, context) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if let Some(ref mut p) = self.pager {
|
||||
if p.process_event(event, context) {
|
||||
return true;
|
||||
_ => {
|
||||
if let Some(p) = self.pager.as_mut() {
|
||||
if p.process_event(event, context) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match event.event_type {
|
||||
UIEventType::Input(Key::Esc) | UIEventType::Input(Key::Alt('')) => {
|
||||
self.cmd_buf.clear();
|
||||
|
@ -542,5 +562,18 @@ impl Component for MailView {
|
|||
}
|
||||
fn set_dirty(&mut self) {
|
||||
self.dirty = true;
|
||||
match self.mode {
|
||||
ViewMode::Normal => {
|
||||
if let Some(p) = self.pager.as_mut() {
|
||||
p.set_dirty();
|
||||
}
|
||||
}
|
||||
ViewMode::Subview => {
|
||||
if let Some(s) = self.subview.as_mut() {
|
||||
s.set_dirty();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -46,7 +46,17 @@ pub struct ThreadView {
|
|||
}
|
||||
|
||||
impl ThreadView {
|
||||
pub fn new(coordinates: (usize, usize, usize), context: &Context) -> Self {
|
||||
/*
|
||||
* coordinates: (account index, mailbox index, root set container index)
|
||||
* expanded_idx: optional position of expanded entry when we render the threadview. Default
|
||||
* expanded message is the last one.
|
||||
* context: current context
|
||||
*/
|
||||
pub fn new(
|
||||
coordinates: (usize, usize, usize),
|
||||
expanded_idx: Option<usize>,
|
||||
context: &Context,
|
||||
) -> Self {
|
||||
/* stack to push thread messages in order in order to pop and print them later */
|
||||
let mut stack: Vec<(usize, usize)> = Vec::with_capacity(32);
|
||||
let mailbox = &context.accounts[coordinates.0][coordinates.1]
|
||||
|
@ -78,6 +88,13 @@ impl ThreadView {
|
|||
let entry = view.make_entry(context, (ind, idx, line));
|
||||
view.entries.push(entry);
|
||||
line += 1;
|
||||
match expanded_idx {
|
||||
Some(expanded_idx) if expanded_idx == idx => {
|
||||
view.new_expanded_pos = view.entries.len().saturating_sub(1);
|
||||
view.expanded_pos = view.new_expanded_pos + 1;
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
let container = &threads.containers()[idx];
|
||||
if let Some(i) = container.next_sibling() {
|
||||
stack.push((ind, i));
|
||||
|
@ -87,8 +104,10 @@ impl ThreadView {
|
|||
stack.push((ind + 1, i));
|
||||
}
|
||||
}
|
||||
view.new_expanded_pos = view.entries.len().saturating_sub(1);
|
||||
view.expanded_pos = view.new_expanded_pos + 1;
|
||||
if expanded_idx.is_none() {
|
||||
view.new_expanded_pos = view.entries.len().saturating_sub(1);
|
||||
view.expanded_pos = view.new_expanded_pos + 1;
|
||||
}
|
||||
|
||||
let height = 2 * view.entries.len() + 1;
|
||||
let mut width = 0;
|
||||
|
@ -545,6 +564,16 @@ impl Component for ThreadView {
|
|||
return true;
|
||||
}
|
||||
match event.event_type {
|
||||
UIEventType::Input(Key::Char('R')) => {
|
||||
context.replies.push_back(UIEvent {
|
||||
id: 0,
|
||||
event_type: UIEventType::Reply(
|
||||
self.coordinates,
|
||||
self.entries[self.expanded_pos].index.1,
|
||||
),
|
||||
});
|
||||
return true;
|
||||
}
|
||||
UIEventType::Input(Key::Up) => {
|
||||
if self.cursor_pos > 0 {
|
||||
self.new_cursor_pos = self.new_cursor_pos.saturating_sub(1);
|
||||
|
@ -574,7 +603,7 @@ impl Component for ThreadView {
|
|||
return true;
|
||||
}
|
||||
UIEventType::Resize => {
|
||||
self.dirty = true;
|
||||
self.set_dirty();
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
@ -584,6 +613,7 @@ impl Component for ThreadView {
|
|||
self.dirty || self.mailview.is_dirty()
|
||||
}
|
||||
fn set_dirty(&mut self) {
|
||||
self.initiated = false;
|
||||
self.dirty = true;
|
||||
self.mailview.set_dirty();
|
||||
}
|
||||
|
|
|
@ -37,7 +37,9 @@ pub use self::utilities::*;
|
|||
|
||||
use std::fmt;
|
||||
use std::fmt::{Debug, Display};
|
||||
use std::ops::Deref;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use uuid::Uuid;
|
||||
|
||||
use super::{Key, StatusEvent, UIEvent, UIEventType};
|
||||
/// The upper and lower boundary char.
|
||||
|
@ -62,20 +64,46 @@ const LIGHT_DOWN_AND_HORIZONTAL: char = '┬';
|
|||
|
||||
const LIGHT_UP_AND_HORIZONTAL: char = '┴';
|
||||
|
||||
/// `Entity` is a container for Components. Totally useless now so if it is not useful in the
|
||||
/// future (ie hold some information, id or state) it should be removed.
|
||||
/// `Entity` is a container for Components.
|
||||
#[derive(Debug)]
|
||||
pub struct Entity {
|
||||
//context: VecDeque,
|
||||
id: Uuid,
|
||||
pub component: Box<Component>, // more than one?
|
||||
}
|
||||
|
||||
impl From<Box<Component>> for Entity {
|
||||
fn from(kind: Box<Component>) -> Entity {
|
||||
Entity {
|
||||
id: Uuid::new_v4(),
|
||||
component: kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: 'static> From<Box<C>> for Entity
|
||||
where
|
||||
C: Component,
|
||||
{
|
||||
fn from(kind: Box<C>) -> Entity {
|
||||
Entity {
|
||||
id: Uuid::new_v4(),
|
||||
component: kind,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Entity {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
Display::fmt(&self.component, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Entity {
|
||||
fn deref_mut(&mut self) -> &mut Box<Component> {
|
||||
&mut self.component
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Entity {
|
||||
type Target = Box<Component>;
|
||||
|
||||
|
@ -85,6 +113,9 @@ impl Deref for Entity {
|
|||
}
|
||||
|
||||
impl Entity {
|
||||
pub fn uuid(&self) -> &Uuid {
|
||||
&self.id
|
||||
}
|
||||
/// Pass events to child component.
|
||||
pub fn rcv_event(&mut self, event: &UIEvent, context: &mut Context) -> bool {
|
||||
self.component.process_event(&event, context)
|
||||
|
@ -101,6 +132,7 @@ pub trait Component: Display + Debug {
|
|||
true
|
||||
}
|
||||
fn set_dirty(&mut self);
|
||||
fn kill(&mut self, _uuid: Uuid) {}
|
||||
}
|
||||
|
||||
fn new_draft(_context: &mut Context) -> Vec<u8> {
|
||||
|
@ -113,6 +145,7 @@ fn new_draft(_context: &mut Context) -> Vec<u8> {
|
|||
v.into_bytes()
|
||||
}
|
||||
|
||||
/*
|
||||
pub(crate) fn is_box_char(ch: char) -> bool {
|
||||
match ch {
|
||||
HORZ_BOUNDARY | VERT_BOUNDARY => true,
|
||||
|
@ -120,7 +153,6 @@ pub(crate) fn is_box_char(ch: char) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* pub(crate) fn is_box_char(ch: char) -> bool {
|
||||
* match ch {
|
||||
* '└' | '─' | '┘' | '┴' | '┌' | '│' | '├' | '┐' | '┬' | '┤' | '┼' | '╷' | '╵' | '╴' | '╶' => true,
|
||||
|
|
|
@ -712,14 +712,17 @@ impl Component for Progress {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct Tabbed {
|
||||
children: Vec<Box<Component>>,
|
||||
children: Vec<Entity>,
|
||||
cursor_pos: usize,
|
||||
}
|
||||
|
||||
impl Tabbed {
|
||||
pub fn new(children: Vec<Box<Component>>) -> Self {
|
||||
Tabbed {
|
||||
children,
|
||||
children: children
|
||||
.into_iter()
|
||||
.map(|x: Box<Component>| Entity::from(x))
|
||||
.collect(),
|
||||
cursor_pos: 0,
|
||||
}
|
||||
}
|
||||
|
@ -748,13 +751,15 @@ impl Tabbed {
|
|||
}
|
||||
let (cols, _) = grid.size();
|
||||
let cslice: &mut [Cell] = grid;
|
||||
for c in cslice[(y * cols) + x..(y * cols) + cols].iter_mut() {
|
||||
for c in cslice[(y * cols) + x - 1..(y * cols) + cols].iter_mut() {
|
||||
c.set_bg(Color::Byte(7));
|
||||
c.set_ch(' ');
|
||||
}
|
||||
|
||||
context.dirty_areas.push_back(area);
|
||||
}
|
||||
pub fn add_component(&mut self, new: Box<Component>) {
|
||||
self.children.push(new);
|
||||
self.children.push(Entity::from(new));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -787,15 +792,44 @@ impl Component for Tabbed {
|
|||
}
|
||||
}
|
||||
fn process_event(&mut self, event: &UIEvent, context: &mut Context) -> bool {
|
||||
if let UIEventType::Input(Key::Char('T')) = event.event_type {
|
||||
self.cursor_pos = (self.cursor_pos + 1) % self.children.len();
|
||||
self.children[self.cursor_pos].set_dirty();
|
||||
return true;
|
||||
match event.event_type {
|
||||
UIEventType::Input(Key::Char('T')) => {
|
||||
self.cursor_pos = (self.cursor_pos + 1) % self.children.len();
|
||||
self.set_dirty();
|
||||
return true;
|
||||
}
|
||||
UIEventType::Reply(coordinates, msg) => {
|
||||
self.add_component(Box::new(Composer::with_context(coordinates, msg, context)));
|
||||
self.cursor_pos = self.children.len() - 1;
|
||||
self.children[self.cursor_pos].set_dirty();
|
||||
return true;
|
||||
}
|
||||
UIEventType::Action(Tab(Close)) => {
|
||||
let uuid = self.children[self.cursor_pos].uuid().clone();
|
||||
self.children[self.cursor_pos].kill(uuid);
|
||||
return true;
|
||||
}
|
||||
UIEventType::Action(Tab(Kill(ref uuid))) => {
|
||||
if let Some(c_idx) = self.children.iter().position(|x| x.uuid() == uuid) {
|
||||
self.children.remove(c_idx);
|
||||
self.cursor_pos = self.cursor_pos.saturating_sub(1);
|
||||
self.set_dirty();
|
||||
return true;
|
||||
} else {
|
||||
eprintln!(
|
||||
"DEBUG: Child entity with uuid {:?} not found.\nList: {:?}",
|
||||
uuid, self.children
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
self.children[self.cursor_pos].process_event(event, context)
|
||||
}
|
||||
fn is_dirty(&self) -> bool {
|
||||
self.children[self.cursor_pos].is_dirty()
|
||||
}
|
||||
fn set_dirty(&mut self) {}
|
||||
fn set_dirty(&mut self) {
|
||||
self.children[self.cursor_pos].set_dirty();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,15 +25,25 @@
|
|||
|
||||
pub use melib::mailbox::{SortField, SortOrder};
|
||||
|
||||
extern crate uuid;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum PlainListingAction {
|
||||
pub enum ListingAction {
|
||||
ToggleThreaded,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum TabAction {
|
||||
Close,
|
||||
Kill(Uuid),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Action {
|
||||
PlainListing(PlainListingAction),
|
||||
Listing(ListingAction),
|
||||
ViewMailbox(usize),
|
||||
Sort(SortField, SortOrder),
|
||||
SubSort(SortField, SortOrder),
|
||||
Tab(TabAction),
|
||||
}
|
||||
|
|
|
@ -21,10 +21,13 @@
|
|||
|
||||
/*! A parser module for user commands passed through the Ex mode.
|
||||
*/
|
||||
pub use melib::mailbox::{SortField, SortOrder};
|
||||
use nom::{digit, not_line_ending};
|
||||
use std;
|
||||
pub mod actions;
|
||||
pub use actions::*;
|
||||
pub use actions::Action::{self, *};
|
||||
pub use actions::ListingAction::{self, *};
|
||||
pub use actions::TabAction::{self, *};
|
||||
|
||||
named!(
|
||||
usize_c<usize>,
|
||||
|
@ -50,6 +53,7 @@ named!(
|
|||
)
|
||||
);
|
||||
|
||||
named!(close<Action>, map!(ws!(tag!("close")), |_| Tab(Close)));
|
||||
named!(
|
||||
goto<Action>,
|
||||
preceded!(tag!("b "), map!(call!(usize_c), Action::ViewMailbox))
|
||||
|
@ -57,22 +61,18 @@ named!(
|
|||
|
||||
named!(
|
||||
subsort<Action>,
|
||||
do_parse!(tag!("subsort ") >> p: pair!(sortfield, sortorder) >> (Action::SubSort(p.0, p.1)))
|
||||
do_parse!(tag!("subsort ") >> p: pair!(sortfield, sortorder) >> (SubSort(p.0, p.1)))
|
||||
);
|
||||
named!(
|
||||
sort<Action>,
|
||||
do_parse!(
|
||||
tag!("sort ")
|
||||
>> p: separated_pair!(sortfield, tag!(" "), sortorder)
|
||||
>> (Action::Sort(p.0, p.1))
|
||||
tag!("sort ") >> p: separated_pair!(sortfield, tag!(" "), sortorder) >> (Sort(p.0, p.1))
|
||||
)
|
||||
);
|
||||
|
||||
named!(
|
||||
threaded<Action>,
|
||||
map!(ws!(tag!("threaded")), |_| {
|
||||
Action::PlainListing(PlainListingAction::ToggleThreaded)
|
||||
})
|
||||
map!(ws!(tag!("threaded")), |_| Listing(ToggleThreaded))
|
||||
);
|
||||
named!(
|
||||
toggle<Action>,
|
||||
|
@ -80,5 +80,5 @@ named!(
|
|||
);
|
||||
|
||||
named!(pub parse_command<Action>,
|
||||
alt_complete!( goto | toggle | sort | subsort)
|
||||
alt_complete!( goto | toggle | sort | subsort | close)
|
||||
);
|
||||
|
|
|
@ -39,6 +39,8 @@ use termion::raw::IntoRawMode;
|
|||
use termion::screen::AlternateScreen;
|
||||
use termion::{clear, cursor, style};
|
||||
|
||||
type StateStdout = termion::screen::AlternateScreen<termion::raw::RawTerminal<std::io::Stdout>>;
|
||||
|
||||
struct InputHandler {
|
||||
rx: Receiver<bool>,
|
||||
tx: Sender<bool>,
|
||||
|
@ -133,12 +135,12 @@ impl Context {
|
|||
|
||||
/// A State object to manage and own components and entities of the UI. `State` is responsible for
|
||||
/// managing the terminal and interfacing with `melib`
|
||||
pub struct State<W: Write> {
|
||||
pub struct State {
|
||||
cols: usize,
|
||||
rows: usize,
|
||||
|
||||
grid: CellBuffer,
|
||||
stdout: Option<termion::screen::AlternateScreen<termion::raw::RawTerminal<W>>>,
|
||||
stdout: Option<StateStdout>,
|
||||
child: Option<ForkType>,
|
||||
pub mode: UIMode,
|
||||
entities: Vec<Entity>,
|
||||
|
@ -149,7 +151,7 @@ pub struct State<W: Write> {
|
|||
threads: FnvHashMap<thread::ThreadId, (chan::Sender<bool>, thread::JoinHandle<()>)>,
|
||||
}
|
||||
|
||||
impl<W: Write> Drop for State<W> {
|
||||
impl Drop for State {
|
||||
fn drop(&mut self) {
|
||||
// When done, restore the defaults to avoid messing with the terminal.
|
||||
write!(
|
||||
|
@ -164,13 +166,13 @@ impl<W: Write> Drop for State<W> {
|
|||
}
|
||||
}
|
||||
|
||||
impl Default for State<std::io::Stdout> {
|
||||
impl Default for State {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl State<std::io::Stdout> {
|
||||
impl State {
|
||||
pub fn new() -> Self {
|
||||
/* Create a channel to communicate with other threads. The main process is the sole receiver.
|
||||
* */
|
||||
|
@ -370,8 +372,7 @@ impl State<std::io::Stdout> {
|
|||
).unwrap();
|
||||
self.flush();
|
||||
}
|
||||
}
|
||||
impl<W: Write> State<W> {
|
||||
|
||||
pub fn receiver(&self) -> Receiver<ThreadEvent> {
|
||||
self.context.receiver.clone()
|
||||
}
|
||||
|
@ -509,7 +510,15 @@ impl<W: Write> State<W> {
|
|||
UIEventType::Fork(child) => {
|
||||
self.mode = UIMode::Fork;
|
||||
self.child = Some(child);
|
||||
self.flush();
|
||||
if let Some(ForkType::Finished) = self.child {
|
||||
/*
|
||||
* Fork has finished in the past.
|
||||
* We're back in the AlternateScreen, but the cursor is reset to Shown, so fix
|
||||
* it.
|
||||
*/
|
||||
write!(self.stdout(), "{}", cursor::Hide,).unwrap();
|
||||
self.flush();
|
||||
}
|
||||
return;
|
||||
}
|
||||
UIEventType::EditDraft(mut file) => {
|
||||
|
@ -538,8 +547,8 @@ impl<W: Write> State<W> {
|
|||
self.entities[i].rcv_event(
|
||||
&UIEvent {
|
||||
id: 0,
|
||||
event_type: UIEventType::Action(Action::PlainListing(
|
||||
PlainListingAction::ToggleThreaded,
|
||||
event_type: UIEventType::Action(Action::Listing(
|
||||
ListingAction::ToggleThreaded,
|
||||
)),
|
||||
},
|
||||
&mut self.context,
|
||||
|
@ -578,7 +587,7 @@ impl<W: Write> State<W> {
|
|||
}
|
||||
|
||||
pub fn try_wait_on_child(&mut self) -> Option<bool> {
|
||||
if match self.child {
|
||||
let should_return_flag = match self.child {
|
||||
Some(ForkType::NewDraft(_, ref mut c)) => {
|
||||
let mut w = c.try_wait();
|
||||
match w {
|
||||
|
@ -599,10 +608,16 @@ impl<W: Write> State<W> {
|
|||
}
|
||||
}
|
||||
}
|
||||
Some(ForkType::Finished) => {
|
||||
/* Fork has already finished */
|
||||
std::mem::replace(&mut self.child, None);
|
||||
return None;
|
||||
}
|
||||
_ => {
|
||||
return None;
|
||||
}
|
||||
} {
|
||||
};
|
||||
if should_return_flag {
|
||||
if let Some(ForkType::NewDraft(f, _)) = std::mem::replace(&mut self.child, None) {
|
||||
self.rcv_event(UIEvent {
|
||||
id: 0,
|
||||
|
@ -618,7 +633,7 @@ impl<W: Write> State<W> {
|
|||
s.flush().unwrap();
|
||||
}
|
||||
}
|
||||
fn stdout(&mut self) -> &mut termion::screen::AlternateScreen<termion::raw::RawTerminal<W>> {
|
||||
fn stdout(&mut self) -> &mut StateStdout {
|
||||
self.stdout.as_mut().unwrap()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -40,6 +40,7 @@ use melib::RefreshEvent;
|
|||
use std;
|
||||
use std::fmt;
|
||||
use std::thread;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum StatusEvent {
|
||||
|
@ -71,6 +72,7 @@ impl From<RefreshEvent> for ThreadEvent {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub enum ForkType {
|
||||
Finished, // Already finished fork, we only want to restore input/output
|
||||
Generic(std::process::Child),
|
||||
NewDraft(File, std::process::Child),
|
||||
}
|
||||
|
@ -91,7 +93,10 @@ pub enum UIEventType {
|
|||
EditDraft(File),
|
||||
Action(Action),
|
||||
StatusEvent(StatusEvent),
|
||||
MailboxUpdate((usize, usize)),
|
||||
MailboxUpdate((usize, usize)), // (account_idx, mailbox_idx)
|
||||
|
||||
Reply((usize, usize, usize), usize), // thread coordinates (account, mailbox, root_set idx) and message idx
|
||||
EntityKill(Uuid),
|
||||
|
||||
StartupCheck,
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue