Run rustfmt

embed
Manos Pitsidianakis 2019-03-14 12:19:25 +02:00
parent e7c95ba229
commit bf038428c2
Signed by: Manos Pitsidianakis
GPG Key ID: 73627C2F690DF710
36 changed files with 1023 additions and 726 deletions

View File

@ -19,8 +19,8 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
use chrono::{DateTime, Local};
use uuid::Uuid;
use fnv::FnvHashMap;
use uuid::Uuid;
use std::ops::Deref;
@ -31,7 +31,7 @@ pub struct AddressBook {
display_name: String,
created: DateTime<Local>,
last_edited: DateTime<Local>,
cards: FnvHashMap<CardId, Card>
cards: FnvHashMap<CardId, Card>,
}
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
@ -44,7 +44,6 @@ pub struct Card {
name_prefix: String,
name_suffix: String,
//address
birthday: Option<DateTime<Local>>,
email: String,
url: String,
@ -74,7 +73,11 @@ impl AddressBook {
self.cards.contains_key(&card_id)
}
pub fn search(&self, term: &str) -> Vec<String> {
self.cards.values().filter(|c| c.email.contains(term)).map(|c| c.email.clone()).collect()
self.cards
.values()
.filter(|c| c.email.contains(term))
.map(|c| c.email.clone())
.collect()
}
}
@ -86,7 +89,6 @@ impl Deref for AddressBook {
}
}
impl Card {
pub fn new() -> Card {
Card {
@ -98,7 +100,6 @@ impl Card {
name_prefix: String::new(),
name_suffix: String::new(),
//address
birthday: None,
email: String::new(),
url: String::new(),
@ -182,7 +183,6 @@ impl Card {
pub fn extra_property(&self, key: &str) -> Option<&str> {
self.extra_properties.get(key).map(|v| v.as_str())
}
}
impl From<FnvHashMap<String, String>> for Card {

View File

@ -18,11 +18,11 @@
* You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
pub mod addressbook;
pub mod async;
pub mod conf;
pub mod error;
pub mod mailbox;
pub mod addressbook;
#[macro_use]
extern crate serde_derive;
@ -38,8 +38,8 @@ extern crate chan;
#[macro_use]
extern crate bitflags;
extern crate uuid;
extern crate fnv;
extern crate uuid;
pub use conf::*;
pub use mailbox::*;

View File

@ -27,7 +27,7 @@ extern crate xdg;
use super::{MaildirFolder, MaildirOp};
use async::*;
use conf::AccountSettings;
use error::{Result, MeliError};
use error::{MeliError, Result};
use mailbox::backends::{
BackendFolder, BackendOp, Folder, FolderHash, MailBackend, RefreshEvent, RefreshEventConsumer,
RefreshEventKind::*,
@ -132,16 +132,16 @@ fn get_file_hash(file: &Path) -> EnvelopeHash {
}
fn move_to_cur(p: PathBuf) -> PathBuf {
let mut new = p.clone();
{
let file_name = p.file_name().unwrap();
new.pop();
new.pop();
let mut new = p.clone();
{
let file_name = p.file_name().unwrap();
new.pop();
new.pop();
new.push("cur");
new.push(file_name);
new.set_extension(":2,");
}
new.push("cur");
new.push(file_name);
new.set_extension(":2,");
}
eprintln!("moved to cur: {}", new.display());
fs::rename(p, &new).unwrap();
new
@ -347,7 +347,10 @@ impl MailBackend for MaildirType {
}
}
Err(MeliError::new(format!("'{}' is not a valid folder.", folder)))
Err(MeliError::new(format!(
"'{}' is not a valid folder.",
folder
)))
}
}
@ -447,13 +450,13 @@ impl MaildirType {
let thunk = move || {
let mut path = path.clone();
let cache_dir = cache_dir.clone();
path.push("new");
for d in path.read_dir()? {
if let Ok(p) = d {
move_to_cur(p.path());
}
path.push("new");
for d in path.read_dir()? {
if let Ok(p) = d {
move_to_cur(p.path());
}
path.pop();
}
path.pop();
path.push("cur");
let iter = path.read_dir()?;

View File

@ -56,24 +56,24 @@ impl Collection {
let threads = Threads::new(&mut envelopes);
/*let cache_dir =
xdg::BaseDirectories::with_profile("meli", format!("{}_Thread", folder.hash()))
.unwrap();
if let Some(cached) = cache_dir.find_cache_file("threads") {
let reader = io::BufReader::new(fs::File::open(cached).unwrap());
let result: result::Result<Threads, _> = bincode::deserialize_from(reader);
let ret = if let Ok(mut cached_t) = result {
use std::iter::FromIterator;
eprintln!("loaded cache, our hash set is {:?}\n and the cached one is {:?}", FnvHashSet::from_iter(envelopes.keys().cloned()), cached_t.hash_set);
cached_t.amend(&mut envelopes);
cached_t
} else {
Threads::new(&mut envelopes)
};
ret
} else {
Threads::new(&mut envelopes)
};
*/
xdg::BaseDirectories::with_profile("meli", format!("{}_Thread", folder.hash()))
.unwrap();
if let Some(cached) = cache_dir.find_cache_file("threads") {
let reader = io::BufReader::new(fs::File::open(cached).unwrap());
let result: result::Result<Threads, _> = bincode::deserialize_from(reader);
let ret = if let Ok(mut cached_t) = result {
use std::iter::FromIterator;
eprintln!("loaded cache, our hash set is {:?}\n and the cached one is {:?}", FnvHashSet::from_iter(envelopes.keys().cloned()), cached_t.hash_set);
cached_t.amend(&mut envelopes);
cached_t
} else {
Threads::new(&mut envelopes)
};
ret
} else {
Threads::new(&mut envelopes)
};
*/
Collection {
folder: folder.clone(),

View File

@ -74,7 +74,7 @@ impl Address {
Address::Group(g) => g.display_name.display(&g.raw),
}
}
pub fn get_email(&self) -> String {
match self {
Address::Mailbox(m) => m.address_spec.display(&m.raw),
@ -94,11 +94,11 @@ impl PartialEq for Address {
s.address_spec.display(&s.raw) == o.address_spec.display(&o.raw)
}
(Address::Group(s), Address::Group(o)) => {
s.display_name.display(&s.raw) == o.display_name.display(&o.raw) && s
.mailbox_list
.iter()
.zip(o.mailbox_list.iter())
.fold(true, |b, (s, o)| b && (s == o))
s.display_name.display(&s.raw) == o.display_name.display(&o.raw)
&& s.mailbox_list
.iter()
.zip(o.mailbox_list.iter())
.fold(true, |b, (s, o)| b && (s == o))
}
}
}
@ -616,22 +616,22 @@ impl Envelope {
pub fn message_id_raw(&self) -> Cow<str> {
String::from_utf8_lossy(self.message_id.raw())
}
fn set_date(&mut self, new_val: &[u8]) {
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>) {
fn set_bcc(&mut self, new_val: Vec<Address>) {
self.bcc = new_val;
}
fn set_cc(&mut self, new_val: Vec<Address>) {
fn set_cc(&mut self, new_val: Vec<Address>) {
self.cc = new_val;
}
fn set_from(&mut self, new_val: Vec<Address>) {
fn set_from(&mut self, new_val: Vec<Address>) {
self.from = new_val;
}
fn set_to(&mut self, new_val: Vec<Address>) {
fn set_to(&mut self, new_val: Vec<Address>) {
self.to = new_val;
}
fn set_in_reply_to(&mut self, new_val: &[u8]) {
fn set_in_reply_to(&mut self, new_val: &[u8]) {
let slice = match parser::message_id(new_val).to_full_result() {
Ok(v) => v,
Err(_) => {
@ -641,10 +641,10 @@ impl Envelope {
};
self.in_reply_to = Some(MessageID::new(new_val, slice));
}
fn set_subject(&mut self, new_val: Vec<u8>) {
fn set_subject(&mut self, new_val: Vec<u8>) {
self.subject = Some(new_val);
}
fn set_message_id(&mut self, new_val: &[u8]) {
fn set_message_id(&mut self, new_val: &[u8]) {
let slice = match parser::message_id(new_val).to_full_result() {
Ok(v) => v,
Err(_) => {
@ -653,7 +653,7 @@ impl Envelope {
};
self.message_id = MessageID::new(new_val, slice);
}
fn push_references(&mut self, new_val: &[u8]) {
fn push_references(&mut self, new_val: &[u8]) {
let slice = match parser::message_id(new_val).to_full_result() {
Ok(v) => v,
Err(_) => {
@ -685,7 +685,7 @@ impl Envelope {
}
}
}
fn set_references(&mut self, new_val: &[u8]) {
fn set_references(&mut self, new_val: &[u8]) {
match self.references {
Some(ref mut s) => {
s.raw = new_val.into();
@ -713,10 +713,10 @@ impl Envelope {
pub fn thread(&self) -> usize {
self.thread
}
pub fn set_thread(&mut self, new_val: usize) {
pub fn set_thread(&mut self, new_val: usize) {
self.thread = new_val;
}
pub fn set_datetime(&mut self, new_val: chrono::DateTime<chrono::FixedOffset>) {
pub fn set_datetime(&mut self, new_val: chrono::DateTime<chrono::FixedOffset>) {
self.timestamp = new_val.timestamp() as UnixTimestamp;
}
pub fn set_flag(&mut self, f: Flag, mut operation: Box<BackendOp>) -> Result<()> {

View File

@ -71,73 +71,77 @@ impl AttachmentBuilder {
}
pub fn content_type(&mut self, value: &[u8]) -> &Self {
match parser::content_type(value).to_full_result() {
Ok((ct, cst, params)) => if ct.eq_ignore_ascii_case(b"multipart") {
let mut boundary = None;
for (n, v) in params {
if n.eq_ignore_ascii_case(b"boundary") {
boundary = Some(v);
break;
Ok((ct, cst, params)) => {
if ct.eq_ignore_ascii_case(b"multipart") {
let mut boundary = None;
for (n, v) in params {
if n.eq_ignore_ascii_case(b"boundary") {
boundary = Some(v);
break;
}
}
}
assert!(boundary.is_some());
let _boundary = boundary.unwrap();
let offset = (_boundary.as_ptr() as usize).wrapping_sub(value.as_ptr() as usize);
let boundary = SliceBuild::new(offset, _boundary.len());
let subattachments = Self::subattachments(&self.raw, boundary.get(&value));
assert!(!subattachments.is_empty());
self.content_type = ContentType::Multipart {
boundary,
kind: if cst.eq_ignore_ascii_case(b"mixed") {
MultipartType::Mixed
} else if cst.eq_ignore_ascii_case(b"alternative") {
MultipartType::Alternative
} else if cst.eq_ignore_ascii_case(b"digest") {
MultipartType::Digest
} else {
Default::default()
},
subattachments,
};
} else if ct.eq_ignore_ascii_case(b"text") {
self.content_type = ContentType::Text {
kind: Text::Plain,
charset: Charset::UTF8,
};
for (n, v) in params {
if n.eq_ignore_ascii_case(b"charset") {
assert!(boundary.is_some());
let _boundary = boundary.unwrap();
let offset =
(_boundary.as_ptr() as usize).wrapping_sub(value.as_ptr() as usize);
let boundary = SliceBuild::new(offset, _boundary.len());
let subattachments = Self::subattachments(&self.raw, boundary.get(&value));
assert!(!subattachments.is_empty());
self.content_type = ContentType::Multipart {
boundary,
kind: if cst.eq_ignore_ascii_case(b"mixed") {
MultipartType::Mixed
} else if cst.eq_ignore_ascii_case(b"alternative") {
MultipartType::Alternative
} else if cst.eq_ignore_ascii_case(b"digest") {
MultipartType::Digest
} else {
Default::default()
},
subattachments,
};
} else if ct.eq_ignore_ascii_case(b"text") {
self.content_type = ContentType::Text {
kind: Text::Plain,
charset: Charset::UTF8,
};
for (n, v) in params {
if n.eq_ignore_ascii_case(b"charset") {
if let ContentType::Text {
charset: ref mut c, ..
} = self.content_type
{
*c = Charset::from(v);
}
break;
}
}
if cst.eq_ignore_ascii_case(b"html") {
if let ContentType::Text {
charset: ref mut c, ..
kind: ref mut k, ..
} = self.content_type
{
*c = Charset::from(v);
*k = Text::Html;
}
} else if !cst.eq_ignore_ascii_case(b"plain") {
if let ContentType::Text {
kind: ref mut k, ..
} = self.content_type
{
*k = Text::Other { tag: cst.into() };
}
break;
}
} else if ct.eq_ignore_ascii_case(b"message") && cst.eq_ignore_ascii_case(b"rfc822")
{
self.content_type = ContentType::MessageRfc822;
} else {
let mut tag: Vec<u8> = Vec::with_capacity(ct.len() + cst.len() + 1);
tag.extend(ct);
tag.push(b'/');
tag.extend(cst);
self.content_type = ContentType::Unsupported { tag };
}
if cst.eq_ignore_ascii_case(b"html") {
if let ContentType::Text {
kind: ref mut k, ..
} = self.content_type
{
*k = Text::Html;
}
} else if !cst.eq_ignore_ascii_case(b"plain") {
if let ContentType::Text {
kind: ref mut k, ..
} = self.content_type
{
*k = Text::Other { tag: cst.into() };
}
}
} else if ct.eq_ignore_ascii_case(b"message") && cst.eq_ignore_ascii_case(b"rfc822") {
self.content_type = ContentType::MessageRfc822;
} else {
let mut tag: Vec<u8> = Vec::with_capacity(ct.len() + cst.len() + 1);
tag.extend(ct);
tag.push(b'/');
tag.extend(cst);
self.content_type = ContentType::Unsupported { tag };
},
}
Err(v) => {
eprintln!("parsing error in content_type: {:?} {:?}", value, v);
}
@ -405,23 +409,25 @@ fn decode_rec_helper(a: &Attachment, filter: &Option<Filter>) -> Vec<u8> {
kind: ref multipart_type,
subattachments: ref sub_att_vec,
..
} => if *multipart_type == MultipartType::Alternative {
for a in sub_att_vec {
if let ContentType::Text {
kind: Text::Plain, ..
} = a.content_type
{
return decode_helper(a, filter);
} => {
if *multipart_type == MultipartType::Alternative {
for a in sub_att_vec {
if let ContentType::Text {
kind: Text::Plain, ..
} = a.content_type
{
return decode_helper(a, filter);
}
}
decode_helper(a, filter)
} else {
let mut vec = Vec::new();
for a in sub_att_vec {
vec.extend(decode_rec_helper(a, filter));
}
vec
}
decode_helper(a, filter)
} else {
let mut vec = Vec::new();
for a in sub_att_vec {
vec.extend(decode_rec_helper(a, filter));
}
vec
},
}
};
if let Some(filter) = filter {
filter(a, &mut ret);

View File

@ -3,8 +3,8 @@ use chrono::{DateTime, Local};
use data_encoding::BASE64_MIME;
use std::str;
mod random;
mod mime;
mod random;
//use self::mime::*;
@ -70,13 +70,16 @@ impl str::FromStr for Draft {
if ignore_header(k) {
continue;
}
if ret.headers.insert(
String::from_utf8(k.to_vec())?,
String::from_utf8(v.to_vec())?,
).is_none() {
if ret
.headers
.insert(
String::from_utf8(k.to_vec())?,
String::from_utf8(v.to_vec())?,
)
.is_none()
{
ret.header_order.push(String::from_utf8(k.to_vec())?);
}
}
let body = Envelope::new(0).body_bytes(s.as_bytes());
@ -191,7 +194,6 @@ impl Draft {
}
Ok(ret)
}
}

View File

@ -135,16 +135,19 @@ named!(
named!(
header_no_val<(&[u8], &[u8])>,
do_parse!(
name: complete!(name) >>
tag!(b":") >>
opt!(is_a!(" \t")) >>
tag!(b"\n") >>
( { (name, b"") } )));
do_parse!(
name: complete!(name)
>> tag!(b":")
>> opt!(is_a!(" \t"))
>> tag!(b"\n")
>> ({ (name, b"") })
)
);
named!(
header<(&[u8], &[u8])>,
alt_complete!(header_no_val | header_has_val));
alt_complete!(header_no_val | header_has_val)
);
/* Parse all headers -> Vec<(&str, Vec<&str>)> */
named!(pub headers<std::vec::Vec<(&[u8], &[u8])>>,
many1!(complete!(header)));

0
rustfmt.toml 100644
View File

View File

@ -48,9 +48,9 @@ fn main() {
//let _stdout = stdout();
//let mut _stdout = _stdout.lock();
/*
let _stderr = stderr();
let mut _stderr = _stderr.lock();
*/
let _stderr = stderr();
let mut _stderr = _stderr.lock();
*/
/* Catch SIGWINCH to handle terminal resizing */
let signal = chan_signal::notify(&[Signal::WINCH]);
@ -66,7 +66,11 @@ fn main() {
let menu = Entity::from(Box::new(AccountMenu::new(&state.context.accounts)));
let listing = listing::Listing::from(IndexStyle::Compact);
let b = Entity::from(Box::new(listing));
let tabs = Box::new(Tabbed::new(vec![Box::new(VSplit::new(menu, b, 90, true)), Box::new(AccountsPanel::new(&state.context)), Box::new(ContactList::default())]));
let tabs = Box::new(Tabbed::new(vec![
Box::new(VSplit::new(menu, b, 90, true)),
Box::new(AccountsPanel::new(&state.context)),
Box::new(ContactList::default()),
]));
let window = Entity::from(tabs);
let status_bar = Entity::from(Box::new(StatusBar::new(window)));
@ -75,7 +79,9 @@ fn main() {
let xdg_notifications =
Entity::from(Box::new(ui::components::notifications::XDGNotifications {}));
state.register_entity(xdg_notifications);
state.register_entity(Entity::from(Box::new(ui::components::notifications::NotificationFilter {})));
state.register_entity(Entity::from(Box::new(
ui::components::notifications::NotificationFilter {},
)));
/* Keep track of the input mode. See ui::UIMode for details */
'main: loop {

View File

@ -20,10 +20,10 @@
*/
/*!
Components are ways to handle application data. They can draw on the terminal and receive events, but also do other stuff as well. (For example, see the `notifications` module.)
Components are ways to handle application data. They can draw on the terminal and receive events, but also do other stuff as well. (For example, see the `notifications` module.)
See the `Component` Trait for more details.
*/
See the `Component` Trait for more details.
*/
use super::*;
@ -76,8 +76,6 @@ const _DOUBLE_DOWN_AND_LEFT: char = '╗';
const _DOUBLE_UP_AND_LEFT: char = '╝';
const _DOUBLE_UP_AND_RIGHT: char = '╚';
type EntityId = Uuid;
/// `Entity` is a container for Components.
@ -157,7 +155,9 @@ pub trait Component: Display + Debug + Send {
fn kill(&mut self, _id: EntityId) {}
fn set_id(&mut self, _id: EntityId) {}
fn get_shortcuts(&self, context: &Context) -> ShortcutMap { Default::default() }
fn get_shortcuts(&self, context: &Context) -> ShortcutMap {
Default::default()
}
}
/*
@ -429,20 +429,23 @@ pub(crate) fn set_and_join_box(grid: &mut CellBuffer, idx: Pos, ch: char) {
}
pub fn create_box(grid: &mut CellBuffer, area: Area) {
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
if !is_valid_area!(area) {
return;
}
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
for x in get_x(upper_left)..get_x(bottom_right) {
grid[(x, get_y(upper_left))].set_ch(HORZ_BOUNDARY);
grid[(x, get_y(bottom_right))].set_ch(HORZ_BOUNDARY);
}
for x in get_x(upper_left)..get_x(bottom_right) {
grid[(x, get_y(upper_left))].set_ch(HORZ_BOUNDARY);
grid[(x, get_y(bottom_right))].set_ch(HORZ_BOUNDARY);
}
for y in get_y(upper_left)..get_y(bottom_right) {
grid[(get_x(upper_left), y)].set_ch(VERT_BOUNDARY);
grid[(get_x(bottom_right), y)].set_ch(VERT_BOUNDARY);
}
set_and_join_box(grid, upper_left, HORZ_BOUNDARY);
set_and_join_box(grid, set_x(upper_left, get_x(bottom_right)), HORZ_BOUNDARY);
set_and_join_box(grid, set_y(upper_left, get_y(bottom_right)), VERT_BOUNDARY);
set_and_join_box(grid, bottom_right, VERT_BOUNDARY);
for y in get_y(upper_left)..get_y(bottom_right) {
grid[(get_x(upper_left), y)].set_ch(VERT_BOUNDARY);
grid[(get_x(bottom_right), y)].set_ch(VERT_BOUNDARY);
}
set_and_join_box(grid, upper_left, HORZ_BOUNDARY);
set_and_join_box(grid, set_x(upper_left, get_x(bottom_right)), HORZ_BOUNDARY);
set_and_join_box(grid, set_y(upper_left, get_y(bottom_right)), VERT_BOUNDARY);
set_and_join_box(grid, bottom_right, VERT_BOUNDARY);
}

View File

@ -78,7 +78,7 @@ impl ContactManager {
Color::Default,
((0, 0), (width, 0)),
false,
);
);
let (x, _) = write_string_to_grid(
"Last edited: ",
&mut self.content,
@ -86,7 +86,7 @@ impl ContactManager {
Color::Default,
((x, 0), (width, 0)),
false,
);
);
write_string_to_grid(
&self.card.last_edited(),
&mut self.content,
@ -94,15 +94,23 @@ impl ContactManager {
Color::Default,
((x, 0), (width, 0)),
false,
);
);
self.form = FormWidget::new("Save".into());
self.form.add_button(("Cancel".into(), false));
self.form.push(("First Name".into(), self.card.firstname().to_string()));
self.form.push(("Last Name".into(), self.card.lastname().to_string()));
self.form.push(("Additional Name".into(), self.card.additionalname().to_string()));
self.form.push(("Name Prefix".into(), self.card.name_prefix().to_string()));
self.form.push(("Name Suffix".into(), self.card.name_suffix().to_string()));
self.form.push(("E-mail".into(), self.card.email().to_string()));
self.form
.push(("First Name".into(), self.card.firstname().to_string()));
self.form
.push(("Last Name".into(), self.card.lastname().to_string()));
self.form.push((
"Additional Name".into(),
self.card.additionalname().to_string(),
));
self.form
.push(("Name Prefix".into(), self.card.name_prefix().to_string()));
self.form
.push(("Name Suffix".into(), self.card.name_suffix().to_string()));
self.form
.push(("E-mail".into(), self.card.email().to_string()));
self.form.push(("url".into(), self.card.url().to_string()));
self.form.push(("key".into(), self.card.key().to_string()));
}
@ -120,55 +128,71 @@ impl Component for ContactManager {
let upper_left = upper_left!(area);
let bottom_right = bottom_right!(area);
self.form.draw(grid, (set_y(upper_left, get_y(upper_left) + 1), bottom_right), context);
self.form.draw(
grid,
(set_y(upper_left, get_y(upper_left) + 1), bottom_right),
context,
);
context.dirty_areas.push_back(area);
}
fn process_event(&mut self, event: &mut UIEvent, context: &mut Context) -> bool {
if self.form.process_event(event, context) {
match self.form.buttons_result() {
None => {},
None => {}
Some(true) => {
let mut fields = std::mem::replace(&mut self.form, FormWidget::default()).collect().unwrap();
let fields: FnvHashMap<String, String> = fields.into_iter().map(|(s, v)| {
(s, match v {
Field::Text(v, _, _) | Field::TextArea(v, _) => v,
Field::Choice(mut v, c) => v.remove(c),
})}).collect();
let mut fields = std::mem::replace(&mut self.form, FormWidget::default())
.collect()
.unwrap();
let fields: FnvHashMap<String, String> = fields
.into_iter()
.map(|(s, v)| {
(
s,
match v {
Field::Text(v, _, _) | Field::TextArea(v, _) => v,
Field::Choice(mut v, c) => v.remove(c),
},
)
})
.collect();
let mut new_card = Card::from(fields);
new_card.set_id(*self.card.id());
context.accounts[self.account_pos].address_book.add_card(new_card);
context.accounts[self.account_pos]
.address_book
.add_card(new_card);
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::StatusEvent(StatusEvent::DisplayMessage("Saved.".into())),
event_type: UIEventType::StatusEvent(StatusEvent::DisplayMessage(
"Saved.".into(),
)),
});
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::EntityKill(self.id),
});
},
}
Some(false) => {
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::EntityKill(self.id),
});
},
}
}
return true;
}
/*
match event.event_type {
UIEventType::Input(Key::Char('\n')) => {
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::EntityKill(self.id),
});
return true;
},
_ => {},
}
*/
match event.event_type {
UIEventType::Input(Key::Char('\n')) => {
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::EntityKill(self.id),
});
return true;
},
_ => {},
}
*/
false
}

View File

@ -20,7 +20,7 @@ pub struct ContactList {
content: CellBuffer,
id_positions: Vec<CardId>,
mode: ViewMode,
dirty: bool,
view: Option<Entity>,
@ -60,29 +60,30 @@ impl ContactList {
..Self::new()
}
}
fn initialize(&mut self, context: &mut Context) {
let account = &mut context.accounts[self.account_pos];
let book = &mut account.address_book;
self.length = book.len();
self.content.resize(MAX_COLS, book.len(), Cell::with_char(' '));
self.content
.resize(MAX_COLS, book.len(), Cell::with_char(' '));
self.id_positions.clear();
if self.id_positions.capacity() < book.len() {
self.id_positions.reserve(book.len());
self.id_positions.reserve(book.len());
}
for (i, c) in book.values().enumerate() {
self.id_positions.push(*c.id());
write_string_to_grid(
c.email(),
&mut self.content,
Color::Default,
Color::Default,
((0, i), (MAX_COLS - 1, book.len() - 1)),
false
);
false,
);
}
}
}
@ -105,7 +106,15 @@ impl Component for ContactList {
if self.dirty {
self.initialize(context);
clear_area(grid, area);
copy_area(grid, &self.content, area, ((0, 0), (MAX_COLS - 1, self.content.size().1.saturating_sub(1))));
copy_area(
grid,
&self.content,
area,
(
(0, 0),
(MAX_COLS - 1, self.content.size().1.saturating_sub(1)),
),
);
context.dirty_areas.push_back(area);
self.dirty = false;
}
@ -116,11 +125,27 @@ impl Component for ContactList {
/* Reset previously highlighted line */
let fg_color = Color::Default;
let bg_color = Color::Default;
change_colors(grid, (pos_inc(upper_left, (0, self.cursor_pos)), set_y(bottom_right, get_y(upper_left) + self.cursor_pos)), fg_color, bg_color);
change_colors(
grid,
(
pos_inc(upper_left, (0, self.cursor_pos)),
set_y(bottom_right, get_y(upper_left) + self.cursor_pos),
),
fg_color,
bg_color,
);
/* Highlight current line */
let bg_color = Color::Byte(246);
change_colors(grid, (pos_inc(upper_left, (0, self.new_cursor_pos)), set_y(bottom_right, get_y(upper_left) + self.new_cursor_pos)), fg_color, bg_color);
change_colors(
grid,
(
pos_inc(upper_left, (0, self.new_cursor_pos)),
set_y(bottom_right, get_y(upper_left) + self.new_cursor_pos),
),
fg_color,
bg_color,
);
self.cursor_pos = self.new_cursor_pos;
}
@ -139,10 +164,10 @@ impl Component for ContactList {
self.mode = ViewMode::View(*entity.id());
self.view = Some(entity);
return true;
},
}
UIEventType::Input(ref key) if *key == shortcuts["edit_contact"] && self.length > 0 => {
let account = &mut context.accounts[self.account_pos];
let book = &mut account.address_book;
@ -154,9 +179,9 @@ impl Component for ContactList {
self.mode = ViewMode::View(*entity.id());
self.view = Some(entity);
return true;
},
}
UIEventType::Input(Key::Char('n')) => {
let card = Card::new();
let mut manager = ContactManager::default();
@ -165,27 +190,26 @@ impl Component for ContactList {
let entity = Entity::from(Box::new(manager));
self.mode = ViewMode::View(*entity.id());
self.view = Some(entity);
return true;
},
}
UIEventType::Input(Key::Up) => {
self.set_dirty();
self.new_cursor_pos = self.cursor_pos.saturating_sub(1);
return true;
},
}
UIEventType::Input(Key::Down) if self.cursor_pos < self.length.saturating_sub(1) => {
self.set_dirty();
self.new_cursor_pos += 1;
return true;
},
}
UIEventType::EntityKill(ref kill_id) if self.mode == ViewMode::View(*kill_id) => {
self.mode = ViewMode::List;
self.view.take();
self.set_dirty();
return true;
},
_ => {},
}
_ => {}
}
false
}
@ -205,7 +229,11 @@ impl Component for ContactList {
self.mode = ViewMode::Close(uuid);
}
fn get_shortcuts(&self, context: &Context) -> ShortcutMap {
let mut map = self.view.as_ref().map(|p| p.get_shortcuts(context)).unwrap_or_default();
let mut map = self
.view
.as_ref()
.map(|p| p.get_shortcuts(context))
.unwrap_or_default();
let config_map = context.settings.shortcuts.contact_list.key_values();
map.insert("create_contact", (*config_map["create_contact"]).clone());

View File

@ -58,8 +58,7 @@ impl Default for Indexer {
}
impl Indexer {
fn draw_menu(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {
}
fn draw_menu(&mut self, _grid: &mut CellBuffer, _area: Area, _context: &mut Context) {}
}
impl Component for Indexer {

View File

@ -42,7 +42,7 @@ impl Index {
let bg_color = if self.cursor_pos == idx {
Color::Byte(246)
/* } else if idx % 2 == 0 {
Color::Byte(236)*/
Color::Byte(236)*/
} else {
Color::Default
};

View File

@ -73,7 +73,8 @@ impl AccountMenu {
}
entries
},
}).collect();
})
.collect();
AccountMenu {
accounts,
dirty: true,

View File

@ -35,7 +35,6 @@ impl fmt::Display for AccountsPanel {
}
}
impl Component for AccountsPanel {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if self.dirty {
@ -46,26 +45,26 @@ impl Component for AccountsPanel {
Color::Default,
((2, 3), (120 - 1, 3)),
true,
);
);
for (i, a) in context.accounts.iter().enumerate() {
create_box(&mut self.content, ((2,5+i*10 ), (120-1, 15+i*10)));
create_box(&mut self.content, ((2, 5 + i * 10), (120 - 1, 15 + i * 10)));
let (x, y) = write_string_to_grid(
a.name(),
&mut self.content,
Color::Default,
Color::Default,
((3, 5 + i*10), (120 - 2, 5 + i*10)),
((3, 5 + i * 10), (120 - 2, 5 + i * 10)),
true,
);
);
write_string_to_grid(
" ▒██▒ ",
&mut self.content,
Color::Byte(32),
Color::Default,
((x, y), (120 - 2, 5 + i*10)),
((x, y), (120 - 2, 5 + i * 10)),
true,
);
);
write_string_to_grid(
&a.runtime_settings.account().identity,
&mut self.content,
@ -73,10 +72,10 @@ impl Component for AccountsPanel {
Color::Default,
((4, y + 2), (120 - 2, y + 2)),
true,
);
);
if i == self.cursor {
for h in 1..8 {
self.content[(2, h+y+1)].set_ch('*');
self.content[(2, h + y + 1)].set_ch('*');
}
}
write_string_to_grid(
@ -86,7 +85,7 @@ impl Component for AccountsPanel {
Color::Default,
((5, y + 3), (120 - 2, y + 3)),
true,
);
);
write_string_to_grid(
"- Contacts",
&mut self.content,
@ -94,7 +93,7 @@ impl Component for AccountsPanel {
Color::Default,
((5, y + 4), (120 - 2, y + 4)),
true,
);
);
write_string_to_grid(
"- Mailing Lists",
&mut self.content,
@ -102,10 +101,7 @@ impl Component for AccountsPanel {
Color::Default,
((5, y + 5), (120 - 2, y + 5)),
true,
);
);
}
self.dirty = false;
}
@ -121,22 +117,24 @@ impl Component for AccountsPanel {
self.cursor = self.cursor.saturating_sub(1);
self.dirty = true;
return true;
},
}
UIEventType::Input(Key::Down) => {
if self.cursor + 1 < context.accounts.len() {
self.cursor += 1;
self.dirty = true;
}
return true;
},
}
UIEventType::Input(Key::Char('\n')) => {
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::Action(Tab(TabOpen(Some(Box::new(ContactList::for_account(self.cursor)))
)))});
event_type: UIEventType::Action(Tab(TabOpen(Some(Box::new(
ContactList::for_account(self.cursor),
))))),
});
return true;
},
_ => {},
}
_ => {}
}
false

View File

@ -24,7 +24,6 @@ use super::*;
use melib::Draft;
use std::str::FromStr;
#[derive(Debug, PartialEq)]
enum Cursor {
Headers,
@ -173,10 +172,15 @@ impl Composer {
let account_cursor = self.account_cursor;
for &k in &["Date", "From", "To", "Cc", "Bcc", "Subject"] {
if k == "To" {
self.form.push_cl((k.into(), headers[k].to_string(), Box::new(move |c, term| {
let book: &AddressBook = &c.accounts[account_cursor].address_book;
let results: Vec<String> = book.search(term);
results})));
self.form.push_cl((
k.into(),
headers[k].to_string(),
Box::new(move |c, term| {
let book: &AddressBook = &c.accounts[account_cursor].address_book;
let results: Vec<String> = book.search(term);
results
}),
));
} else {
self.form.push((k.into(), headers[k].to_string()));
}
@ -184,36 +188,36 @@ impl Composer {
}
/*
let (x, y) = if k == "From" {
write_string_to_grid(
"",
grid,
Color::Byte(251),
Color::Default,
((x, y), set_y(bottom_right, y)),
true,
)
} else {
(x, y)
};
let (x, y) = write_string_to_grid(
&headers[k],
grid,
Color::Default,
bg_color,
((x, y), set_y(bottom_right, y)),
true,
);
if k == "From" {
write_string_to_grid(
"",
grid,
Color::Byte(251),
Color::Default,
((x, y), set_y(bottom_right, y)),
true,
)
*/
let (x, y) = if k == "From" {
write_string_to_grid(
"",
grid,
Color::Byte(251),
Color::Default,
((x, y), set_y(bottom_right, y)),
true,
)
} else {
(x, y)
};
let (x, y) = write_string_to_grid(
&headers[k],
grid,
Color::Default,
bg_color,
((x, y), set_y(bottom_right, y)),
true,
);
if k == "From" {
write_string_to_grid(
"",
grid,
Color::Byte(251),
Color::Default,
((x, y), set_y(bottom_right, y)),
true,
)
*/
}
impl Component for Composer {
@ -283,14 +287,21 @@ impl Component for Composer {
}
if self.dirty {
for i in get_x(upper_left) + mid + 1..=get_x(upper_left) + mid + width.saturating_sub(0) {
for i in get_x(upper_left) + mid + 1..=get_x(upper_left) + mid + width.saturating_sub(0)
{
//set_and_join_box(grid, (i, header_height), HORZ_BOUNDARY);
//grid[(i, header_height)].set_fg(Color::Default);
//grid[(i, header_height)].set_bg(Color::Default);
}
}
let header_area = (pos_inc(upper_left, (mid + 1, 0)), (get_x(bottom_right).saturating_sub(mid), get_y(upper_left) + header_height + 1));
let header_area = (
pos_inc(upper_left, (mid + 1, 0)),
(
get_x(bottom_right).saturating_sub(mid),
get_y(upper_left) + header_height + 1,
),
);
let body_area = (
pos_inc(upper_left, (mid + 1, header_height + 2)),
pos_dec(bottom_right, ((mid, 0))),
@ -303,21 +314,26 @@ impl Component for Composer {
ViewMode::Overview | ViewMode::Pager => {
self.pager.set_dirty();
self.pager.draw(grid, body_area, context);
},
}
ViewMode::Discard(_) => {
/* Let user choose whether to quit with/without saving or cancel */
let mid_x = {
std::cmp::max(width!(area) / 2, width / 2) - width / 2
};
let mid_y = {
std::cmp::max(height!(area) / 2, 11) - 11
};
let mid_x = { std::cmp::max(width!(area) / 2, width / 2) - width / 2 };
let mid_y = { std::cmp::max(height!(area) / 2, 11) - 11 };
let upper_left = upper_left!(body_area);
let bottom_right = bottom_right!(body_area);
let area = (pos_inc(upper_left, (mid_x, mid_y)), pos_dec(bottom_right, (mid_x, mid_y)));
let area = (
pos_inc(upper_left, (mid_x, mid_y)),
pos_dec(bottom_right, (mid_x, mid_y)),
);
create_box(grid, area);
let area = (pos_inc(upper_left, (mid_x + 2, mid_y + 2)), pos_dec(bottom_right, (mid_x.saturating_sub(2), mid_y.saturating_sub(2))));
let area = (
pos_inc(upper_left, (mid_x + 2, mid_y + 2)),
pos_dec(
bottom_right,
(mid_x.saturating_sub(2), mid_y.saturating_sub(2)),
),
);
let (_, y) = write_string_to_grid(
&format!("Draft \"{:10}\"", self.draft.headers()["Subject"]),
@ -326,7 +342,7 @@ impl Component for Composer {
Color::Default,
area,
true,
);
);
let (_, y) = write_string_to_grid(
"[x] quit without saving",
grid,
@ -334,7 +350,7 @@ impl Component for Composer {
Color::Default,
(set_y(upper_left!(area), y + 2), bottom_right!(area)),
true,
);
);
let (_, y) = write_string_to_grid(
"[y] save draft and quit",
grid,
@ -342,7 +358,7 @@ impl Component for Composer {
Color::Default,
(set_y(upper_left!(area), y + 1), bottom_right!(area)),
true,
);
);
write_string_to_grid(
"[n] cancel",
grid,
@ -350,9 +366,8 @@ impl Component for Composer {
Color::Default,
(set_y(upper_left!(area), y + 1), bottom_right!(area)),
true,
);
},
);
}
}
context.dirty_areas.push_back(area);
@ -371,7 +386,7 @@ impl Component for Composer {
self.dirty = true;
return true;
}
},
}
_ => {}
}
if self.form.process_event(event, context) {
@ -381,36 +396,36 @@ impl Component for Composer {
match event.event_type {
UIEventType::Resize => {
self.set_dirty();
},
}
/*
/* Switch e-mail From: field to the `left` configured account. */
UIEventType::Input(Key::Left) if self.cursor == Cursor::From => {
self.account_cursor = self.account_cursor.saturating_sub(1);
self.draft.headers_mut().insert(
"From".into(),
get_display_name(context, self.account_cursor),
);
self.dirty = true;
return true;
self.account_cursor = self.account_cursor.saturating_sub(1);
self.draft.headers_mut().insert(
"From".into(),
get_display_name(context, self.account_cursor),
);
self.dirty = true;
return true;
}
/* Switch e-mail From: field to the `right` configured account. */
UIEventType::Input(Key::Right) if self.cursor == Cursor::From => {
if self.account_cursor + 1 < context.accounts.len() {
self.account_cursor += 1;
self.draft.headers_mut().insert(
"From".into(),
get_display_name(context, self.account_cursor),
);
self.dirty = true;
}
return true;
if self.account_cursor + 1 < context.accounts.len() {
self.account_cursor += 1;
self.draft.headers_mut().insert(
"From".into(),
get_display_name(context, self.account_cursor),
);
self.dirty = true;
}
return true;
}*/
UIEventType::Input(Key::Up) => {
self.cursor = Cursor::Headers;
},
}
UIEventType::Input(Key::Down) => {
self.cursor = Cursor::Body;
},
}
UIEventType::Input(Key::Char(key)) if self.mode.is_discard() => {
match (key, &self.mode) {
('x', ViewMode::Discard(u)) => {
@ -420,7 +435,7 @@ impl Component for Composer {
});
return true;
}
('n', _) => {},
('n', _) => {}
('y', ViewMode::Discard(u)) => {
let account = &context.accounts[self.account_cursor];
let draft = std::mem::replace(&mut self.draft, Draft::default());
@ -432,7 +447,7 @@ impl Component for Composer {
event_type: UIEventType::Action(Tab(Kill(*u))),
});
return true;
},
}
_ => {
return false;
}
@ -454,48 +469,48 @@ impl Component for Composer {
return true;
}
UIEventType::Input(Key::Char('e')) if self.cursor == Cursor::Body => {
/* Edit draft in $EDITOR */
use std::process::{Command, Stdio};
/* 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 mut f =
create_temp_file(self.draft.to_string().unwrap().as_str().as_bytes(), None);
//let mut f = Box::new(std::fs::File::create(&dir).unwrap());
/* Edit draft in $EDITOR */
use std::process::{Command, Stdio};
/* 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 mut f =
create_temp_file(self.draft.to_string().unwrap().as_str().as_bytes(), None);
//let mut f = Box::new(std::fs::File::create(&dir).unwrap());
// TODO: check exit status
Command::new("vim")
.arg("+/^$")
.arg(&f.path())
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.output()
.expect("failed to execute process");
let result = f.read_to_string();
self.draft = Draft::from_str(result.as_str()).unwrap();
self.initialized = false;
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::Fork(ForkType::Finished),
});
context.restore_input();
/*
// TODO: check exit status
Command::new("vim")
.arg("+/^$")
.arg(&f.path())
.stdin(Stdio::inherit())
.stdout(Stdio::inherit())
.output()
.expect("failed to execute process");
let result = f.read_to_string();
self.draft = Draft::from_str(result.as_str()).unwrap();
self.initialized = false;
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::Fork(ForkType::Finished),
});
context.restore_input();
/*
Cursor::To | Cursor::Cc | Cursor::Bcc => {
let account = &context.accounts[self.account_cursor];
let mut entries = account.address_book.values().map(|v| (v.id().as_bytes().to_vec(), v.email().to_string())).collect();
self.mode = ViewMode::Selector(Selector::new(entries, true));
},
Cursor::Attachments => {
unimplemented!()
},
Cursor::From => {
return true;
}
*/
Cursor::To | Cursor::Cc | Cursor::Bcc => {
let account = &context.accounts[self.account_cursor];
let mut entries = account.address_book.values().map(|v| (v.id().as_bytes().to_vec(), v.email().to_string())).collect();
self.mode = ViewMode::Selector(Selector::new(entries, true));
},
Cursor::Attachments => {
unimplemented!()
},
Cursor::From => {
return true;
}
*/
self.dirty = true;
return true;
}
@ -505,11 +520,14 @@ 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) || self.form.is_dirty()
self.dirty
|| self.pager.is_dirty()
|| self
.reply_context
.as_ref()
.map(|(_, p)| p.is_dirty())
.unwrap_or(false)
|| self.form.is_dirty()
}
fn set_dirty(&mut self) {

View File

@ -131,7 +131,6 @@ impl From<IndexStyle> for Listing {
IndexStyle::Plain => Listing::Plain(Default::default()),
IndexStyle::Threaded => Listing::Threaded(Default::default()),
IndexStyle::Compact => Listing::Compact(Default::default()),
}
}
}

View File

@ -342,7 +342,9 @@ impl CompactListing {
fn format_date(envelope: &Envelope) -> String {
let d = std::time::UNIX_EPOCH + std::time::Duration::from_secs(envelope.date());
let now: std::time::Duration = std::time::SystemTime::now().duration_since(d).unwrap_or_else(|_| std::time::Duration::new(std::u64::MAX, 0));
let now: std::time::Duration = std::time::SystemTime::now()
.duration_since(d)
.unwrap_or_else(|_| std::time::Duration::new(std::u64::MAX, 0));
match now.as_secs() {
n if n < 10 * 60 * 60 => format!("{} hours ago{}", n / (60 * 60), " ".repeat(8)),
n if n < 24 * 60 * 60 => format!("{} hours ago{}", n / (60 * 60), " ".repeat(7)),
@ -527,20 +529,86 @@ impl Component for CompactListing {
}
fn get_shortcuts(&self, context: &Context) -> ShortcutMap {
let mut map = self.view.as_ref().map(|p| p.get_shortcuts(context)).unwrap_or_default();
let mut map = self
.view
.as_ref()
.map(|p| p.get_shortcuts(context))
.unwrap_or_default();
let config_map = context.settings.shortcuts.compact_listing.key_values();
map.insert("open_thread", if let Some(key) = config_map.get("open_thread") { (*key).clone() } else { Key::Char('\n') });
map.insert("prev_page", if let Some(key) = config_map.get("prev_page") { (*key).clone() } else { Key::PageUp });
map.insert("next_page", if let Some(key) = config_map.get("next_page") { (*key).clone() } else { Key::PageDown });
map.insert("exit_thread", if let Some(key) = config_map.get("exit_thread") { (*key).clone() } else { Key::Char('i') });
map.insert("prev_folder", if let Some(key) = config_map.get("prev_folder") { (*key).clone() } else { Key::Char('J') });
map.insert("next_folder", if let Some(key) = config_map.get("next_folder") { (*key).clone() } else { Key::Char('K') });
map.insert("prev_account", if let Some(key) = config_map.get("prev_account") { (*key).clone() } else { Key::Char('h') });
map.insert("next_account", if let Some(key) = config_map.get("next_account") { (*key).clone() } else { Key::Char('l') });
map.insert("new_mail", if let Some(key) = config_map.get("new_mail") { (*key).clone() } else { Key::Char('m') });
map.insert(
"open_thread",
if let Some(key) = config_map.get("open_thread") {
(*key).clone()
} else {
Key::Char('\n')
},
);
map.insert(
"prev_page",
if let Some(key) = config_map.get("prev_page") {
(*key).clone()
} else {
Key::PageUp
},
);
map.insert(
"next_page",
if let Some(key) = config_map.get("next_page") {
(*key).clone()
} else {
Key::PageDown
},
);
map.insert(
"exit_thread",
if let Some(key) = config_map.get("exit_thread") {
(*key).clone()
} else {
Key::Char('i')
},
);
map.insert(
"prev_folder",
if let Some(key) = config_map.get("prev_folder") {
(*key).clone()
} else {
Key::Char('J')
},
);
map.insert(
"next_folder",
if let Some(key) = config_map.get("next_folder") {
(*key).clone()
} else {
Key::Char('K')
},
);
map.insert(
"prev_account",
if let Some(key) = config_map.get("prev_account") {
(*key).clone()
} else {
Key::Char('h')
},
);
map.insert(
"next_account",
if let Some(key) = config_map.get("next_account") {
(*key).clone()
} else {
Key::Char('l')
},
);
map.insert(
"new_mail",
if let Some(key) = config_map.get("new_mail") {
(*key).clone()
} else {
Key::Char('m')
},
);
map
}
}

View File

@ -413,7 +413,9 @@ impl ThreadListing {
}
fn format_date(envelope: &Envelope) -> String {
let d = std::time::UNIX_EPOCH + std::time::Duration::from_secs(envelope.date());
let now: std::time::Duration = std::time::SystemTime::now().duration_since(d).unwrap_or_else(|_| std::time::Duration::new(std::u64::MAX, 0));
let now: std::time::Duration = std::time::SystemTime::now()
.duration_since(d)
.unwrap_or_else(|_| std::time::Duration::new(std::u64::MAX, 0));
match now.as_secs() {
n if n < 10 * 60 * 60 => format!("{} hours ago{}", n / (60 * 60), " ".repeat(8)),
n if n < 24 * 60 * 60 => format!("{} hours ago{}", n / (60 * 60), " ".repeat(7)),
@ -486,8 +488,10 @@ impl Component for ThreadListing {
backend.operation(hash, folder_hash)
};
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
let envelope: &mut Envelope =
mailbox.collection.get_mut(&self.locations[self.cursor_pos.2]).unwrap();
let envelope: &mut Envelope = mailbox
.collection
.get_mut(&self.locations[self.cursor_pos.2])
.unwrap();
envelope.set_seen(op).unwrap();
true
} else {
@ -538,7 +542,7 @@ impl Component for ThreadListing {
self.cursor_pos.0,
self.cursor_pos.1,
self.locations[self.cursor_pos.2],
);
);
self.view = Some(MailView::new(coordinates, None, None));
self.view.as_mut().unwrap().draw(

View File

@ -123,7 +123,8 @@ impl MailView {
v.extend(html_filter.wait_with_output().unwrap().stdout);
}
})),
)).into_owned();
))
.into_owned();
match self.mode {
ViewMode::Normal | ViewMode::Subview => {
let mut t = body_text.to_string();
@ -171,8 +172,8 @@ impl MailView {
let mut ret = "Viewing attachment. Press `r` to return \n".to_string();
ret.push_str(&attachments[aidx].text());
ret
},
ViewMode::ContactSelector(_) => { unimplemented!()},
}
ViewMode::ContactSelector(_) => unimplemented!(),
}
}
pub fn plain_text_to_buf(s: &str, highlight_urls: bool) -> CellBuffer {
@ -341,13 +342,13 @@ impl Component for MailView {
};
self.dirty = false;
}
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);
}
},
}
ViewMode::ContactSelector(ref mut s) => {
clear_area(grid, (set_y(upper_left, y + 1), bottom_right));
s.draw(grid, (set_y(upper_left, y + 1), bottom_right), context);
@ -368,12 +369,12 @@ impl Component for MailView {
return true;
}
}
},
}
ViewMode::ContactSelector(ref mut s) => {
if s.process_event(event, context) {
return true;
}
},
}
_ => {
if let Some(p) = self.pager.as_mut() {
if p.process_event(event, context) {
@ -386,21 +387,25 @@ impl Component for MailView {
match event.event_type {
UIEventType::Input(Key::Char('c')) => {
/*
let mut new_card: Card = Card::new();
new_card.set_email(&envelope.from()[0].get_email());
new_card.set_firstname(&envelope.from()[0].get_display_name());
let mut new_card: Card = Card::new();
new_card.set_email(&envelope.from()[0].get_email());
new_card.set_firstname(&envelope.from()[0].get_display_name());
eprintln!("{:?}", new_card);
eprintln!("{:?}", new_card);
*/
*/
if let ViewMode::ContactSelector(_) = self.mode {
if let ViewMode::ContactSelector(s) = std::mem::replace(&mut self.mode, ViewMode::Normal) {
if let ViewMode::ContactSelector(s) =
std::mem::replace(&mut self.mode, ViewMode::Normal)
{
for c in s.collect() {
let mut new_card: Card = Card::new();
let email = String::from_utf8(c).unwrap();
new_card.set_email(&email);
new_card.set_firstname("");
context.accounts[self.coordinates.0].address_book.add_card(new_card);
context.accounts[self.coordinates.0]
.address_book
.add_card(new_card);
}
//eprintln!("{:?}", s.collect());
}
@ -413,12 +418,18 @@ impl Component for MailView {
.unwrap();
let envelope: &Envelope = &mailbox.collection[&self.coordinates.2];
let mut entries = Vec::new();
entries.push((envelope.from()[0].get_email().into_bytes(), format!("{}", envelope.from()[0])));
entries.push((envelope.to()[0].get_email().into_bytes(), format!("{}", envelope.to()[0])));
entries.push((
envelope.from()[0].get_email().into_bytes(),
format!("{}", envelope.from()[0]),
));
entries.push((
envelope.to()[0].get_email().into_bytes(),
format!("{}", envelope.to()[0]),
));
self.mode = ViewMode::ContactSelector(Selector::new(entries, true));
self.dirty = true;
//context.accounts.context(self.coordinates.0).address_book.add_card(new_card);
},
}
UIEventType::Input(Key::Esc) | UIEventType::Input(Key::Alt('')) => {
self.cmd_buf.clear();
context.replies.push_back(UIEvent {
@ -603,7 +614,7 @@ impl Component for MailView {
true
}
fn is_dirty(&self) -> bool {
self.dirty
self.dirty
|| self.pager.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|| self.subview.as_ref().map(|p| p.is_dirty()).unwrap_or(false)
|| if let ViewMode::ContactSelector(ref s) = self.mode {

View File

@ -108,7 +108,8 @@ impl EnvelopeView {
v.extend(html_filter.wait_with_output().unwrap().stdout);
}
})),
)).into_owned();
))
.into_owned();
match self.mode {
ViewMode::Normal | ViewMode::Subview => {
let mut t = body_text.to_string();
@ -378,8 +379,10 @@ impl Component for EnvelopeView {
ContentType::MessageRfc822 => {
self.mode = ViewMode::Subview;
self.subview = Some(Box::new(Pager::from_string(
String::from_utf8_lossy(&decode_rec(u, None)).to_string(), context,
None, None
String::from_utf8_lossy(&decode_rec(u, None)).to_string(),
context,
None,
None,
)));
}

View File

@ -20,8 +20,8 @@
*/
/*!
Notification handling components.
*/
Notification handling components.
*/
use notify_rust::Notification as notify_Notification;
use std::process::{Command, Stdio};
@ -45,12 +45,8 @@ impl Component for XDGNotifications {
notify_Notification::new()
.appname("meli")
.icon("mail-message-new")
.summary(
title
.as_ref()
.map(|v| v.as_str())
.unwrap_or("Event"),
).body(&escape_str(body))
.summary(title.as_ref().map(|v| v.as_str()).unwrap_or("Event"))
.body(&escape_str(body))
.icon("dialog-information")
.show()
.unwrap();
@ -60,7 +56,6 @@ impl Component for XDGNotifications {
fn set_dirty(&mut self) {}
}
fn escape_str(s: &str) -> String {
let mut ret: String = String::with_capacity(s.len());
for c in s.chars() {
@ -72,16 +67,17 @@ fn escape_str(s: &str) -> String {
'"' => ret.push_str("&quot;"),
_ => {
let i = c as u32;
if (0x1 <= i && i <= 0x8) ||
(0xb <= i && i <= 0xc) ||
(0xe <= i && i <= 0x1f) ||
(0x7f <= i && i <= 0x84) ||
(0x86 <= i && i <= 0x9f) {
if (0x1 <= i && i <= 0x8)
|| (0xb <= i && i <= 0xc)
|| (0xe <= i && i <= 0x1f)
|| (0x7f <= i && i <= 0x84)
|| (0x86 <= i && i <= 0x9f)
{
ret.push_str(&format!("&#{:x}%{:x};", i, i));
} else {
ret.push(c);
}
},
}
}
}
ret
@ -123,16 +119,14 @@ impl Component for NotificationFilter {
if let UIEventType::Notification(ref title, ref body) = event.event_type {
if let Some(ref bin) = context.runtime_settings.notifications.script {
if let Err(v) = Command::new(bin)
.arg(title
.as_ref()
.map(|v| v.as_str())
.unwrap_or("Event"))
.arg(body)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn() {
eprintln!("{:?}",v);
}
.arg(title.as_ref().map(|v| v.as_str()).unwrap_or("Event"))
.arg(body)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.spawn()
{
eprintln!("{:?}", v);
}
}
}
false

View File

@ -252,7 +252,12 @@ impl Pager {
self.cursor_pos = 0;
self.max_cursor_pos = None;
}
pub fn from_string(mut text: String, context: &mut Context, cursor_pos: Option<usize>, width: Option<usize>) -> Self {
pub fn from_string(
mut text: String,
context: &mut Context,
cursor_pos: Option<usize>,
width: Option<usize>,
) -> Self {
let pager_filter: Option<&String> = context.settings.pager.filter.as_ref();
//let format_flowed: bool = context.settings.pager.format_flowed;
if let Some(bin) = pager_filter {
@ -275,10 +280,10 @@ impl Pager {
.wait_with_output()
.expect("Failed to wait on filter")
.stdout,
).to_string();
)
.to_string();
}
let content = {
let lines: Vec<&str> = if let Some(width) = width {
word_break_string(text.as_str(), width)
@ -313,7 +318,7 @@ impl Pager {
let height = lines.len() + 1;
let width = width.unwrap_or_else(|| lines.iter().map(|l| l.len()).max().unwrap_or(0));
let mut content = CellBuffer::new(width, height, Cell::with_char(' '));
Pager::print_string(&mut content, lines);
Pager {
text: text.to_string(),
@ -347,7 +352,7 @@ impl Pager {
Color::Default,
((0, i), (width - 1, i)),
true,
);
);
}
}
pub fn cursor_pos(&self) -> usize {
@ -824,7 +829,10 @@ impl Tabbed {
let cslice: &mut [Cell] = grid;
//TODO: bounds check
let cslice_len = cslice.len();
for c in cslice[(y * cols) + x.saturating_sub(1)..std::cmp::min((y * cols) + x.saturating_sub(1), cslice_len)].iter_mut() {
for c in cslice[(y * cols) + x.saturating_sub(1)
..std::cmp::min((y * cols) + x.saturating_sub(1), cslice_len)]
.iter_mut()
{
c.set_bg(Color::Byte(7));
c.set_ch(' ');
}
@ -846,7 +854,13 @@ impl fmt::Display for Tabbed {
impl Component for Tabbed {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if self.dirty {
clear_area(grid, (upper_left!(area), set_x(upper_left!(area), get_x(bottom_right!(area)))));
clear_area(
grid,
(
upper_left!(area),
set_x(upper_left!(area), get_x(bottom_right!(area))),
),
);
self.dirty = false;
}
@ -871,29 +885,50 @@ impl Component for Tabbed {
if self.show_shortcuts {
let area = (
pos_inc(upper_left!(area), (2, 1)), set_x(bottom_right!(area), get_x(bottom_right!(area)).saturating_sub(2)));
pos_inc(upper_left!(area), (2, 1)),
set_x(
bottom_right!(area),
get_x(bottom_right!(area)).saturating_sub(2),
),
);
clear_area(grid, area);
create_box(grid, area);
// TODO: print into a pager
for (idx, (k, v)) in self.children[self.cursor_pos].get_shortcuts(context).into_iter().enumerate() {
let (x, y) = write_string_to_grid(
&k,
grid,
Color::Byte(29),
Color::Default,
(pos_inc(upper_left!(area), (2, 1 + idx)), set_x(bottom_right!(area), get_x(bottom_right!(area)).saturating_sub(2))),
false,
);
write_string_to_grid(
&format!("{:?}", v),
grid,
Color::Default,
Color::Default,
((x + 2, y), set_x(bottom_right!(area), get_x(bottom_right!(area)).saturating_sub(2))),
false,
);
};
for (idx, (k, v)) in self.children[self.cursor_pos]
.get_shortcuts(context)
.into_iter()
.enumerate()
{
let (x, y) = write_string_to_grid(
&k,
grid,
Color::Byte(29),
Color::Default,
(
pos_inc(upper_left!(area), (2, 1 + idx)),
set_x(
bottom_right!(area),
get_x(bottom_right!(area)).saturating_sub(2),
),
),
false,
);
write_string_to_grid(
&format!("{:?}", v),
grid,
Color::Default,
Color::Default,
(
(x + 2, y),
set_x(
bottom_right!(area),
get_x(bottom_right!(area)).saturating_sub(2),
),
),
false,
);
}
context.dirty_areas.push_back(area);
}
}
@ -959,12 +994,12 @@ impl Component for Tabbed {
}
}
type EntryIdentifier = Vec<u8>;
/// Shows selection to user
#[derive(Debug, PartialEq)]
pub struct Selector {
single_only: bool, /// allow only one selection
single_only: bool,
/// allow only one selection
entries: Vec<(EntryIdentifier, bool)>,
selected_entry_count: u32,
content: CellBuffer,
@ -982,21 +1017,15 @@ impl fmt::Display for Selector {
impl Component for Selector {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
eprintln!("drawing");
let (width, height) = self.content.size();
copy_area_with_break(
grid,
&self.content,
area,
((0, 0), (width, height)),
);
copy_area_with_break(grid, &self.content, area, ((0, 0), (width, height)));
context.dirty_areas.push_back(area);
}
fn process_event(&mut self, event: &mut UIEvent, _context: &mut Context) -> bool {
let (width, height) = self.content.size();
match event.event_type {
UIEventType::Input(Key::Char(' ')) => {
self.entries[self.cursor].1 = ! self.entries[self.cursor].1;
self.entries[self.cursor].1 = !self.entries[self.cursor].1;
if self.entries[self.cursor].1 {
write_string_to_grid(
"x",
@ -1005,7 +1034,7 @@ impl Component for Selector {
Color::Default,
((1, self.cursor), (width, self.cursor)),
false,
);
);
} else {
write_string_to_grid(
" ",
@ -1014,21 +1043,21 @@ impl Component for Selector {
Color::Default,
((1, self.cursor), (width, self.cursor)),
false,
);
);
}
self.dirty = true;
return true;
},
}
UIEventType::Input(Key::Up) if self.cursor > 0 => {
self.cursor -= 1;
self.dirty = true;
return true;
},
}
UIEventType::Input(Key::Down) if self.cursor < height.saturating_sub(1) => {
self.cursor += 1;
self.dirty = true;
return true;
},
}
_ => {}
}
@ -1044,10 +1073,18 @@ impl Component for Selector {
impl Selector {
pub fn new(mut entries: Vec<(EntryIdentifier, String)>, single_only: bool) -> Selector {
let width = entries.iter().max_by_key(|e| e.1.len()).map(|v| v.1.len()).unwrap_or(0) + 4;
let width = entries
.iter()
.max_by_key(|e| e.1.len())
.map(|v| v.1.len())
.unwrap_or(0)
+ 4;
let height = entries.len();
let mut content = CellBuffer::new(width, height, Cell::with_char(' '));
let identifiers = entries.iter_mut().map(|(id, _)| (std::mem::replace(&mut *id, Vec::new()), false)).collect();
let identifiers = entries
.iter_mut()
.map(|(id, _)| (std::mem::replace(&mut *id, Vec::new()), false))
.collect();
for (i, e) in entries.into_iter().enumerate() {
write_string_to_grid(
&format!("[ ] {}", e.1),
@ -1070,6 +1107,10 @@ impl Selector {
}
pub fn collect(self) -> Vec<EntryIdentifier> {
self.entries.into_iter().filter(|v| v.1).map(|(id, _)| id).collect()
self.entries
.into_iter()
.filter(|v| v.1)
.map(|(id, _)| id)
.collect()
}
}

View File

@ -17,7 +17,11 @@ impl Default for FormFocus {
}
pub enum Field {
Text(String, Cursor, Option<(Box<Fn(&Context, &str) -> Vec<String> + Send>, AutoComplete)>),
Text(
String,
Cursor,
Option<(Box<Fn(&Context, &str) -> Vec<String> + Send>, AutoComplete)>,
),
Choice(Vec<String>, Cursor),
TextArea(String, Cursor),
}
@ -42,12 +46,8 @@ impl Default for Field {
impl Field {
fn as_str(&self) -> &str {
match self {
Text(ref s, _, _) => {
s
},
TextArea(ref s, _) => {
s
},
Text(ref s, _, _) => s,
TextArea(ref s, _) => s,
Choice(ref v, cursor) => {
if v.is_empty() {
""
@ -60,48 +60,56 @@ impl Field {
pub fn into_string(self) -> String {
match self {
Text(s, _, _) => {
s
},
TextArea(s, _) => {
s
},
Choice(mut v, cursor) => {
v.remove(cursor)
}
Text(s, _, _) => s,
TextArea(s, _) => s,
Choice(mut v, cursor) => v.remove(cursor),
}
}
fn draw_cursor(&mut self, grid: &mut CellBuffer, area: Area, secondary_area: Area, context: &mut Context) {
fn draw_cursor(
&mut self,
grid: &mut CellBuffer,
area: Area,
secondary_area: Area,
context: &mut Context,
) {
let upper_left = upper_left!(area);
match self {
Text(ref term, cursor, auto_complete_fn) => {
change_colors(grid, (pos_inc(upper_left, (*cursor, 0)), (pos_inc(upper_left, (*cursor, 0)))), Color::Default, Color::Byte(248));
if term.chars().count() <= 2 { return; }
change_colors(
grid,
(
pos_inc(upper_left, (*cursor, 0)),
(pos_inc(upper_left, (*cursor, 0))),
),
Color::Default,
Color::Byte(248),
);
if term.chars().count() <= 2 {
return;
}
if let Some((auto_complete_fn, auto_complete)) = auto_complete_fn {
let entries = auto_complete_fn(context, term);
auto_complete.set_suggestions(entries);
auto_complete.draw(grid, secondary_area, context);
}
},
TextArea(_, _) => {
},
Choice(_, _cursor) => {
}
TextArea(_, _) => {}
Choice(_, _cursor) => {}
}
}
}
impl Component for Field {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, _context: &mut Context) {
write_string_to_grid(
self.as_str(),
grid,
Color::Default,
Color::Default,
area,
true);
write_string_to_grid(
self.as_str(),
grid,
Color::Default,
Color::Default,
area,
true,
);
}
fn process_event(&mut self, event: &mut UIEvent, _context: &mut Context) -> bool {
if let Text(ref mut s, ref mut cursor, Some((_, auto_complete))) = self {
@ -112,8 +120,8 @@ impl Component for Field {
*cursor = s.chars().count();
return true;
}
},
_ => {},
}
_ => {}
}
}
@ -124,74 +132,68 @@ impl Component for Field {
} else {
return false;
}
},
}
UIEventType::InsertInput(Key::Down) => {
if let Text(_, _, Some((_, auto_complete))) = self {
auto_complete.inc_cursor();
} else {
return false;
}
},
UIEventType::InsertInput(Key::Right) => {
match self {
TextArea(ref s, ref mut cursor) | Text(ref s, ref mut cursor, _) => {
if *cursor < s.len() {
*cursor += 1;
}
},
Choice(ref vec, ref mut cursor) => {
*cursor = if *cursor == vec.len().saturating_sub(1) {
0
} else {
*cursor + 1
};
}
}
},
UIEventType::InsertInput(Key::Left) => {
match self {
TextArea(_, ref mut cursor) | Text(_, ref mut cursor, _) => {
if *cursor == 0 {
return false;
} else {
*cursor -= 1;
}
},
Choice(_, ref mut cursor) => {
if *cursor == 0 {
return false;
} else {
*cursor -= 1;
}
}
}
},
UIEventType::InsertInput(Key::Char(k)) => {
match self {
Text(ref mut s, ref mut cursor, _) | TextArea(ref mut s, ref mut cursor) => {
s.insert(*cursor, k);
}
UIEventType::InsertInput(Key::Right) => match self {
TextArea(ref s, ref mut cursor) | Text(ref s, ref mut cursor, _) => {
if *cursor < s.len() {
*cursor += 1;
},
_ => {}
}
}
Choice(ref vec, ref mut cursor) => {
*cursor = if *cursor == vec.len().saturating_sub(1) {
0
} else {
*cursor + 1
};
}
},
UIEventType::InsertInput(Key::Backspace) => {
match self {
Text(ref mut s, ref mut cursor, ref mut auto_complete) => {
if *cursor > 0 {
*cursor -= 1;
s.remove(*cursor);
}
auto_complete.as_mut().map(|ac| ac.1.set_suggestions(Vec::new()));
},
TextArea(ref mut s, ref mut cursor) => {
if *cursor > 0 {
*cursor -= 1;
s.remove(*cursor);
}
},
_ => {}
UIEventType::InsertInput(Key::Left) => match self {
TextArea(_, ref mut cursor) | Text(_, ref mut cursor, _) => {
if *cursor == 0 {
return false;
} else {
*cursor -= 1;
}
}
Choice(_, ref mut cursor) => {
if *cursor == 0 {
return false;
} else {
*cursor -= 1;
}
}
},
UIEventType::InsertInput(Key::Char(k)) => match self {
Text(ref mut s, ref mut cursor, _) | TextArea(ref mut s, ref mut cursor) => {
s.insert(*cursor, k);
*cursor += 1;
}
_ => {}
},
UIEventType::InsertInput(Key::Backspace) => match self {
Text(ref mut s, ref mut cursor, ref mut auto_complete) => {
if *cursor > 0 {
*cursor -= 1;
s.remove(*cursor);
}
auto_complete
.as_mut()
.map(|ac| ac.1.set_suggestions(Vec::new()));
}
TextArea(ref mut s, ref mut cursor) => {
if *cursor > 0 {
*cursor -= 1;
s.remove(*cursor);
}
}
_ => {}
},
_ => {
return false;
@ -212,7 +214,6 @@ impl fmt::Display for Field {
}
}
#[derive(Debug, Default)]
pub struct FormWidget {
fields: FnvHashMap<String, Field>,
@ -272,10 +273,20 @@ impl FormWidget {
self.layout.push(value.0.clone());
self.fields.insert(value.0, TextArea(value.1, 0));
}
pub fn push_cl(&mut self, value: (String, String, Box<Fn(&Context, &str) -> Vec<String> + Send>)) {
pub fn push_cl(
&mut self,
value: (
String,
String,
Box<Fn(&Context, &str) -> Vec<String> + Send>,
),
) {
self.field_name_max_length = std::cmp::max(self.field_name_max_length, value.0.len());
self.layout.push(value.0.clone());
self.fields.insert(value.0, Text(value.1, 0, Some((value.2, AutoComplete::new(Vec::new())))));
self.fields.insert(
value.0,
Text(value.1, 0, Some((value.2, AutoComplete::new(Vec::new())))),
);
}
pub fn push(&mut self, value: (String, String)) {
self.field_name_max_length = std::cmp::max(self.field_name_max_length, value.0.len());
@ -317,33 +328,64 @@ impl Component for FormWidget {
grid,
Color::Default,
Color::Default,
(pos_inc(upper_left, (1, i)), set_y(bottom_right, i + get_y(upper_left))),
(
pos_inc(upper_left, (1, i)),
set_y(bottom_right, i + get_y(upper_left)),
),
false,
);
);
/* draw field */
v.draw(grid,
(pos_inc(upper_left, (self.field_name_max_length + 3, i)), set_y(bottom_right, i + get_y(upper_left))), context);
v.draw(
grid,
(
pos_inc(upper_left, (self.field_name_max_length + 3, i)),
set_y(bottom_right, i + get_y(upper_left)),
),
context,
);
/* Highlight if necessary */
if i == self.cursor {
if i == self.cursor {
if self.focus == FormFocus::Fields {
change_colors(grid, (pos_inc(upper_left, (0, i)), set_y(bottom_right, i + get_y(upper_left))), Color::Default, Color::Byte(246));
change_colors(
grid,
(
pos_inc(upper_left, (0, i)),
set_y(bottom_right, i + get_y(upper_left)),
),
Color::Default,
Color::Byte(246),
);
}
if self.focus == FormFocus::TextInput {
v.draw_cursor(grid,
(pos_inc(upper_left, (self.field_name_max_length + 3 , i)),
(get_x(upper_left) + self.field_name_max_length + 3, i + get_y(upper_left))),
(pos_inc(upper_left, (self.field_name_max_length + 3 , i + 1)), bottom_right),
context);
v.draw_cursor(
grid,
(
pos_inc(upper_left, (self.field_name_max_length + 3, i)),
(
get_x(upper_left) + self.field_name_max_length + 3,
i + get_y(upper_left),
),
),
(
pos_inc(upper_left, (self.field_name_max_length + 3, i + 1)),
bottom_right,
),
context,
);
}
}
}
if !self.hide_buttons {
let length = self.layout.len();
self.buttons.draw(grid,
(pos_inc(upper_left, (1, length * 2 + 3)), set_y(bottom_right, length * 2 + 3 + get_y(upper_left))),
context);
self.buttons.draw(
grid,
(
pos_inc(upper_left, (1, length * 2 + 3)),
set_y(bottom_right, length * 2 + 3 + get_y(upper_left)),
),
context,
);
}
self.dirty = false;
context.dirty_areas.push_back(area);
@ -356,43 +398,43 @@ impl Component for FormWidget {
match event.event_type {
UIEventType::Input(Key::Up) if self.focus == FormFocus::Buttons => {
self.focus = FormFocus::Fields;
},
}
UIEventType::InsertInput(Key::Up) if self.focus == FormFocus::TextInput => {
let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
field.process_event(event, context);
},
}
UIEventType::Input(Key::Up) => {
self.cursor = self.cursor.saturating_sub(1);
},
}
UIEventType::InsertInput(Key::Down) if self.focus == FormFocus::TextInput => {
let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
field.process_event(event, context);
},
}
UIEventType::Input(Key::Down) if self.cursor < self.layout.len().saturating_sub(1) => {
self.cursor += 1;
},
}
UIEventType::Input(Key::Down) if self.focus == FormFocus::Fields => {
self.focus = FormFocus::Buttons;
if self.hide_buttons {
self.set_dirty();
return false;
}
},
}
UIEventType::InsertInput(Key::Char('\t')) if self.focus == FormFocus::TextInput => {
let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
field.process_event(event, context);
},
}
UIEventType::Input(Key::Char('\n')) if self.focus == FormFocus::Fields => {
self.focus = FormFocus::TextInput;
context.replies.push_back(UIEvent {
id: 0,
event_type: UIEventType::ChangeMode(UIMode::Insert),
});
},
id: 0,
event_type: UIEventType::ChangeMode(UIMode::Insert),
});
}
UIEventType::InsertInput(Key::Right) if self.focus == FormFocus::TextInput => {
let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
field.process_event(event, context);
},
}
UIEventType::InsertInput(Key::Left) if self.focus == FormFocus::TextInput => {
let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
if !field.process_event(event, context) {
@ -402,18 +444,18 @@ impl Component for FormWidget {
event_type: UIEventType::ChangeMode(UIMode::Normal),
});
}
},
}
UIEventType::ChangeMode(UIMode::Normal) if self.focus == FormFocus::TextInput => {
self.focus = FormFocus::Fields;
},
}
UIEventType::InsertInput(Key::Char(_)) if self.focus == FormFocus::TextInput => {
let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
field.process_event(event, context);
},
}
UIEventType::InsertInput(Key::Backspace) if self.focus == FormFocus::TextInput => {
let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
field.process_event(event, context);
},
}
_ => {
return false;
}
@ -429,9 +471,11 @@ impl Component for FormWidget {
}
}
#[derive(Debug, Default)]
pub struct ButtonWidget<T> where T: std::fmt::Debug + Default + Send{
pub struct ButtonWidget<T>
where
T: std::fmt::Debug + Default + Send,
{
buttons: FnvHashMap<String, T>,
layout: Vec<String>,
@ -439,13 +483,19 @@ pub struct ButtonWidget<T> where T: std::fmt::Debug + Default + Send{
cursor: usize,
}
impl<T> fmt::Display for ButtonWidget<T> where T: std::fmt::Debug + Default + Send {
impl<T> fmt::Display for ButtonWidget<T>
where
T: std::fmt::Debug + Default + Send,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt("", f)
}
}
impl<T> ButtonWidget<T> where T: std::fmt::Debug + Default + Send {
impl<T> ButtonWidget<T>
where
T: std::fmt::Debug + Default + Send,
{
pub fn new(init_val: (String, T)) -> ButtonWidget<T> {
ButtonWidget {
layout: vec![init_val.0.clone()],
@ -465,39 +515,52 @@ impl<T> ButtonWidget<T> where T: std::fmt::Debug + Default + Send {
}
}
impl<T> Component for ButtonWidget<T> where T: std::fmt::Debug + Default + Send {
impl<T> Component for ButtonWidget<T>
where
T: std::fmt::Debug + Default + Send,
{
fn draw(&mut self, grid: &mut CellBuffer, area: Area, _context: &mut Context) {
let upper_left = upper_left!(area);
let mut len = 0;
let mut len = 0;
for (i, k) in self.layout.iter().enumerate() {
let cur_len = k.len();
write_string_to_grid(
k.as_str(),
grid,
Color::Default,
if i == self.cursor { Color::Byte(246) } else { Color::Default },
(pos_inc(upper_left, (len, 0)), pos_inc(upper_left, (cur_len + len, 0))),
if i == self.cursor {
Color::Byte(246)
} else {
Color::Default
},
(
pos_inc(upper_left, (len, 0)),
pos_inc(upper_left, (cur_len + len, 0)),
),
false,
);
);
len += cur_len + 3;
}
}
fn process_event(&mut self, event: &mut UIEvent, _context: &mut Context) -> bool {
match event.event_type {
UIEventType::Input(Key::Char('\n')) => {
self.result = Some(self.buttons.remove(&self.layout[self.cursor]).unwrap_or_default());
self.result = Some(
self.buttons
.remove(&self.layout[self.cursor])
.unwrap_or_default(),
);
return true;
},
}
UIEventType::Input(Key::Left) => {
self.cursor = self.cursor.saturating_sub(1);
return true;
},
}
UIEventType::Input(Key::Right) if self.cursor < self.layout.len().saturating_sub(1) => {
self.cursor += 1;
return true;
},
}
_ => {}
}
@ -509,8 +572,6 @@ impl<T> Component for ButtonWidget<T> where T: std::fmt::Debug + Default + Send
fn set_dirty(&mut self) {}
}
#[derive(Debug, PartialEq)]
pub struct AutoComplete {
entries: Vec<String>,
@ -528,7 +589,9 @@ impl fmt::Display for AutoComplete {
impl Component for AutoComplete {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if self.entries.is_empty() { return; };
if self.entries.is_empty() {
return;
};
let upper_left = upper_left!(area);
self.dirty = false;
@ -540,7 +603,15 @@ impl Component for AutoComplete {
((0, 0), (width.saturating_sub(1), height.saturating_sub(1))),
);
/* Highlight cursor */
change_colors(grid, (pos_inc(upper_left, (0, self.cursor)), pos_inc(upper_left, (width.saturating_sub(1), self.cursor))), Color::Default, Color::Byte(246));
change_colors(
grid,
(
pos_inc(upper_left, (0, self.cursor)),
pos_inc(upper_left, (width.saturating_sub(1), self.cursor)),
),
Color::Default,
Color::Byte(246),
);
context.dirty_areas.push_back(area);
}
fn process_event(&mut self, event: &mut UIEvent, _context: &mut Context) -> bool {
@ -568,10 +639,14 @@ impl AutoComplete {
pub fn set_suggestions(&mut self, entries: Vec<String>) {
if entries.len() == self.entries.len() && entries == self.entries {
return;
return;
}
let mut content = CellBuffer::new(entries.iter().map(|e| e.len()).max().unwrap_or(0) + 1, entries.len(), Cell::with_style(Color::Byte(23), Color::Byte(7), Attr::Default));
let mut content = CellBuffer::new(
entries.iter().map(|e| e.len()).max().unwrap_or(0) + 1,
entries.len(),
Cell::with_style(Color::Byte(23), Color::Byte(7), Attr::Default),
);
let width = content.cols();
for (i, e) in entries.iter().enumerate() {
let (x, _) = write_string_to_grid(
@ -581,7 +656,7 @@ impl AutoComplete {
Color::Byte(7),
((0, i), (width - 1, i)),
false,
);
);
write_string_to_grid(
"",
&mut content,
@ -589,15 +664,23 @@ impl AutoComplete {
Color::Byte(7),
((width - 1, i), (width - 1, i)),
false,
);
);
}
self.content = content;
self.entries = entries;
self.cursor = 0;
}
pub fn inc_cursor(&mut self) { if self.cursor < self.entries.len().saturating_sub(1) { self.cursor += 1; self.set_dirty(); } }
pub fn dec_cursor(&mut self) { self.cursor = self.cursor.saturating_sub(1); self.set_dirty(); }
pub fn inc_cursor(&mut self) {
if self.cursor < self.entries.len().saturating_sub(1) {
self.cursor += 1;
self.set_dirty();
}
}
pub fn dec_cursor(&mut self) {
self.cursor = self.cursor.saturating_sub(1);
self.set_dirty();
}
pub fn get_suggestion(&mut self) -> Option<String> {
if self.entries.is_empty() {

View File

@ -19,25 +19,24 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
extern crate bincode;
extern crate config;
extern crate serde;
extern crate xdg;
extern crate bincode;
pub mod pager;
pub mod notifications;
pub mod pager;
pub mod shortcuts;
pub mod accounts;
pub use self::accounts::Account;
pub use self::shortcuts::*;
use self::config::{Config, File, FileFormat};
pub use self::shortcuts::*;
use self::notifications::NotificationsSettings;
use melib::conf::AccountSettings;
use melib::error::*;
use pager::PagerSettings;
use self::notifications::NotificationsSettings;
use self::serde::{de, Deserialize, Deserializer};
use std::collections::HashMap;
@ -194,7 +193,6 @@ impl Settings {
}
}
#[derive(Copy, Debug, Clone, Deserialize)]
pub enum IndexStyle {
Plain,
@ -209,7 +207,8 @@ impl Default for IndexStyle {
}
fn index_from_str<'de, D>(deserializer: D) -> std::result::Result<IndexStyle, D::Error>
where D: Deserializer<'de>
where
D: Deserializer<'de>,
{
let s = <String>::deserialize(deserializer)?;
match s.as_str() {

View File

@ -58,7 +58,6 @@ pub struct Account {
pub(crate) workers: Vec<Worker>,
pub(crate) settings: AccountConf,
pub(crate) runtime_settings: AccountConf,
pub(crate) backend: Box<MailBackend>,
@ -68,9 +67,7 @@ pub struct Account {
impl Drop for Account {
fn drop(&mut self) {
//TODO: Avoid panics
let data_dir =
xdg::BaseDirectories::with_profile("meli", &self.name)
.unwrap();
let data_dir = xdg::BaseDirectories::with_profile("meli", &self.name).unwrap();
if let Ok(data) = data_dir.place_data_file("addressbook") {
/* place result in cache directory */
let f = match fs::File::create(data) {
@ -99,9 +96,7 @@ impl Account {
folders.push(None);
workers.push(Account::new_worker(f, &mut backend, notify_fn.clone()));
}
let data_dir =
xdg::BaseDirectories::with_profile("meli", &name)
.unwrap();
let data_dir = xdg::BaseDirectories::with_profile("meli", &name).unwrap();
let address_book = if let Ok(data) = data_dir.place_data_file("addressbook") {
if data.exists() {
let reader = io::BufReader::new(fs::File::open(data).unwrap());
@ -229,14 +224,14 @@ impl Account {
if self.sent_folder.is_some() && self.sent_folder.unwrap() == index {
self.folders[index] = Some(mailbox);
/* Add our replies to other folders */
for id in (0..self.folders.len()).filter(|i| *i != index) {
self.add_replies_to_folder(id);
}
for id in (0..self.folders.len()).filter(|i| *i != index) {
self.add_replies_to_folder(id);
}
} else {
self.folders[index] = Some(mailbox);
self.add_replies_to_folder(index);
self.folders[index] = Some(mailbox);
self.add_replies_to_folder(index);
};
*/
*/
}
/*
@ -303,7 +298,8 @@ impl Account {
}
pub fn save_draft(&self, draft: Draft) -> Result<()> {
self.backend.save(draft.to_string()?, &self.settings.conf.draft_folder)
self.backend
.save(draft.to_string()?, &self.settings.conf.draft_folder)
}
}

View File

@ -31,14 +31,14 @@ macro_rules! key_values {
}
pub fn key_values(&self) -> FnvHashMap<&'static str, &Key> {
let mut map: FnvHashMap<&'static str, &Key> = Default::default();
$(map.insert(stringify!($fname),&(self.$fname));)*
$(map.insert(stringify!($fname),&(self.$fname));)*
map
}
}
}
}
key_values!{ "compact-listing", derive (Debug, Clone, Deserialize) :
key_values! { "compact-listing", derive (Debug, Clone, Deserialize) :
pub struct CompactListingShortcuts {
open_thread: Key |> "Open thread.",
exit_thread: Key |> "Exit thread view.",
@ -52,24 +52,23 @@ pub struct CompactListingShortcuts {
}
}
impl Default for CompactListingShortcuts {
fn default() -> Self {
CompactListingShortcuts {
CompactListingShortcuts {
open_thread: Key::Char('\n'),
exit_thread: Key::Char('i'),
prev_page: Key::PageUp,
next_page: Key::PageDown,
prev_folder: Key::Char('J'),
next_folder: Key::Char('K'),
prev_account:Key::Char('h'),
next_account:Key::Char('l'),
prev_account: Key::Char('h'),
next_account: Key::Char('l'),
new_mail: Key::Char('m'),
}
}
}
key_values!{ "contact-list", derive (Debug, Clone, Deserialize) :
key_values! { "contact-list", derive (Debug, Clone, Deserialize) :
pub struct ContactListShortcuts {
create_contact: Key |> "Create new contact.",
edit_contact: Key |> "Edit contact under cursor."
@ -78,14 +77,14 @@ pub struct ContactListShortcuts {
impl Default for ContactListShortcuts {
fn default() -> Self {
ContactListShortcuts {
ContactListShortcuts {
create_contact: Key::Char('c'),
edit_contact: Key::Char('e'),
}
}
}
key_values!{ "pager", derive (Debug, Clone, Deserialize) :
key_values! { "pager", derive (Debug, Clone, Deserialize) :
pub struct PagerShortcuts {
scroll_up: Key |> "Scroll up pager.",
scroll_down: Key |> "Scroll down pager.",

View File

@ -23,20 +23,20 @@
* User actions that need to be handled by the UI
*/
pub use melib::mailbox::{SortField, SortOrder};
use components::Component;
pub use melib::mailbox::{SortField, SortOrder};
extern crate uuid;
use uuid::Uuid;
#[derive(Debug, )]
#[derive(Debug)]
pub enum ListingAction {
SetPlain,
SetThreaded,
SetCompact,
}
#[derive(Debug, )]
#[derive(Debug)]
pub enum TabAction {
TabOpen(Option<Box<Component>>),
NewDraft,
@ -45,7 +45,7 @@ pub enum TabAction {
Kill(Uuid),
}
#[derive(Debug, )]
#[derive(Debug)]
pub enum Action {
Listing(ListingAction),
ViewMailbox(usize),

View File

@ -21,12 +21,12 @@
/*! The application's state.
The UI crate has an Entity-Component-System design. The System part, is also the application's state, so they're both merged in the `State` struct.
The UI crate has an Entity-Component-System design. The System part, is also the application's state, so they're both merged in the `State` struct.
`State` owns all the Entities of the UI, which are currently plain Containers for `Component`s. In the application's main event loop, input is handed to the state in the form of `UIEvent` objects which traverse the entity graph. Components decide to handle each input or not.
`State` owns all the Entities of the UI, which are currently plain Containers for `Component`s. In the application's main event loop, input is handed to the state in the form of `UIEvent` objects which traverse the entity graph. Components decide to handle each input or not.
Input is received in the main loop from threads which listen on the stdin for user input, observe folders for file changes etc. The relevant struct is `ThreadEvent`.
*/
Input is received in the main loop from threads which listen on the stdin for user input, observe folders for file changes etc. The relevant struct is `ThreadEvent`.
*/
use super::*;
use melib::backends::{FolderHash, NotifyFn};
@ -64,7 +64,8 @@ impl InputHandler {
},
&rx,
)
}).unwrap();
})
.unwrap();
}
fn kill(&self) {
self.tx.send(false);
@ -141,7 +142,8 @@ impl Drop for State {
cursor::Goto(1, 1),
cursor::Show,
BracketModeEnd,
).unwrap();
)
.unwrap();
self.flush();
}
}
@ -187,7 +189,8 @@ impl State {
sender.send(ThreadEvent::UIEvent(UIEventType::StartupCheck))
})),
)
}).collect();
})
.collect();
accounts.sort_by(|a, b| a.name().cmp(&b.name()));
let mut s = State {
cols,
@ -235,7 +238,8 @@ impl State {
cursor::Hide,
clear::All,
cursor::Goto(1, 1)
).unwrap();
)
.unwrap();
s.flush();
eprintln!("DEBUG: inserting mailbox hashes:");
for (x, account) in s.context.accounts.iter_mut().enumerate() {
@ -305,7 +309,8 @@ impl State {
"{}{}",
termion::screen::ToMainScreen,
cursor::Show
).unwrap();
)
.unwrap();
self.flush();
self.stdout = None;
self.context.input.kill();
@ -322,7 +327,8 @@ impl State {
cursor::Hide,
clear::All,
cursor::Goto(1, 1)
).unwrap();
)
.unwrap();
self.flush();
}
@ -381,7 +387,8 @@ impl State {
self.stdout(),
"{}",
cursor::Goto(get_x(upper_left) as u16 + 1, (y + 1) as u16)
).unwrap();
)
.unwrap();
for x in get_x(upper_left)..=get_x(bottom_right) {
let c = self.grid[(x, y)];
@ -397,14 +404,16 @@ impl State {
self.stdout(),
"{}",
termion::color::Bg(termion::color::Reset)
).unwrap();
)
.unwrap();
}
if c.fg() != Color::Default {
write!(
self.stdout(),
"{}",
termion::color::Fg(termion::color::Reset)
).unwrap();
)
.unwrap();
}
}
}
@ -473,12 +482,12 @@ impl State {
self.flush();
}
return;
},
}
UIEventType::ChangeMode(m) => {
self.context
.sender
.send(ThreadEvent::UIEvent(UIEventType::ChangeMode(m)));
},
}
_ => {}
}
/* inform each entity */

View File

@ -19,8 +19,8 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>.
*/
extern crate serde;
use self::serde::{de, Deserialize, Deserializer, };
use self::serde::de::Visitor;
use self::serde::{de, Deserialize, Deserializer};
#[macro_use]
mod position;

View File

@ -20,9 +20,9 @@
*/
/*!
Define a (x, y) point in the terminal display as a holder of a character, foreground/background
colors and attributes.
*/
Define a (x, y) point in the terminal display as a holder of a character, foreground/background
colors and attributes.
*/
use super::position::*;
use std::convert::From;
use std::fmt;
@ -183,7 +183,6 @@ impl CellBuffer {
self.cols = 0;
self.rows = 0;
}
}
impl HasSize for CellBuffer {
@ -580,7 +579,7 @@ pub fn copy_area_with_break(
);
return upper_left!(dest);
}
if grid_src.is_empty() || grid_dest.is_empty() {
return upper_left!(dest);
}
@ -755,7 +754,10 @@ pub fn word_break_string(mut s: &str, width: usize) -> Vec<&str> {
}
}
if s.len() > width {
if let Some(next_idx) = s.as_bytes()[..width].iter().rposition(u8::is_ascii_whitespace) {
if let Some(next_idx) = s.as_bytes()[..width]
.iter()
.rposition(u8::is_ascii_whitespace)
{
ret.push(&s[..next_idx]);
s = &s[next_idx + 1..];
} else {
@ -766,7 +768,6 @@ pub fn word_break_string(mut s: &str, width: usize) -> Vec<&str> {
ret.push(s);
break;
}
}
ret

View File

@ -203,44 +203,43 @@ pub const BRACKET_PASTE_END: &[u8] = b"\x1B[201~";
const FIELDS: &[&str] = &[];
impl<'de> Deserialize<'de> for Key {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct KeyVisitor;
where
D: Deserializer<'de>,
{
struct KeyVisitor;
impl<'de> Visitor<'de> for KeyVisitor {
type Value = Key;
impl<'de> Visitor<'de> for KeyVisitor {
type Value = Key;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("`secs` or `nanos`")
}
fn visit_str<E>(self, value: &str) -> Result<Key, E>
where
E: de::Error,
{
match value {
"Backspace" => Ok(Key::Backspace),
"Left" => Ok(Key::Left),
"Right" => Ok(Key::Right),
"Up" => Ok(Key::Up),
"Down" => Ok(Key::Down),
"Home" => Ok(Key::Home),
"End" => Ok(Key::End),
"PageUp" => Ok(Key::PageUp),
"PageDown" => Ok(Key::PageDown),
"Delete" => Ok(Key::Delete),
"Insert" => Ok(Key::Insert),
"Esc" => Ok(Key::Esc),
ref s if s.len() == 1 => Ok(Key::Char(s.chars().nth(0).unwrap())),
_ => Err(de::Error::unknown_field(value, FIELDS)),
}
}
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("`secs` or `nanos`")
}
deserializer.deserialize_identifier(KeyVisitor)
fn visit_str<E>(self, value: &str) -> Result<Key, E>
where
E: de::Error,
{
match value {
"Backspace" => Ok(Key::Backspace),
"Left" => Ok(Key::Left),
"Right" => Ok(Key::Right),
"Up" => Ok(Key::Up),
"Down" => Ok(Key::Down),
"Home" => Ok(Key::Home),
"End" => Ok(Key::End),
"PageUp" => Ok(Key::PageUp),
"PageDown" => Ok(Key::PageDown),
"Delete" => Ok(Key::Delete),
"Insert" => Ok(Key::Insert),
"Esc" => Ok(Key::Esc),
ref s if s.len() == 1 => Ok(Key::Char(s.chars().nth(0).unwrap())),
_ => Err(de::Error::unknown_field(value, FIELDS)),
}
}
}
deserializer.deserialize_identifier(KeyVisitor)
}
}

View File

@ -20,10 +20,10 @@
*/
/*!
Simple type definitions and macro helper for a (x,y) position on the terminal and the areas they define.
Simple type definitions and macro helper for a (x,y) position on the terminal and the areas they define.
An `Area` consists of two points: the upper left and bottom right corners.
*/
An `Area` consists of two points: the upper left and bottom right corners.
*/
/// A `(x, y)` position on screen.
pub type Pos = (usize, usize);

View File

@ -206,7 +206,7 @@ fn wcwidth(ucs: WChar) -> Option<usize> {
(ucs >= 0xff00 && ucs <= 0xff5f) || /* Fullwidth Forms */
(ucs >= 0xffe0 && ucs <= 0xffe6) ||
(ucs >= 0x20000 && ucs <= 0x2ffff))
)
),
);
}