ui/compose: add modification detection

Detect if modifications were done to the draft in the compose tab so
that we can ask for confirmation if user wants to quit an unsaved draft.
embed
Manos Pitsidianakis 2019-10-20 11:17:54 +03:00
parent 5beed91df2
commit 1a02491f04
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
6 changed files with 76 additions and 35 deletions

15
Cargo.lock generated
View File

@ -645,18 +645,6 @@ dependencies = [
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
] ]
[[package]]
name = "nix"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.64 (registry+https://github.com/rust-lang/crates.io-index)",
"void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]] [[package]]
name = "nodrop" name = "nodrop"
version = "0.1.13" version = "0.1.13"
@ -1155,7 +1143,6 @@ version = "0.3.2"
dependencies = [ dependencies = [
"melib 0.3.2", "melib 0.3.2",
"text_processing 0.3.2", "text_processing 0.3.2",
"ui 0.3.2",
] ]
[[package]] [[package]]
@ -1194,7 +1181,6 @@ dependencies = [
"linkify 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", "linkify 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"melib 0.3.2", "melib 0.3.2",
"mime_apps 0.2.0 (git+https://git.meli.delivery/meli/mime_apps)", "mime_apps 0.2.0 (git+https://git.meli.delivery/meli/mime_apps)",
"nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
"nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)", "nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
"notify 4.0.12 (registry+https://github.com/rust-lang/crates.io-index)", "notify 4.0.12 (registry+https://github.com/rust-lang/crates.io-index)",
"notify-rust 3.6.0 (registry+https://github.com/rust-lang/crates.io-index)", "notify-rust 3.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1372,7 +1358,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
"checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e" "checksum native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "4b2df1a4c22fd44a62147fd8f13dd0f95c9d8ca7b2610299b2a2f9cf8964274e"
"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88" "checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
"checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce" "checksum nix 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6c722bee1037d430d0f8e687bbdbf222f27cc6e4e68d5caf630857bb2b6dbdce"
"checksum nix 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3b2e0b4f3320ed72aaedb9a5ac838690a8047c7b275da22711fddff4f8a14229"
"checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945" "checksum nodrop 0.1.13 (registry+https://github.com/rust-lang/crates.io-index)" = "2f9667ddcc6cc8a43afc9b7917599d7216aa09c463919ea32c59ed6cac8bc945"
"checksum nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b" "checksum nom 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05aec50c70fd288702bcd93284a8444607f3292dbdf2a30de5ea5dcdbe72287b"
"checksum notify 4.0.12 (registry+https://github.com/rust-lang/crates.io-index)" = "3572d71f13ea8ed41867accd971fd564aa75934cf7a1fae03ddb8c74a8a49943" "checksum notify 4.0.12 (registry+https://github.com/rust-lang/crates.io-index)" = "3572d71f13ea8ed41867accd971fd564aa75934cf7a1fae03ddb8c74a8a49943"

View File

@ -128,7 +128,7 @@ impl fmt::Debug for Address {
} }
/// Helper struct to return slices from a struct field on demand. /// Helper struct to return slices from a struct field on demand.
#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Copy)] #[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq, Eq, Copy)]
pub struct StrBuilder { pub struct StrBuilder {
pub offset: usize, pub offset: usize,
pub length: usize, pub length: usize,

View File

@ -24,7 +24,7 @@ use crate::email::parser::BytesExt;
use std::fmt::{Display, Formatter, Result as FmtResult}; use std::fmt::{Display, Formatter, Result as FmtResult};
use std::str; use std::str;
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Charset { pub enum Charset {
Ascii, Ascii,
UTF8, UTF8,
@ -95,7 +95,7 @@ impl Display for Charset {
} }
} }
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum MultipartType { pub enum MultipartType {
Mixed, Mixed,
Alternative, Alternative,
@ -136,7 +136,7 @@ impl From<&[u8]> for MultipartType {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum ContentType { pub enum ContentType {
Text { Text {
kind: Text, kind: Text,
@ -245,7 +245,7 @@ impl ContentType {
} }
} }
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Text { pub enum Text {
Plain, Plain,
Html, Html,
@ -274,7 +274,7 @@ impl Display for Text {
} }
} }
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)] #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub enum ContentTransferEncoding { pub enum ContentTransferEncoding {
_8Bit, _8Bit,
_7Bit, _7Bit,

View File

@ -28,7 +28,7 @@ use data_encoding::BASE64_MIME;
pub use crate::email::attachment_types::*; pub use crate::email::attachment_types::*;
#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct AttachmentBuilder { pub struct AttachmentBuilder {
pub content_type: ContentType, pub content_type: ContentType,
pub content_transfer_encoding: ContentTransferEncoding, pub content_transfer_encoding: ContentTransferEncoding,
@ -291,7 +291,7 @@ impl From<AttachmentBuilder> for Attachment {
} }
/// Immutable attachment type. /// Immutable attachment type.
#[derive(Clone, Serialize, Deserialize, PartialEq)] #[derive(Clone, Serialize, Deserialize, PartialEq, Eq)]
pub struct Attachment { pub struct Attachment {
pub content_type: ContentType, pub content_type: ContentType,
pub content_transfer_encoding: ContentTransferEncoding, pub content_transfer_encoding: ContentTransferEncoding,

View File

@ -17,7 +17,7 @@ pub mod random;
use super::parser; use super::parser;
use fnv::FnvHashMap; use fnv::FnvHashMap;
#[derive(Debug, PartialEq, Clone)] #[derive(Debug, PartialEq, Eq, Clone)]
pub struct Draft { pub struct Draft {
pub headers: FnvHashMap<String, String>, pub headers: FnvHashMap<String, String>,
pub header_order: Vec<String>, pub header_order: Vec<String>,

View File

@ -46,6 +46,7 @@ pub struct Composer {
mode: ViewMode, mode: ViewMode,
sign_mail: ToggleFlag, sign_mail: ToggleFlag,
dirty: bool, dirty: bool,
has_changes: bool,
initialized: bool, initialized: bool,
id: ComponentId, id: ComponentId,
} }
@ -65,6 +66,7 @@ impl Default for Composer {
mode: ViewMode::Edit, mode: ViewMode::Edit,
sign_mail: ToggleFlag::Unset, sign_mail: ToggleFlag::Unset,
dirty: true, dirty: true,
has_changes: false,
initialized: false, initialized: false,
id: ComponentId::new_v4(), id: ComponentId::new_v4(),
} }
@ -246,6 +248,7 @@ impl Composer {
fn draw_attachments(&self, grid: &mut CellBuffer, area: Area, context: &Context) { fn draw_attachments(&self, grid: &mut CellBuffer, area: Area, context: &Context) {
let attachments_no = self.draft.attachments().len(); let attachments_no = self.draft.attachments().len();
clear_area(grid, area);
if self.sign_mail.is_true() { if self.sign_mail.is_true() {
write_string_to_grid( write_string_to_grid(
&format!( &format!(
@ -331,7 +334,6 @@ impl Composer {
impl Component for Composer { impl Component for Composer {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
clear_area(grid, area);
let upper_left = upper_left!(area); let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area); let bottom_right = bottom_right!(area);
@ -459,6 +461,7 @@ impl Component for Composer {
), ),
false, false,
); );
clear_area(grid, ((x, y), (set_y(bottom_right, y))));
change_colors( change_colors(
grid, grid,
( (
@ -468,28 +471,63 @@ impl Component for Composer {
Color::Byte(189), Color::Byte(189),
Color::Byte(167), Color::Byte(167),
); );
if mid != 0 {
clear_area(
grid,
(
pos_dec(upper_left, (0, 1)),
set_x(bottom_right, get_x(upper_left) + mid),
),
);
clear_area(
grid,
(
(
get_x(bottom_right).saturating_sub(mid),
get_y(upper_left) - 1,
),
bottom_right,
),
);
}
/* Regardless of view mode, do the following */ /* Regardless of view mode, do the following */
self.form.draw(grid, header_area, context); self.form.draw(grid, header_area, context);
self.pager.set_dirty();
self.pager.draw(grid, body_area, context);
if self.cursor == Cursor::Body {
change_colors(
grid,
(
set_y(upper_left!(body_area), get_y(bottom_right!(body_area))),
bottom_right!(body_area),
),
Color::Default,
Color::Byte(237),
);
} else {
change_colors(
grid,
(
set_y(upper_left!(body_area), get_y(bottom_right!(body_area))),
bottom_right!(body_area),
),
Color::Default,
Color::Default,
);
}
match self.mode { match self.mode {
ViewMode::ThreadView | ViewMode::Edit => { ViewMode::ThreadView | ViewMode::Edit => {}
self.pager.set_dirty();
self.pager.draw(grid, body_area, context);
}
ViewMode::SelectRecipients(ref mut s) => { ViewMode::SelectRecipients(ref mut s) => {
self.pager.set_dirty();
self.pager.draw(grid, body_area, context);
s.draw(grid, center_area(area, s.content.size()), context); s.draw(grid, center_area(area, s.content.size()), context);
} }
ViewMode::Discard(_, ref mut s) => { ViewMode::Discard(_, ref mut s) => {
self.pager.set_dirty();
self.pager.draw(grid, body_area, context);
/* Let user choose whether to quit with/without saving or cancel */ /* Let user choose whether to quit with/without saving or cancel */
s.draw(grid, center_area(area, s.content.size()), context); s.draw(grid, center_area(area, s.content.size()), context);
} }
} }
self.dirty = false;
self.draw_attachments(grid, attachment_area, context); self.draw_attachments(grid, attachment_area, context);
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
} }
@ -626,7 +664,10 @@ impl Component for Composer {
} }
_ => {} _ => {}
} }
if self.form.process_event(event, context) { if self.cursor == Cursor::Headers && self.form.process_event(event, context) {
if let UIEvent::InsertInput(_) = event {
self.has_changes = true;
}
return true; return true;
} }
@ -659,9 +700,12 @@ impl Component for Composer {
}*/ }*/
UIEvent::Input(Key::Up) => { UIEvent::Input(Key::Up) => {
self.cursor = Cursor::Headers; self.cursor = Cursor::Headers;
self.form.process_event(event, context);
self.dirty = true;
} }
UIEvent::Input(Key::Down) => { UIEvent::Input(Key::Down) => {
self.cursor = Cursor::Body; self.cursor = Cursor::Body;
self.dirty = true;
} }
/* Switch to thread view mode if we're on Edit mode */ /* Switch to thread view mode if we're on Edit mode */
UIEvent::Input(Key::Char('v')) if self.mode.is_edit() => { UIEvent::Input(Key::Char('v')) if self.mode.is_edit() => {
@ -742,6 +786,9 @@ impl Component for Composer {
let result = f.read_to_string(); let result = f.read_to_string();
let mut new_draft = Draft::from_str(result.as_str()).unwrap(); let mut new_draft = Draft::from_str(result.as_str()).unwrap();
std::mem::swap(self.draft.attachments_mut(), new_draft.attachments_mut()); std::mem::swap(self.draft.attachments_mut(), new_draft.attachments_mut());
if self.draft != new_draft {
self.has_changes = true;
}
self.draft = new_draft; self.draft = new_draft;
self.initialized = false; self.initialized = false;
context.replies.push_back(UIEvent::Fork(ForkType::Finished)); context.replies.push_back(UIEvent::Fork(ForkType::Finished));
@ -828,6 +875,11 @@ impl Component for Composer {
} }
fn kill(&mut self, uuid: Uuid, context: &mut Context) { fn kill(&mut self, uuid: Uuid, context: &mut Context) {
if !self.has_changes {
context.replies.push_back(UIEvent::Action(Tab(Kill(uuid))));
return;
}
self.mode = ViewMode::Discard( self.mode = ViewMode::Discard(
uuid, uuid,
Selector::new( Selector::new(
@ -876,6 +928,10 @@ impl Component for Composer {
} }
fn can_quit_cleanly(&mut self, context: &Context) -> bool { fn can_quit_cleanly(&mut self, context: &Context) -> bool {
if !self.has_changes {
return true;
}
/* Play it safe and ask user for confirmation */ /* Play it safe and ask user for confirmation */
self.mode = ViewMode::Discard( self.mode = ViewMode::Discard(
self.id, self.id,