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/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
use chrono::{DateTime, Local}; use chrono::{DateTime, Local};
use uuid::Uuid;
use fnv::FnvHashMap; use fnv::FnvHashMap;
use uuid::Uuid;
use std::ops::Deref; use std::ops::Deref;
@ -31,7 +31,7 @@ pub struct AddressBook {
display_name: String, display_name: String,
created: DateTime<Local>, created: DateTime<Local>,
last_edited: DateTime<Local>, last_edited: DateTime<Local>,
cards: FnvHashMap<CardId, Card> cards: FnvHashMap<CardId, Card>,
} }
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
@ -44,7 +44,6 @@ pub struct Card {
name_prefix: String, name_prefix: String,
name_suffix: String, name_suffix: String,
//address //address
birthday: Option<DateTime<Local>>, birthday: Option<DateTime<Local>>,
email: String, email: String,
url: String, url: String,
@ -74,7 +73,11 @@ impl AddressBook {
self.cards.contains_key(&card_id) self.cards.contains_key(&card_id)
} }
pub fn search(&self, term: &str) -> Vec<String> { 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 { impl Card {
pub fn new() -> Card { pub fn new() -> Card {
Card { Card {
@ -98,7 +100,6 @@ impl Card {
name_prefix: String::new(), name_prefix: String::new(),
name_suffix: String::new(), name_suffix: String::new(),
//address //address
birthday: None, birthday: None,
email: String::new(), email: String::new(),
url: String::new(), url: String::new(),
@ -182,7 +183,6 @@ impl Card {
pub fn extra_property(&self, key: &str) -> Option<&str> { pub fn extra_property(&self, key: &str) -> Option<&str> {
self.extra_properties.get(key).map(|v| v.as_str()) self.extra_properties.get(key).map(|v| v.as_str())
} }
} }
impl From<FnvHashMap<String, String>> for Card { 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 * You should have received a copy of the GNU General Public License
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
pub mod addressbook;
pub mod async; pub mod async;
pub mod conf; pub mod conf;
pub mod error; pub mod error;
pub mod mailbox; pub mod mailbox;
pub mod addressbook;
#[macro_use] #[macro_use]
extern crate serde_derive; extern crate serde_derive;
@ -38,8 +38,8 @@ extern crate chan;
#[macro_use] #[macro_use]
extern crate bitflags; extern crate bitflags;
extern crate uuid;
extern crate fnv; extern crate fnv;
extern crate uuid;
pub use conf::*; pub use conf::*;
pub use mailbox::*; pub use mailbox::*;

View File

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

View File

@ -56,24 +56,24 @@ impl Collection {
let threads = Threads::new(&mut envelopes); let threads = Threads::new(&mut envelopes);
/*let cache_dir = /*let cache_dir =
xdg::BaseDirectories::with_profile("meli", format!("{}_Thread", folder.hash())) xdg::BaseDirectories::with_profile("meli", format!("{}_Thread", folder.hash()))
.unwrap(); .unwrap();
if let Some(cached) = cache_dir.find_cache_file("threads") { if let Some(cached) = cache_dir.find_cache_file("threads") {
let reader = io::BufReader::new(fs::File::open(cached).unwrap()); let reader = io::BufReader::new(fs::File::open(cached).unwrap());
let result: result::Result<Threads, _> = bincode::deserialize_from(reader); let result: result::Result<Threads, _> = bincode::deserialize_from(reader);
let ret = if let Ok(mut cached_t) = result { let ret = if let Ok(mut cached_t) = result {
use std::iter::FromIterator; 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); 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.amend(&mut envelopes);
cached_t cached_t
} else { } else {
Threads::new(&mut envelopes) Threads::new(&mut envelopes)
}; };
ret ret
} else { } else {
Threads::new(&mut envelopes) Threads::new(&mut envelopes)
}; };
*/ */
Collection { Collection {
folder: folder.clone(), folder: folder.clone(),

View File

@ -74,7 +74,7 @@ impl Address {
Address::Group(g) => g.display_name.display(&g.raw), Address::Group(g) => g.display_name.display(&g.raw),
} }
} }
pub fn get_email(&self) -> String { pub fn get_email(&self) -> String {
match self { match self {
Address::Mailbox(m) => m.address_spec.display(&m.raw), 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) s.address_spec.display(&s.raw) == o.address_spec.display(&o.raw)
} }
(Address::Group(s), Address::Group(o)) => { (Address::Group(s), Address::Group(o)) => {
s.display_name.display(&s.raw) == o.display_name.display(&o.raw) && s s.display_name.display(&s.raw) == o.display_name.display(&o.raw)
.mailbox_list && s.mailbox_list
.iter() .iter()
.zip(o.mailbox_list.iter()) .zip(o.mailbox_list.iter())
.fold(true, |b, (s, o)| b && (s == o)) .fold(true, |b, (s, o)| b && (s == o))
} }
} }
} }
@ -616,22 +616,22 @@ impl Envelope {
pub fn message_id_raw(&self) -> Cow<str> { pub fn message_id_raw(&self) -> Cow<str> {
String::from_utf8_lossy(self.message_id.raw()) 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(); 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; 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; 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; 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; 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() { let slice = match parser::message_id(new_val).to_full_result() {
Ok(v) => v, Ok(v) => v,
Err(_) => { Err(_) => {
@ -641,10 +641,10 @@ impl Envelope {
}; };
self.in_reply_to = Some(MessageID::new(new_val, slice)); 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); 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() { let slice = match parser::message_id(new_val).to_full_result() {
Ok(v) => v, Ok(v) => v,
Err(_) => { Err(_) => {
@ -653,7 +653,7 @@ impl Envelope {
}; };
self.message_id = MessageID::new(new_val, slice); 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() { let slice = match parser::message_id(new_val).to_full_result() {
Ok(v) => v, Ok(v) => v,
Err(_) => { 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 { match self.references {
Some(ref mut s) => { Some(ref mut s) => {
s.raw = new_val.into(); s.raw = new_val.into();
@ -713,10 +713,10 @@ impl Envelope {
pub fn thread(&self) -> usize { pub fn thread(&self) -> usize {
self.thread self.thread
} }
pub fn set_thread(&mut self, new_val: usize) { pub fn set_thread(&mut self, new_val: usize) {
self.thread = new_val; 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; self.timestamp = new_val.timestamp() as UnixTimestamp;
} }
pub fn set_flag(&mut self, f: Flag, mut operation: Box<BackendOp>) -> Result<()> { 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 { pub fn content_type(&mut self, value: &[u8]) -> &Self {
match parser::content_type(value).to_full_result() { match parser::content_type(value).to_full_result() {
Ok((ct, cst, params)) => if ct.eq_ignore_ascii_case(b"multipart") { Ok((ct, cst, params)) => {
let mut boundary = None; if ct.eq_ignore_ascii_case(b"multipart") {
for (n, v) in params { let mut boundary = None;
if n.eq_ignore_ascii_case(b"boundary") { for (n, v) in params {
boundary = Some(v); if n.eq_ignore_ascii_case(b"boundary") {
break; boundary = Some(v);
break;
}
} }
} assert!(boundary.is_some());
assert!(boundary.is_some()); let _boundary = boundary.unwrap();
let _boundary = boundary.unwrap(); let offset =
let offset = (_boundary.as_ptr() as usize).wrapping_sub(value.as_ptr() as usize); (_boundary.as_ptr() as usize).wrapping_sub(value.as_ptr() as usize);
let boundary = SliceBuild::new(offset, _boundary.len()); let boundary = SliceBuild::new(offset, _boundary.len());
let subattachments = Self::subattachments(&self.raw, boundary.get(&value)); let subattachments = Self::subattachments(&self.raw, boundary.get(&value));
assert!(!subattachments.is_empty()); assert!(!subattachments.is_empty());
self.content_type = ContentType::Multipart { self.content_type = ContentType::Multipart {
boundary, boundary,
kind: if cst.eq_ignore_ascii_case(b"mixed") { kind: if cst.eq_ignore_ascii_case(b"mixed") {
MultipartType::Mixed MultipartType::Mixed
} else if cst.eq_ignore_ascii_case(b"alternative") { } else if cst.eq_ignore_ascii_case(b"alternative") {
MultipartType::Alternative MultipartType::Alternative
} else if cst.eq_ignore_ascii_case(b"digest") { } else if cst.eq_ignore_ascii_case(b"digest") {
MultipartType::Digest MultipartType::Digest
} else { } else {
Default::default() Default::default()
}, },
subattachments, subattachments,
}; };
} else if ct.eq_ignore_ascii_case(b"text") { } else if ct.eq_ignore_ascii_case(b"text") {
self.content_type = ContentType::Text { self.content_type = ContentType::Text {
kind: Text::Plain, kind: Text::Plain,
charset: Charset::UTF8, charset: Charset::UTF8,
}; };
for (n, v) in params { for (n, v) in params {
if n.eq_ignore_ascii_case(b"charset") { 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 { if let ContentType::Text {
charset: ref mut c, .. kind: ref mut k, ..
} = self.content_type } = 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) => { Err(v) => {
eprintln!("parsing error in content_type: {:?} {:?}", value, 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, kind: ref multipart_type,
subattachments: ref sub_att_vec, subattachments: ref sub_att_vec,
.. ..
} => if *multipart_type == MultipartType::Alternative { } => {
for a in sub_att_vec { if *multipart_type == MultipartType::Alternative {
if let ContentType::Text { for a in sub_att_vec {
kind: Text::Plain, .. if let ContentType::Text {
} = a.content_type kind: Text::Plain, ..
{ } = a.content_type
return decode_helper(a, filter); {
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 { if let Some(filter) = filter {
filter(a, &mut ret); filter(a, &mut ret);

View File

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

View File

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

0
rustfmt.toml 100644
View File

View File

@ -48,9 +48,9 @@ fn main() {
//let _stdout = stdout(); //let _stdout = stdout();
//let mut _stdout = _stdout.lock(); //let mut _stdout = _stdout.lock();
/* /*
let _stderr = stderr(); let _stderr = stderr();
let mut _stderr = _stderr.lock(); let mut _stderr = _stderr.lock();
*/ */
/* Catch SIGWINCH to handle terminal resizing */ /* Catch SIGWINCH to handle terminal resizing */
let signal = chan_signal::notify(&[Signal::WINCH]); 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 menu = Entity::from(Box::new(AccountMenu::new(&state.context.accounts)));
let listing = listing::Listing::from(IndexStyle::Compact); let listing = listing::Listing::from(IndexStyle::Compact);
let b = Entity::from(Box::new(listing)); 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 window = Entity::from(tabs);
let status_bar = Entity::from(Box::new(StatusBar::new(window))); let status_bar = Entity::from(Box::new(StatusBar::new(window)));
@ -75,7 +79,9 @@ fn main() {
let xdg_notifications = let xdg_notifications =
Entity::from(Box::new(ui::components::notifications::XDGNotifications {})); Entity::from(Box::new(ui::components::notifications::XDGNotifications {}));
state.register_entity(xdg_notifications); 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 */ /* Keep track of the input mode. See ui::UIMode for details */
'main: loop { '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::*; use super::*;
@ -76,8 +76,6 @@ const _DOUBLE_DOWN_AND_LEFT: char = '╗';
const _DOUBLE_UP_AND_LEFT: char = '╝'; const _DOUBLE_UP_AND_LEFT: char = '╝';
const _DOUBLE_UP_AND_RIGHT: char = '╚'; const _DOUBLE_UP_AND_RIGHT: char = '╚';
type EntityId = Uuid; type EntityId = Uuid;
/// `Entity` is a container for Components. /// `Entity` is a container for Components.
@ -157,7 +155,9 @@ pub trait Component: Display + Debug + Send {
fn kill(&mut self, _id: EntityId) {} fn kill(&mut self, _id: EntityId) {}
fn set_id(&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) { pub fn create_box(grid: &mut CellBuffer, area: Area) {
let upper_left = upper_left!(area); if !is_valid_area!(area) {
let bottom_right = bottom_right!(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) { 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(upper_left))].set_ch(HORZ_BOUNDARY);
grid[(x, get_y(bottom_right))].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) { for y in get_y(upper_left)..get_y(bottom_right) {
grid[(get_x(upper_left), y)].set_ch(VERT_BOUNDARY); grid[(get_x(upper_left), y)].set_ch(VERT_BOUNDARY);
grid[(get_x(bottom_right), 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, 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_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, set_y(upper_left, get_y(bottom_right)), VERT_BOUNDARY);
set_and_join_box(grid, bottom_right, VERT_BOUNDARY); set_and_join_box(grid, bottom_right, VERT_BOUNDARY);
} }

View File

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

View File

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

View File

@ -58,8 +58,7 @@ impl Default for Indexer {
} }
impl 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 { impl Component for Indexer {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -342,7 +342,9 @@ impl CompactListing {
fn format_date(envelope: &Envelope) -> String { fn format_date(envelope: &Envelope) -> String {
let d = std::time::UNIX_EPOCH + std::time::Duration::from_secs(envelope.date()); 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() { match now.as_secs() {
n if n < 10 * 60 * 60 => format!("{} hours ago{}", n / (60 * 60), " ".repeat(8)), 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)), 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 { 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(); 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(
map.insert("prev_page", if let Some(key) = config_map.get("prev_page") { (*key).clone() } else { Key::PageUp }); "open_thread",
map.insert("next_page", if let Some(key) = config_map.get("next_page") { (*key).clone() } else { Key::PageDown }); if let Some(key) = config_map.get("open_thread") {
map.insert("exit_thread", if let Some(key) = config_map.get("exit_thread") { (*key).clone() } else { Key::Char('i') }); (*key).clone()
map.insert("prev_folder", if let Some(key) = config_map.get("prev_folder") { (*key).clone() } else { Key::Char('J') }); } else {
map.insert("next_folder", if let Some(key) = config_map.get("next_folder") { (*key).clone() } else { Key::Char('K') }); Key::Char('\n')
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(
"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 map
} }
} }

View File

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

View File

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

View File

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

View File

@ -252,7 +252,12 @@ impl Pager {
self.cursor_pos = 0; self.cursor_pos = 0;
self.max_cursor_pos = None; 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 pager_filter: Option<&String> = context.settings.pager.filter.as_ref();
//let format_flowed: bool = context.settings.pager.format_flowed; //let format_flowed: bool = context.settings.pager.format_flowed;
if let Some(bin) = pager_filter { if let Some(bin) = pager_filter {
@ -275,10 +280,10 @@ impl Pager {
.wait_with_output() .wait_with_output()
.expect("Failed to wait on filter") .expect("Failed to wait on filter")
.stdout, .stdout,
).to_string(); )
.to_string();
} }
let content = { let content = {
let lines: Vec<&str> = if let Some(width) = width { let lines: Vec<&str> = if let Some(width) = width {
word_break_string(text.as_str(), width) word_break_string(text.as_str(), width)
@ -313,7 +318,7 @@ impl Pager {
let height = lines.len() + 1; let height = lines.len() + 1;
let width = width.unwrap_or_else(|| lines.iter().map(|l| l.len()).max().unwrap_or(0)); 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(' ')); let mut content = CellBuffer::new(width, height, Cell::with_char(' '));
Pager::print_string(&mut content, lines); Pager::print_string(&mut content, lines);
Pager { Pager {
text: text.to_string(), text: text.to_string(),
@ -347,7 +352,7 @@ impl Pager {
Color::Default, Color::Default,
((0, i), (width - 1, i)), ((0, i), (width - 1, i)),
true, true,
); );
} }
} }
pub fn cursor_pos(&self) -> usize { pub fn cursor_pos(&self) -> usize {
@ -824,7 +829,10 @@ impl Tabbed {
let cslice: &mut [Cell] = grid; let cslice: &mut [Cell] = grid;
//TODO: bounds check //TODO: bounds check
let cslice_len = cslice.len(); 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_bg(Color::Byte(7));
c.set_ch(' '); c.set_ch(' ');
} }
@ -846,7 +854,13 @@ impl fmt::Display for Tabbed {
impl Component for Tabbed { impl Component for Tabbed {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
if self.dirty { 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; self.dirty = false;
} }
@ -871,29 +885,50 @@ impl Component for Tabbed {
if self.show_shortcuts { if self.show_shortcuts {
let area = ( 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); clear_area(grid, area);
create_box(grid, area); create_box(grid, area);
// TODO: print into a pager // TODO: print into a pager
for (idx, (k, v)) in self.children[self.cursor_pos].get_shortcuts(context).into_iter().enumerate() { for (idx, (k, v)) in self.children[self.cursor_pos]
let (x, y) = write_string_to_grid( .get_shortcuts(context)
&k, .into_iter()
grid, .enumerate()
Color::Byte(29), {
Color::Default, let (x, y) = write_string_to_grid(
(pos_inc(upper_left!(area), (2, 1 + idx)), set_x(bottom_right!(area), get_x(bottom_right!(area)).saturating_sub(2))), &k,
false, grid,
); Color::Byte(29),
write_string_to_grid( Color::Default,
&format!("{:?}", v), (
grid, pos_inc(upper_left!(area), (2, 1 + idx)),
Color::Default, set_x(
Color::Default, bottom_right!(area),
((x + 2, y), set_x(bottom_right!(area), get_x(bottom_right!(area)).saturating_sub(2))), get_x(bottom_right!(area)).saturating_sub(2),
false, ),
); ),
}; 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); context.dirty_areas.push_back(area);
} }
} }
@ -959,12 +994,12 @@ impl Component for Tabbed {
} }
} }
type EntryIdentifier = Vec<u8>; type EntryIdentifier = Vec<u8>;
/// Shows selection to user /// Shows selection to user
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct Selector { pub struct Selector {
single_only: bool, /// allow only one selection single_only: bool,
/// allow only one selection
entries: Vec<(EntryIdentifier, bool)>, entries: Vec<(EntryIdentifier, bool)>,
selected_entry_count: u32, selected_entry_count: u32,
content: CellBuffer, content: CellBuffer,
@ -982,21 +1017,15 @@ impl fmt::Display for Selector {
impl Component for Selector { impl Component for Selector {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) {
eprintln!("drawing");
let (width, height) = self.content.size(); let (width, height) = self.content.size();
copy_area_with_break( copy_area_with_break(grid, &self.content, area, ((0, 0), (width, height)));
grid,
&self.content,
area,
((0, 0), (width, height)),
);
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
} }
fn process_event(&mut self, event: &mut UIEvent, _context: &mut Context) -> bool { fn process_event(&mut self, event: &mut UIEvent, _context: &mut Context) -> bool {
let (width, height) = self.content.size(); let (width, height) = self.content.size();
match event.event_type { match event.event_type {
UIEventType::Input(Key::Char(' ')) => { 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 { if self.entries[self.cursor].1 {
write_string_to_grid( write_string_to_grid(
"x", "x",
@ -1005,7 +1034,7 @@ impl Component for Selector {
Color::Default, Color::Default,
((1, self.cursor), (width, self.cursor)), ((1, self.cursor), (width, self.cursor)),
false, false,
); );
} else { } else {
write_string_to_grid( write_string_to_grid(
" ", " ",
@ -1014,21 +1043,21 @@ impl Component for Selector {
Color::Default, Color::Default,
((1, self.cursor), (width, self.cursor)), ((1, self.cursor), (width, self.cursor)),
false, false,
); );
} }
self.dirty = true; self.dirty = true;
return true; return true;
}, }
UIEventType::Input(Key::Up) if self.cursor > 0 => { UIEventType::Input(Key::Up) if self.cursor > 0 => {
self.cursor -= 1; self.cursor -= 1;
self.dirty = true; self.dirty = true;
return true; return true;
}, }
UIEventType::Input(Key::Down) if self.cursor < height.saturating_sub(1) => { UIEventType::Input(Key::Down) if self.cursor < height.saturating_sub(1) => {
self.cursor += 1; self.cursor += 1;
self.dirty = true; self.dirty = true;
return true; return true;
}, }
_ => {} _ => {}
} }
@ -1044,10 +1073,18 @@ impl Component for Selector {
impl Selector { impl Selector {
pub fn new(mut entries: Vec<(EntryIdentifier, String)>, single_only: bool) -> 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 height = entries.len();
let mut content = CellBuffer::new(width, height, Cell::with_char(' ')); 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() { for (i, e) in entries.into_iter().enumerate() {
write_string_to_grid( write_string_to_grid(
&format!("[ ] {}", e.1), &format!("[ ] {}", e.1),
@ -1070,6 +1107,10 @@ impl Selector {
} }
pub fn collect(self) -> Vec<EntryIdentifier> { 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 { 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), Choice(Vec<String>, Cursor),
TextArea(String, Cursor), TextArea(String, Cursor),
} }
@ -42,12 +46,8 @@ impl Default for Field {
impl Field { impl Field {
fn as_str(&self) -> &str { fn as_str(&self) -> &str {
match self { match self {
Text(ref s, _, _) => { Text(ref s, _, _) => s,
s TextArea(ref s, _) => s,
},
TextArea(ref s, _) => {
s
},
Choice(ref v, cursor) => { Choice(ref v, cursor) => {
if v.is_empty() { if v.is_empty() {
"" ""
@ -60,48 +60,56 @@ impl Field {
pub fn into_string(self) -> String { pub fn into_string(self) -> String {
match self { match self {
Text(s, _, _) => { Text(s, _, _) => s,
s TextArea(s, _) => s,
}, Choice(mut v, cursor) => v.remove(cursor),
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); let upper_left = upper_left!(area);
match self { match self {
Text(ref term, cursor, auto_complete_fn) => { 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)); change_colors(
if term.chars().count() <= 2 { return; } 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 { if let Some((auto_complete_fn, auto_complete)) = auto_complete_fn {
let entries = auto_complete_fn(context, term); let entries = auto_complete_fn(context, term);
auto_complete.set_suggestions(entries); auto_complete.set_suggestions(entries);
auto_complete.draw(grid, secondary_area, context); auto_complete.draw(grid, secondary_area, context);
} }
},
TextArea(_, _) => {
},
Choice(_, _cursor) => {
} }
TextArea(_, _) => {}
Choice(_, _cursor) => {}
} }
} }
} }
impl Component for Field { impl Component for Field {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, _context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, _context: &mut Context) {
write_string_to_grid( write_string_to_grid(
self.as_str(), self.as_str(),
grid, grid,
Color::Default, Color::Default,
Color::Default, Color::Default,
area, area,
true); true,
);
} }
fn process_event(&mut self, event: &mut UIEvent, _context: &mut Context) -> bool { 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 { 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(); *cursor = s.chars().count();
return true; return true;
} }
}, }
_ => {}, _ => {}
} }
} }
@ -124,74 +132,68 @@ impl Component for Field {
} else { } else {
return false; return false;
} }
}, }
UIEventType::InsertInput(Key::Down) => { UIEventType::InsertInput(Key::Down) => {
if let Text(_, _, Some((_, auto_complete))) = self { if let Text(_, _, Some((_, auto_complete))) = self {
auto_complete.inc_cursor(); auto_complete.inc_cursor();
} else { } else {
return false; return false;
} }
}, }
UIEventType::InsertInput(Key::Right) => { UIEventType::InsertInput(Key::Right) => match self {
match self { TextArea(ref s, ref mut cursor) | Text(ref s, ref mut cursor, _) => {
TextArea(ref s, ref mut cursor) | Text(ref s, ref mut cursor, _) => { if *cursor < s.len() {
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);
*cursor += 1; *cursor += 1;
}, }
_ => {} }
Choice(ref vec, ref mut cursor) => {
*cursor = if *cursor == vec.len().saturating_sub(1) {
0
} else {
*cursor + 1
};
} }
}, },
UIEventType::InsertInput(Key::Backspace) => { UIEventType::InsertInput(Key::Left) => match self {
match self { TextArea(_, ref mut cursor) | Text(_, ref mut cursor, _) => {
Text(ref mut s, ref mut cursor, ref mut auto_complete) => { if *cursor == 0 {
if *cursor > 0 { return false;
*cursor -= 1; } else {
s.remove(*cursor); *cursor -= 1;
} }
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);
}
},
_ => {}
} }
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; return false;
@ -212,7 +214,6 @@ impl fmt::Display for Field {
} }
} }
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct FormWidget { pub struct FormWidget {
fields: FnvHashMap<String, Field>, fields: FnvHashMap<String, Field>,
@ -272,10 +273,20 @@ impl FormWidget {
self.layout.push(value.0.clone()); self.layout.push(value.0.clone());
self.fields.insert(value.0, TextArea(value.1, 0)); 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.field_name_max_length = std::cmp::max(self.field_name_max_length, value.0.len());
self.layout.push(value.0.clone()); 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)) { pub fn push(&mut self, value: (String, String)) {
self.field_name_max_length = std::cmp::max(self.field_name_max_length, value.0.len()); 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, grid,
Color::Default, Color::Default,
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, false,
); );
/* draw field */ /* draw field */
v.draw(grid, v.draw(
(pos_inc(upper_left, (self.field_name_max_length + 3, i)), set_y(bottom_right, i + get_y(upper_left))), context); 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 */ /* Highlight if necessary */
if i == self.cursor { if i == self.cursor {
if self.focus == FormFocus::Fields { 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 { if self.focus == FormFocus::TextInput {
v.draw_cursor(grid, v.draw_cursor(
(pos_inc(upper_left, (self.field_name_max_length + 3 , i)), grid,
(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), pos_inc(upper_left, (self.field_name_max_length + 3, i)),
context); (
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 { if !self.hide_buttons {
let length = self.layout.len(); let length = self.layout.len();
self.buttons.draw(grid, self.buttons.draw(
(pos_inc(upper_left, (1, length * 2 + 3)), set_y(bottom_right, length * 2 + 3 + get_y(upper_left))), grid,
context); (
pos_inc(upper_left, (1, length * 2 + 3)),
set_y(bottom_right, length * 2 + 3 + get_y(upper_left)),
),
context,
);
} }
self.dirty = false; self.dirty = false;
context.dirty_areas.push_back(area); context.dirty_areas.push_back(area);
@ -356,43 +398,43 @@ impl Component for FormWidget {
match event.event_type { match event.event_type {
UIEventType::Input(Key::Up) if self.focus == FormFocus::Buttons => { UIEventType::Input(Key::Up) if self.focus == FormFocus::Buttons => {
self.focus = FormFocus::Fields; self.focus = FormFocus::Fields;
}, }
UIEventType::InsertInput(Key::Up) if self.focus == FormFocus::TextInput => { UIEventType::InsertInput(Key::Up) if self.focus == FormFocus::TextInput => {
let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap(); let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
field.process_event(event, context); field.process_event(event, context);
}, }
UIEventType::Input(Key::Up) => { UIEventType::Input(Key::Up) => {
self.cursor = self.cursor.saturating_sub(1); self.cursor = self.cursor.saturating_sub(1);
}, }
UIEventType::InsertInput(Key::Down) if self.focus == FormFocus::TextInput => { UIEventType::InsertInput(Key::Down) if self.focus == FormFocus::TextInput => {
let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap(); let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
field.process_event(event, context); field.process_event(event, context);
}, }
UIEventType::Input(Key::Down) if self.cursor < self.layout.len().saturating_sub(1) => { UIEventType::Input(Key::Down) if self.cursor < self.layout.len().saturating_sub(1) => {
self.cursor += 1; self.cursor += 1;
}, }
UIEventType::Input(Key::Down) if self.focus == FormFocus::Fields => { UIEventType::Input(Key::Down) if self.focus == FormFocus::Fields => {
self.focus = FormFocus::Buttons; self.focus = FormFocus::Buttons;
if self.hide_buttons { if self.hide_buttons {
self.set_dirty(); self.set_dirty();
return false; return false;
} }
}, }
UIEventType::InsertInput(Key::Char('\t')) if self.focus == FormFocus::TextInput => { UIEventType::InsertInput(Key::Char('\t')) if self.focus == FormFocus::TextInput => {
let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap(); let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
field.process_event(event, context); field.process_event(event, context);
}, }
UIEventType::Input(Key::Char('\n')) if self.focus == FormFocus::Fields => { UIEventType::Input(Key::Char('\n')) if self.focus == FormFocus::Fields => {
self.focus = FormFocus::TextInput; self.focus = FormFocus::TextInput;
context.replies.push_back(UIEvent { context.replies.push_back(UIEvent {
id: 0, id: 0,
event_type: UIEventType::ChangeMode(UIMode::Insert), event_type: UIEventType::ChangeMode(UIMode::Insert),
}); });
}, }
UIEventType::InsertInput(Key::Right) if self.focus == FormFocus::TextInput => { UIEventType::InsertInput(Key::Right) if self.focus == FormFocus::TextInput => {
let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap(); let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
field.process_event(event, context); field.process_event(event, context);
}, }
UIEventType::InsertInput(Key::Left) if self.focus == FormFocus::TextInput => { UIEventType::InsertInput(Key::Left) if self.focus == FormFocus::TextInput => {
let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap(); let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
if !field.process_event(event, context) { if !field.process_event(event, context) {
@ -402,18 +444,18 @@ impl Component for FormWidget {
event_type: UIEventType::ChangeMode(UIMode::Normal), event_type: UIEventType::ChangeMode(UIMode::Normal),
}); });
} }
}, }
UIEventType::ChangeMode(UIMode::Normal) if self.focus == FormFocus::TextInput => { UIEventType::ChangeMode(UIMode::Normal) if self.focus == FormFocus::TextInput => {
self.focus = FormFocus::Fields; self.focus = FormFocus::Fields;
}, }
UIEventType::InsertInput(Key::Char(_)) if self.focus == FormFocus::TextInput => { UIEventType::InsertInput(Key::Char(_)) if self.focus == FormFocus::TextInput => {
let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap(); let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
field.process_event(event, context); field.process_event(event, context);
}, }
UIEventType::InsertInput(Key::Backspace) if self.focus == FormFocus::TextInput => { UIEventType::InsertInput(Key::Backspace) if self.focus == FormFocus::TextInput => {
let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap(); let field = self.fields.get_mut(&self.layout[self.cursor]).unwrap();
field.process_event(event, context); field.process_event(event, context);
}, }
_ => { _ => {
return false; return false;
} }
@ -429,9 +471,11 @@ impl Component for FormWidget {
} }
} }
#[derive(Debug, Default)] #[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>, buttons: FnvHashMap<String, T>,
layout: Vec<String>, layout: Vec<String>,
@ -439,13 +483,19 @@ pub struct ButtonWidget<T> where T: std::fmt::Debug + Default + Send{
cursor: usize, 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 { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Display::fmt("", f) 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> { pub fn new(init_val: (String, T)) -> ButtonWidget<T> {
ButtonWidget { ButtonWidget {
layout: vec![init_val.0.clone()], 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>
impl<T> Component for ButtonWidget<T> where T: std::fmt::Debug + Default + Send { where
T: std::fmt::Debug + Default + Send,
{
fn draw(&mut self, grid: &mut CellBuffer, area: Area, _context: &mut Context) { fn draw(&mut self, grid: &mut CellBuffer, area: Area, _context: &mut Context) {
let upper_left = upper_left!(area); let upper_left = upper_left!(area);
let mut len = 0; let mut len = 0;
for (i, k) in self.layout.iter().enumerate() { for (i, k) in self.layout.iter().enumerate() {
let cur_len = k.len(); let cur_len = k.len();
write_string_to_grid( write_string_to_grid(
k.as_str(), k.as_str(),
grid, grid,
Color::Default, Color::Default,
if i == self.cursor { Color::Byte(246) } else { Color::Default }, if i == self.cursor {
(pos_inc(upper_left, (len, 0)), pos_inc(upper_left, (cur_len + len, 0))), Color::Byte(246)
} else {
Color::Default
},
(
pos_inc(upper_left, (len, 0)),
pos_inc(upper_left, (cur_len + len, 0)),
),
false, false,
); );
len += cur_len + 3; len += cur_len + 3;
} }
} }
fn process_event(&mut self, event: &mut UIEvent, _context: &mut Context) -> bool { fn process_event(&mut self, event: &mut UIEvent, _context: &mut Context) -> bool {
match event.event_type { match event.event_type {
UIEventType::Input(Key::Char('\n')) => { 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; return true;
}, }
UIEventType::Input(Key::Left) => { UIEventType::Input(Key::Left) => {
self.cursor = self.cursor.saturating_sub(1); self.cursor = self.cursor.saturating_sub(1);
return true; return true;
}, }
UIEventType::Input(Key::Right) if self.cursor < self.layout.len().saturating_sub(1) => { UIEventType::Input(Key::Right) if self.cursor < self.layout.len().saturating_sub(1) => {
self.cursor += 1; self.cursor += 1;
return true; return true;
}, }
_ => {} _ => {}
} }
@ -509,8 +572,6 @@ impl<T> Component for ButtonWidget<T> where T: std::fmt::Debug + Default + Send
fn set_dirty(&mut self) {} fn set_dirty(&mut self) {}
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct AutoComplete { pub struct AutoComplete {
entries: Vec<String>, entries: Vec<String>,
@ -528,7 +589,9 @@ impl fmt::Display for AutoComplete {
impl Component for AutoComplete { impl Component for AutoComplete {
fn draw(&mut self, grid: &mut CellBuffer, area: Area, context: &mut Context) { 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); let upper_left = upper_left!(area);
self.dirty = false; self.dirty = false;
@ -540,7 +603,15 @@ impl Component for AutoComplete {
((0, 0), (width.saturating_sub(1), height.saturating_sub(1))), ((0, 0), (width.saturating_sub(1), height.saturating_sub(1))),
); );
/* Highlight cursor */ /* 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); context.dirty_areas.push_back(area);
} }
fn process_event(&mut self, event: &mut UIEvent, _context: &mut Context) -> bool { 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>) { pub fn set_suggestions(&mut self, entries: Vec<String>) {
if entries.len() == self.entries.len() && entries == self.entries { 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(); let width = content.cols();
for (i, e) in entries.iter().enumerate() { for (i, e) in entries.iter().enumerate() {
let (x, _) = write_string_to_grid( let (x, _) = write_string_to_grid(
@ -581,7 +656,7 @@ impl AutoComplete {
Color::Byte(7), Color::Byte(7),
((0, i), (width - 1, i)), ((0, i), (width - 1, i)),
false, false,
); );
write_string_to_grid( write_string_to_grid(
"", "",
&mut content, &mut content,
@ -589,15 +664,23 @@ impl AutoComplete {
Color::Byte(7), Color::Byte(7),
((width - 1, i), (width - 1, i)), ((width - 1, i), (width - 1, i)),
false, false,
); );
} }
self.content = content; self.content = content;
self.entries = entries; self.entries = entries;
self.cursor = 0; 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 inc_cursor(&mut self) {
pub fn dec_cursor(&mut self) { self.cursor = self.cursor.saturating_sub(1); self.set_dirty(); } 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> { pub fn get_suggestion(&mut self) -> Option<String> {
if self.entries.is_empty() { if self.entries.is_empty() {

View File

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

View File

@ -58,7 +58,6 @@ pub struct Account {
pub(crate) workers: Vec<Worker>, pub(crate) workers: Vec<Worker>,
pub(crate) settings: AccountConf, pub(crate) settings: AccountConf,
pub(crate) runtime_settings: AccountConf, pub(crate) runtime_settings: AccountConf,
pub(crate) backend: Box<MailBackend>, pub(crate) backend: Box<MailBackend>,
@ -68,9 +67,7 @@ pub struct Account {
impl Drop for Account { impl Drop for Account {
fn drop(&mut self) { fn drop(&mut self) {
//TODO: Avoid panics //TODO: Avoid panics
let data_dir = let data_dir = xdg::BaseDirectories::with_profile("meli", &self.name).unwrap();
xdg::BaseDirectories::with_profile("meli", &self.name)
.unwrap();
if let Ok(data) = data_dir.place_data_file("addressbook") { if let Ok(data) = data_dir.place_data_file("addressbook") {
/* place result in cache directory */ /* place result in cache directory */
let f = match fs::File::create(data) { let f = match fs::File::create(data) {
@ -99,9 +96,7 @@ impl Account {
folders.push(None); folders.push(None);
workers.push(Account::new_worker(f, &mut backend, notify_fn.clone())); workers.push(Account::new_worker(f, &mut backend, notify_fn.clone()));
} }
let data_dir = let data_dir = xdg::BaseDirectories::with_profile("meli", &name).unwrap();
xdg::BaseDirectories::with_profile("meli", &name)
.unwrap();
let address_book = if let Ok(data) = data_dir.place_data_file("addressbook") { let address_book = if let Ok(data) = data_dir.place_data_file("addressbook") {
if data.exists() { if data.exists() {
let reader = io::BufReader::new(fs::File::open(data).unwrap()); 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 { if self.sent_folder.is_some() && self.sent_folder.unwrap() == index {
self.folders[index] = Some(mailbox); self.folders[index] = Some(mailbox);
/* Add our replies to other folders */ /* Add our replies to other folders */
for id in (0..self.folders.len()).filter(|i| *i != index) { for id in (0..self.folders.len()).filter(|i| *i != index) {
self.add_replies_to_folder(id); self.add_replies_to_folder(id);
} }
} else { } else {
self.folders[index] = Some(mailbox); self.folders[index] = Some(mailbox);
self.add_replies_to_folder(index); self.add_replies_to_folder(index);
}; };
*/ */
} }
/* /*
@ -303,7 +298,8 @@ impl Account {
} }
pub fn save_draft(&self, draft: Draft) -> Result<()> { 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> { pub fn key_values(&self) -> FnvHashMap<&'static str, &Key> {
let mut map: FnvHashMap<&'static str, &Key> = Default::default(); let mut map: FnvHashMap<&'static str, &Key> = Default::default();
$(map.insert(stringify!($fname),&(self.$fname));)* $(map.insert(stringify!($fname),&(self.$fname));)*
map map
} }
} }
} }
} }
key_values!{ "compact-listing", derive (Debug, Clone, Deserialize) : key_values! { "compact-listing", derive (Debug, Clone, Deserialize) :
pub struct CompactListingShortcuts { pub struct CompactListingShortcuts {
open_thread: Key |> "Open thread.", open_thread: Key |> "Open thread.",
exit_thread: Key |> "Exit thread view.", exit_thread: Key |> "Exit thread view.",
@ -52,24 +52,23 @@ pub struct CompactListingShortcuts {
} }
} }
impl Default for CompactListingShortcuts { impl Default for CompactListingShortcuts {
fn default() -> Self { fn default() -> Self {
CompactListingShortcuts { CompactListingShortcuts {
open_thread: Key::Char('\n'), open_thread: Key::Char('\n'),
exit_thread: Key::Char('i'), exit_thread: Key::Char('i'),
prev_page: Key::PageUp, prev_page: Key::PageUp,
next_page: Key::PageDown, next_page: Key::PageDown,
prev_folder: Key::Char('J'), prev_folder: Key::Char('J'),
next_folder: Key::Char('K'), next_folder: Key::Char('K'),
prev_account:Key::Char('h'), prev_account: Key::Char('h'),
next_account:Key::Char('l'), next_account: Key::Char('l'),
new_mail: Key::Char('m'), new_mail: Key::Char('m'),
} }
} }
} }
key_values!{ "contact-list", derive (Debug, Clone, Deserialize) : key_values! { "contact-list", derive (Debug, Clone, Deserialize) :
pub struct ContactListShortcuts { pub struct ContactListShortcuts {
create_contact: Key |> "Create new contact.", create_contact: Key |> "Create new contact.",
edit_contact: Key |> "Edit contact under cursor." edit_contact: Key |> "Edit contact under cursor."
@ -78,14 +77,14 @@ pub struct ContactListShortcuts {
impl Default for ContactListShortcuts { impl Default for ContactListShortcuts {
fn default() -> Self { fn default() -> Self {
ContactListShortcuts { ContactListShortcuts {
create_contact: Key::Char('c'), create_contact: Key::Char('c'),
edit_contact: Key::Char('e'), edit_contact: Key::Char('e'),
} }
} }
} }
key_values!{ "pager", derive (Debug, Clone, Deserialize) : key_values! { "pager", derive (Debug, Clone, Deserialize) :
pub struct PagerShortcuts { pub struct PagerShortcuts {
scroll_up: Key |> "Scroll up pager.", scroll_up: Key |> "Scroll up pager.",
scroll_down: Key |> "Scroll down pager.", scroll_down: Key |> "Scroll down pager.",

View File

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

View File

@ -21,12 +21,12 @@
/*! The application's state. /*! 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 super::*;
use melib::backends::{FolderHash, NotifyFn}; use melib::backends::{FolderHash, NotifyFn};
@ -64,7 +64,8 @@ impl InputHandler {
}, },
&rx, &rx,
) )
}).unwrap(); })
.unwrap();
} }
fn kill(&self) { fn kill(&self) {
self.tx.send(false); self.tx.send(false);
@ -141,7 +142,8 @@ impl Drop for State {
cursor::Goto(1, 1), cursor::Goto(1, 1),
cursor::Show, cursor::Show,
BracketModeEnd, BracketModeEnd,
).unwrap(); )
.unwrap();
self.flush(); self.flush();
} }
} }
@ -187,7 +189,8 @@ impl State {
sender.send(ThreadEvent::UIEvent(UIEventType::StartupCheck)) sender.send(ThreadEvent::UIEvent(UIEventType::StartupCheck))
})), })),
) )
}).collect(); })
.collect();
accounts.sort_by(|a, b| a.name().cmp(&b.name())); accounts.sort_by(|a, b| a.name().cmp(&b.name()));
let mut s = State { let mut s = State {
cols, cols,
@ -235,7 +238,8 @@ impl State {
cursor::Hide, cursor::Hide,
clear::All, clear::All,
cursor::Goto(1, 1) cursor::Goto(1, 1)
).unwrap(); )
.unwrap();
s.flush(); s.flush();
eprintln!("DEBUG: inserting mailbox hashes:"); eprintln!("DEBUG: inserting mailbox hashes:");
for (x, account) in s.context.accounts.iter_mut().enumerate() { for (x, account) in s.context.accounts.iter_mut().enumerate() {
@ -305,7 +309,8 @@ impl State {
"{}{}", "{}{}",
termion::screen::ToMainScreen, termion::screen::ToMainScreen,
cursor::Show cursor::Show
).unwrap(); )
.unwrap();
self.flush(); self.flush();
self.stdout = None; self.stdout = None;
self.context.input.kill(); self.context.input.kill();
@ -322,7 +327,8 @@ impl State {
cursor::Hide, cursor::Hide,
clear::All, clear::All,
cursor::Goto(1, 1) cursor::Goto(1, 1)
).unwrap(); )
.unwrap();
self.flush(); self.flush();
} }
@ -381,7 +387,8 @@ impl State {
self.stdout(), self.stdout(),
"{}", "{}",
cursor::Goto(get_x(upper_left) as u16 + 1, (y + 1) as u16) 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) { for x in get_x(upper_left)..=get_x(bottom_right) {
let c = self.grid[(x, y)]; let c = self.grid[(x, y)];
@ -397,14 +404,16 @@ impl State {
self.stdout(), self.stdout(),
"{}", "{}",
termion::color::Bg(termion::color::Reset) termion::color::Bg(termion::color::Reset)
).unwrap(); )
.unwrap();
} }
if c.fg() != Color::Default { if c.fg() != Color::Default {
write!( write!(
self.stdout(), self.stdout(),
"{}", "{}",
termion::color::Fg(termion::color::Reset) termion::color::Fg(termion::color::Reset)
).unwrap(); )
.unwrap();
} }
} }
} }
@ -473,12 +482,12 @@ impl State {
self.flush(); self.flush();
} }
return; return;
}, }
UIEventType::ChangeMode(m) => { UIEventType::ChangeMode(m) => {
self.context self.context
.sender .sender
.send(ThreadEvent::UIEvent(UIEventType::ChangeMode(m))); .send(ThreadEvent::UIEvent(UIEventType::ChangeMode(m)));
}, }
_ => {} _ => {}
} }
/* inform each entity */ /* inform each entity */

View File

@ -19,8 +19,8 @@
* along with meli. If not, see <http://www.gnu.org/licenses/>. * along with meli. If not, see <http://www.gnu.org/licenses/>.
*/ */
extern crate serde; extern crate serde;
use self::serde::{de, Deserialize, Deserializer, };
use self::serde::de::Visitor; use self::serde::de::Visitor;
use self::serde::{de, Deserialize, Deserializer};
#[macro_use] #[macro_use]
mod position; 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 Define a (x, y) point in the terminal display as a holder of a character, foreground/background
colors and attributes. colors and attributes.
*/ */
use super::position::*; use super::position::*;
use std::convert::From; use std::convert::From;
use std::fmt; use std::fmt;
@ -183,7 +183,6 @@ impl CellBuffer {
self.cols = 0; self.cols = 0;
self.rows = 0; self.rows = 0;
} }
} }
impl HasSize for CellBuffer { impl HasSize for CellBuffer {
@ -580,7 +579,7 @@ pub fn copy_area_with_break(
); );
return upper_left!(dest); return upper_left!(dest);
} }
if grid_src.is_empty() || grid_dest.is_empty() { if grid_src.is_empty() || grid_dest.is_empty() {
return upper_left!(dest); 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 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]); ret.push(&s[..next_idx]);
s = &s[next_idx + 1..]; s = &s[next_idx + 1..];
} else { } else {
@ -766,7 +768,6 @@ pub fn word_break_string(mut s: &str, width: usize) -> Vec<&str> {
ret.push(s); ret.push(s);
break; break;
} }
} }
ret ret

View File

@ -203,44 +203,43 @@ pub const BRACKET_PASTE_END: &[u8] = b"\x1B[201~";
const FIELDS: &[&str] = &[]; const FIELDS: &[&str] = &[];
impl<'de> Deserialize<'de> for Key { impl<'de> Deserialize<'de> for Key {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error> fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where where
D: Deserializer<'de>, D: Deserializer<'de>,
{ {
struct KeyVisitor; struct KeyVisitor;
impl<'de> Visitor<'de> for KeyVisitor { impl<'de> Visitor<'de> for KeyVisitor {
type Value = Key; type Value = Key;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("`secs` or `nanos`") 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)),
}
}
} }
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. /// A `(x, y)` position on screen.
pub type Pos = (usize, usize); pub type Pos = (usize, usize);

View File

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