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)",
]
[[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]]
name = "nodrop"
version = "0.1.13"
@ -1155,7 +1143,6 @@ version = "0.3.2"
dependencies = [
"melib 0.3.2",
"text_processing 0.3.2",
"ui 0.3.2",
]
[[package]]
@ -1194,7 +1181,6 @@ dependencies = [
"linkify 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
"melib 0.3.2",
"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)",
"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)",
@ -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 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.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 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"

View File

@ -128,7 +128,7 @@ impl fmt::Debug for Address {
}
/// 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 offset: 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::str;
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum Charset {
Ascii,
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 {
Mixed,
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 {
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 {
Plain,
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 {
_8Bit,
_7Bit,

View File

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

View File

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

View File

@ -46,6 +46,7 @@ pub struct Composer {
mode: ViewMode,
sign_mail: ToggleFlag,
dirty: bool,
has_changes: bool,
initialized: bool,
id: ComponentId,
}
@ -65,6 +66,7 @@ impl Default for Composer {
mode: ViewMode::Edit,
sign_mail: ToggleFlag::Unset,
dirty: true,
has_changes: false,
initialized: false,
id: ComponentId::new_v4(),
}
@ -246,6 +248,7 @@ impl Composer {
fn draw_attachments(&self, grid: &mut CellBuffer, area: Area, context: &Context) {
let attachments_no = self.draft.attachments().len();
clear_area(grid, area);
if self.sign_mail.is_true() {
write_string_to_grid(
&format!(
@ -331,7 +334,6 @@ impl Composer {
impl Component for Composer {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
clear_area(grid, area);
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
@ -459,6 +461,7 @@ impl Component for Composer {
),
false,
);
clear_area(grid, ((x, y), (set_y(bottom_right, y))));
change_colors(
grid,
(
@ -468,28 +471,63 @@ impl Component for Composer {
Color::Byte(189),
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 */
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 {
ViewMode::ThreadView | ViewMode::Edit => {
self.pager.set_dirty();
self.pager.draw(grid, body_area, context);
}
ViewMode::ThreadView | ViewMode::Edit => {}
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);
}
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 */
s.draw(grid, center_area(area, s.content.size()), context);
}
}
self.dirty = false;
self.draw_attachments(grid, attachment_area, context);
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;
}
@ -659,9 +700,12 @@ impl Component for Composer {
}*/
UIEvent::Input(Key::Up) => {
self.cursor = Cursor::Headers;
self.form.process_event(event, context);
self.dirty = true;
}
UIEvent::Input(Key::Down) => {
self.cursor = Cursor::Body;
self.dirty = true;
}
/* Switch to thread view mode if we're on Edit mode */
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 mut new_draft = Draft::from_str(result.as_str()).unwrap();
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.initialized = false;
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) {
if !self.has_changes {
context.replies.push_back(UIEvent::Action(Tab(Kill(uuid))));
return;
}
self.mode = ViewMode::Discard(
uuid,
Selector::new(
@ -876,6 +928,10 @@ impl Component for Composer {
}
fn can_quit_cleanly(&mut self, context: &Context) -> bool {
if !self.has_changes {
return true;
}
/* Play it safe and ask user for confirmation */
self.mode = ViewMode::Discard(
self.id,