Run rustfmt
parent
e7c95ba229
commit
bf038428c2
|
@ -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 {
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
|
@ -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()?;
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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<()> {
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)));
|
||||||
|
|
16
src/bin.rs
16
src/bin.rs
|
@ -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 {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
};
|
};
|
||||||
|
|
|
@ -73,7 +73,8 @@ impl AccountMenu {
|
||||||
}
|
}
|
||||||
entries
|
entries
|
||||||
},
|
},
|
||||||
}).collect();
|
})
|
||||||
|
.collect();
|
||||||
AccountMenu {
|
AccountMenu {
|
||||||
accounts,
|
accounts,
|
||||||
dirty: true,
|
dirty: true,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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()),
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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("""),
|
'"' => ret.push_str("""),
|
||||||
_ => {
|
_ => {
|
||||||
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
|
||||||
|
|
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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.",
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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 */
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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))
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue