WIP
parent
3e9d137310
commit
6003bdd28c
|
@ -111,7 +111,7 @@ impl<'a> BackendOp for MaildirOp {
|
|||
'R' => flag |= Flag::REPLIED,
|
||||
'S' => flag |= Flag::SEEN,
|
||||
'T' => flag |= Flag::TRASHED,
|
||||
_ => panic!(),
|
||||
_ => eprintln!("DEBUG: in fetch_flags, path is {}", path),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
use super::*;
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
extern crate fnv;
|
||||
use self::fnv::FnvHashMap;
|
||||
|
||||
/// `Mailbox` represents a folder of mail.
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct Collection {
|
||||
pub envelopes: FnvHashMap<EnvelopeHash, Envelope>,
|
||||
date_index: BTreeMap<UnixTimestamp, EnvelopeHash>,
|
||||
subject_index: Option<BTreeMap<String, EnvelopeHash>>,
|
||||
pub threads: Threads,
|
||||
}
|
||||
|
||||
impl Collection {
|
||||
pub fn new(vec: Vec<Envelope>) -> Collection {
|
||||
let mut envelopes: FnvHashMap<EnvelopeHash, Envelope> =
|
||||
FnvHashMap::with_capacity_and_hasher(vec.len(), Default::default());
|
||||
for e in vec {
|
||||
envelopes.insert(e.hash(), e);
|
||||
}
|
||||
let date_index = BTreeMap::new();
|
||||
let subject_index = None;
|
||||
|
||||
let threads = Threads::new(&mut envelopes); // sent_folder);
|
||||
Collection {
|
||||
envelopes,
|
||||
date_index,
|
||||
subject_index,
|
||||
threads,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.envelopes.len()
|
||||
}
|
||||
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.envelopes.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Collection {
|
||||
type Target = FnvHashMap<EnvelopeHash, Envelope>;
|
||||
|
||||
fn deref(&self) -> &FnvHashMap<EnvelopeHash, Envelope> {
|
||||
&self.envelopes
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Collection {
|
||||
fn deref_mut(&mut self) -> &mut FnvHashMap<EnvelopeHash, Envelope> {
|
||||
&mut self.envelopes
|
||||
}
|
||||
}
|
|
@ -32,7 +32,10 @@ pub mod backends;
|
|||
use error::Result;
|
||||
use mailbox::backends::{folder_default, Folder};
|
||||
pub mod thread;
|
||||
pub use mailbox::thread::{build_threads, Container, SortField, SortOrder, Threads};
|
||||
pub use mailbox::thread::{SortField, SortOrder, ThreadNode, Threads};
|
||||
|
||||
mod collection;
|
||||
pub use self::collection::*;
|
||||
|
||||
use std::option::Option;
|
||||
|
||||
|
@ -40,8 +43,7 @@ use std::option::Option;
|
|||
#[derive(Debug)]
|
||||
pub struct Mailbox {
|
||||
pub folder: Folder,
|
||||
pub collection: Vec<Envelope>,
|
||||
pub threads: Threads,
|
||||
pub collection: Collection,
|
||||
}
|
||||
|
||||
impl Clone for Mailbox {
|
||||
|
@ -49,7 +51,6 @@ impl Clone for Mailbox {
|
|||
Mailbox {
|
||||
folder: self.folder.clone(),
|
||||
collection: self.collection.clone(),
|
||||
threads: self.threads.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,8 +58,7 @@ impl Default for Mailbox {
|
|||
fn default() -> Self {
|
||||
Mailbox {
|
||||
folder: folder_default(),
|
||||
collection: Vec::default(),
|
||||
threads: Threads::default(),
|
||||
collection: Collection::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -67,15 +67,14 @@ impl Mailbox {
|
|||
pub fn new(
|
||||
folder: &Folder,
|
||||
sent_folder: &Option<Result<Mailbox>>,
|
||||
collection: Result<Vec<Envelope>>,
|
||||
envelopes: Result<Vec<Envelope>>,
|
||||
) -> Result<Mailbox> {
|
||||
let mut collection: Vec<Envelope> = collection?;
|
||||
collection.sort_by(|a, b| a.date().cmp(&b.date()));
|
||||
let threads = build_threads(&mut collection, sent_folder);
|
||||
let mut envelopes: Vec<Envelope> = envelopes?;
|
||||
envelopes.sort_by(|a, b| a.date().cmp(&b.date()));
|
||||
let collection = Collection::new(envelopes);
|
||||
Ok(Mailbox {
|
||||
folder: (*folder).clone(),
|
||||
collection,
|
||||
threads,
|
||||
})
|
||||
}
|
||||
pub fn is_empty(&self) -> bool {
|
||||
|
@ -84,41 +83,36 @@ impl Mailbox {
|
|||
pub fn len(&self) -> usize {
|
||||
self.collection.len()
|
||||
}
|
||||
pub fn threaded_mail(&self, i: usize) -> usize {
|
||||
self.threads.thread_to_mail(i)
|
||||
pub fn threaded_mail(&self, i: usize) -> EnvelopeHash {
|
||||
self.collection.threads.thread_to_mail(i)
|
||||
}
|
||||
pub fn mail_and_thread(&mut self, i: usize) -> (&mut Envelope, Container) {
|
||||
let x = &mut self.collection.as_mut_slice()[i];
|
||||
let thread = self.threads[x.thread()];
|
||||
(x, thread)
|
||||
pub fn mail_and_thread(&mut self, i: EnvelopeHash) -> (&mut Envelope, &ThreadNode) {
|
||||
let thread;
|
||||
{
|
||||
let x = &mut self.collection.envelopes.entry(i).or_default();
|
||||
thread = &self.collection.threads[x.thread()];
|
||||
}
|
||||
(self.collection.envelopes.entry(i).or_default(), thread)
|
||||
}
|
||||
pub fn thread(&self, i: usize) -> &Container {
|
||||
&self.threads[i]
|
||||
pub fn thread(&self, i: usize) -> &ThreadNode {
|
||||
&self.collection.threads.thread_nodes()[i]
|
||||
}
|
||||
|
||||
pub fn update(&mut self, old_hash: EnvelopeHash, envelope: Envelope) {
|
||||
if let Some(i) = self.collection.iter().position(|e| e.hash() == old_hash) {
|
||||
self.collection[i] = envelope;
|
||||
} else {
|
||||
panic!()
|
||||
}
|
||||
self.collection.remove(&old_hash);
|
||||
self.collection.insert(envelope.hash(), envelope);
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, envelope: Envelope) -> &Envelope {
|
||||
self.collection.push(envelope);
|
||||
let hash = envelope.hash();
|
||||
self.collection.insert(hash, envelope);
|
||||
// TODO: Update threads.
|
||||
eprintln!("Inserted envelope");
|
||||
&self.collection[self.collection.len() - 1]
|
||||
&self.collection[&hash]
|
||||
}
|
||||
|
||||
pub fn remove(&mut self, envelope_hash: EnvelopeHash) {
|
||||
if let Some(i) = self
|
||||
.collection
|
||||
.iter()
|
||||
.position(|e| e.hash() == envelope_hash)
|
||||
{
|
||||
self.collection.remove(i);
|
||||
}
|
||||
self.collection.remove(&envelope_hash);
|
||||
// eprintln!("envelope_hash: {}\ncollection:\n{:?}", envelope_hash, self.collection);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,15 +23,11 @@
|
|||
* Threading algorithm
|
||||
*/
|
||||
|
||||
use error::Result;
|
||||
use mailbox::email::*;
|
||||
use mailbox::Mailbox;
|
||||
|
||||
extern crate fnv;
|
||||
use self::fnv::FnvHashMap;
|
||||
use std::borrow::Cow;
|
||||
use std::cell::{Ref, RefCell};
|
||||
use std::cmp::Ordering;
|
||||
use std::cell::RefCell;
|
||||
use std::ops::Index;
|
||||
use std::result::Result as StdResult;
|
||||
use std::str::FromStr;
|
||||
|
@ -82,244 +78,175 @@ impl FromStr for SortOrder {
|
|||
}
|
||||
}
|
||||
|
||||
/// A `Container` struct is needed to describe the thread tree forest during creation of threads.
|
||||
/// Because of Rust's memory model, we store indexes of Envelopes inside a collection instead of
|
||||
/// references and every reference is passed through the `Container` owner (a `Vec<Container>`).
|
||||
//
|
||||
/// `message` refers to a `Envelope` entry in a `Vec`. If it's empty, the `Container` is
|
||||
/// nonexistent in our `Mailbox` but we know it exists (for example we have a copy
|
||||
/// of a reply to a mail but we don't have its copy.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Container {
|
||||
id: usize,
|
||||
message: Option<usize>,
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct ThreadNode {
|
||||
message: Option<EnvelopeHash>,
|
||||
parent: Option<usize>,
|
||||
first_child: Option<usize>,
|
||||
next_sibling: Option<usize>,
|
||||
children: Vec<usize>,
|
||||
date: UnixTimestamp,
|
||||
indentation: usize,
|
||||
show_subject: bool,
|
||||
}
|
||||
|
||||
impl Default for Container {
|
||||
fn default() -> Container {
|
||||
Container {
|
||||
id: 0,
|
||||
message: None,
|
||||
parent: None,
|
||||
first_child: None,
|
||||
next_sibling: None,
|
||||
date: UnixTimestamp::default(),
|
||||
indentation: 0,
|
||||
show_subject: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct ContainerTree {
|
||||
id: usize,
|
||||
children: Option<Vec<ContainerTree>>,
|
||||
len: usize,
|
||||
has_unseen: bool,
|
||||
}
|
||||
|
||||
impl ContainerTree {
|
||||
fn new(id: usize) -> Self {
|
||||
ContainerTree {
|
||||
id,
|
||||
children: None,
|
||||
len: 1,
|
||||
impl Default for ThreadNode {
|
||||
fn default() -> ThreadNode {
|
||||
ThreadNode {
|
||||
message: None,
|
||||
parent: None,
|
||||
children: Vec::new(),
|
||||
date: UnixTimestamp::default(),
|
||||
indentation: 0,
|
||||
show_subject: true,
|
||||
|
||||
len: 0,
|
||||
has_unseen: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ThreadNode {
|
||||
pub fn show_subject(&self) -> bool {
|
||||
self.show_subject
|
||||
}
|
||||
|
||||
pub fn has_unseen(&self) -> bool {
|
||||
self.has_unseen
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
self.len
|
||||
}
|
||||
|
||||
fn is_descendant(&self, thread_nodes: &[ThreadNode], other: &ThreadNode) -> bool {
|
||||
if self == other {
|
||||
return true;
|
||||
}
|
||||
|
||||
for v in &self.children {
|
||||
if thread_nodes[*v].is_descendant(thread_nodes, other) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
pub fn message(&self) -> Option<EnvelopeHash> {
|
||||
self.message
|
||||
}
|
||||
|
||||
pub fn has_message(&self) -> bool {
|
||||
self.message.is_some()
|
||||
}
|
||||
|
||||
pub fn parent(&self) -> Option<usize> {
|
||||
self.parent
|
||||
}
|
||||
|
||||
pub fn has_parent(&self) -> bool {
|
||||
self.parent.is_some()
|
||||
}
|
||||
|
||||
pub fn children(&self) -> &[usize] {
|
||||
&self.children
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct Threads {
|
||||
containers: Vec<Container>,
|
||||
threaded_collection: Vec<usize>,
|
||||
thread_nodes: Vec<ThreadNode>,
|
||||
root_set: Vec<usize>,
|
||||
tree: RefCell<Vec<ContainerTree>>,
|
||||
|
||||
message_ids: FnvHashMap<String, EnvelopeHash>,
|
||||
sort: RefCell<(SortField, SortOrder)>,
|
||||
subsort: RefCell<(SortField, SortOrder)>,
|
||||
}
|
||||
|
||||
pub struct ThreadIterator<'a> {
|
||||
pos: usize,
|
||||
stack: Vec<usize>,
|
||||
tree: Ref<'a, Vec<ContainerTree>>,
|
||||
}
|
||||
impl<'a> Iterator for ThreadIterator<'a> {
|
||||
type Item = usize;
|
||||
fn next(&mut self) -> Option<usize> {
|
||||
{
|
||||
let mut tree = &(*self.tree);
|
||||
for i in &self.stack {
|
||||
tree = tree[*i].children.as_ref().unwrap();
|
||||
}
|
||||
if self.pos == tree.len() {
|
||||
if self.stack.is_empty() {
|
||||
return None;
|
||||
}
|
||||
self.pos = self.stack.pop().unwrap() + 1;
|
||||
} else {
|
||||
debug_assert!(self.pos < tree.len());
|
||||
let ret = tree[self.pos].id;
|
||||
if tree[self.pos].children.is_some() {
|
||||
self.stack.push(self.pos);
|
||||
self.pos = 0;
|
||||
return Some(ret);
|
||||
}
|
||||
self.pos += 1;
|
||||
return Some(ret);
|
||||
}
|
||||
impl PartialEq for ThreadNode {
|
||||
fn eq(&self, other: &ThreadNode) -> bool {
|
||||
match (self.message, other.message) {
|
||||
(Some(s), Some(o)) => s == o,
|
||||
_ => false,
|
||||
}
|
||||
self.next()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a Threads {
|
||||
type Item = usize;
|
||||
type IntoIter = ThreadIterator<'a>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
ThreadIterator {
|
||||
pos: 0,
|
||||
stack: Vec::new(),
|
||||
tree: self.tree.borrow(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RootIterator<'a> {
|
||||
pos: usize,
|
||||
tree: Ref<'a, Vec<ContainerTree>>,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for RootIterator<'a> {
|
||||
type Item = (usize, usize, bool);
|
||||
fn next(&mut self) -> Option<(usize, usize, bool)> {
|
||||
if self.pos == self.tree.len() {
|
||||
return None;
|
||||
}
|
||||
let node = &self.tree[self.pos];
|
||||
self.pos += 1;
|
||||
Some((node.id, node.len, node.has_unseen))
|
||||
}
|
||||
}
|
||||
|
||||
impl Threads {
|
||||
pub fn root_len(&self) -> usize {
|
||||
self.tree.borrow().len()
|
||||
}
|
||||
pub fn root_set(&self) -> &Vec<usize> {
|
||||
&self.root_set
|
||||
}
|
||||
pub fn root_set_iter(&self) -> RootIterator {
|
||||
RootIterator {
|
||||
pos: 0,
|
||||
tree: self.tree.borrow(),
|
||||
}
|
||||
}
|
||||
pub fn thread_to_mail(&self, i: usize) -> usize {
|
||||
let thread = self.containers[self.threaded_collection[i]];
|
||||
thread.message().unwrap()
|
||||
}
|
||||
pub fn threaded_collection(&self) -> &Vec<usize> {
|
||||
&self.threaded_collection
|
||||
}
|
||||
pub fn containers(&self) -> &Vec<Container> {
|
||||
&self.containers
|
||||
}
|
||||
pub fn new(collection: &mut FnvHashMap<EnvelopeHash, Envelope>) -> Threads {
|
||||
/* To reconstruct thread information from the mails we need: */
|
||||
|
||||
fn inner_subsort_by(&self, subsort: (SortField, SortOrder), collection: &[Envelope]) {
|
||||
let tree = &mut self.tree.borrow_mut();
|
||||
let containers = &self.containers;
|
||||
for mut t in tree.iter_mut() {
|
||||
if let Some(ref mut c) = t.children {
|
||||
c.sort_by(|a, b| match subsort {
|
||||
(SortField::Date, SortOrder::Desc) => {
|
||||
let a = containers[a.id];
|
||||
let b = containers[b.id];
|
||||
b.date.cmp(&a.date)
|
||||
}
|
||||
(SortField::Date, SortOrder::Asc) => {
|
||||
let a = containers[a.id];
|
||||
let b = containers[b.id];
|
||||
a.date.cmp(&b.date)
|
||||
}
|
||||
(SortField::Subject, SortOrder::Desc) => {
|
||||
let a = containers[a.id].message();
|
||||
let b = containers[b.id].message();
|
||||
/* a vector to hold thread members */
|
||||
let mut thread_nodes: Vec<ThreadNode> =
|
||||
Vec::with_capacity((collection.len() as f64 * 1.2) as usize);
|
||||
/* A hash table of Message IDs */
|
||||
let mut message_ids: FnvHashMap<String, usize> =
|
||||
FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default());
|
||||
/* Add each message to message_ids and threads, and link them together according to the
|
||||
* References / In-Reply-To headers */
|
||||
link_threads(&mut thread_nodes, &mut message_ids, collection);
|
||||
|
||||
if a.is_none() || b.is_none() {
|
||||
return Ordering::Equal;
|
||||
}
|
||||
let ma = &collection[a.unwrap()];
|
||||
let mb = &collection[b.unwrap()];
|
||||
ma.subject().cmp(&mb.subject())
|
||||
}
|
||||
(SortField::Subject, SortOrder::Asc) => {
|
||||
let a = containers[a.id].message();
|
||||
let b = containers[b.id].message();
|
||||
/* Walk over the elements of message_ids, and gather a list of the ThreadNode objects that have
|
||||
* no parents. These are the root messages of each thread */
|
||||
let mut root_set: Vec<usize> = Vec::with_capacity(collection.len());
|
||||
'root_set: for v in message_ids.values() {
|
||||
if thread_nodes[*v].parent.is_none() {
|
||||
if !thread_nodes[*v].has_message() && thread_nodes[*v].children.len() == 1 {
|
||||
/* Do not promote the children if doing so would promote them to the root set
|
||||
* -- unless there is only one child, in which case, do. */
|
||||
root_set.push(*v);
|
||||
|
||||
if a.is_none() || b.is_none() {
|
||||
return Ordering::Equal;
|
||||
}
|
||||
let ma = &collection[a.unwrap()];
|
||||
let mb = &collection[b.unwrap()];
|
||||
mb.subject().cmp(&ma.subject())
|
||||
}
|
||||
});
|
||||
continue 'root_set;
|
||||
}
|
||||
root_set.push(*v);
|
||||
}
|
||||
}
|
||||
root_set.sort_by(|a, b| thread_nodes[*b].date.cmp(&thread_nodes[*a].date));
|
||||
|
||||
let mut t = Threads {
|
||||
thread_nodes,
|
||||
root_set,
|
||||
..Default::default()
|
||||
};
|
||||
t.build_collection(&collection);
|
||||
t
|
||||
}
|
||||
|
||||
fn inner_sort_by(&self, sort: (SortField, SortOrder), collection: &[Envelope]) {
|
||||
let tree = &mut self.tree.borrow_mut();
|
||||
let containers = &self.containers;
|
||||
tree.sort_by(|a, b| match sort {
|
||||
(SortField::Date, SortOrder::Desc) => {
|
||||
let a = containers[a.id];
|
||||
let b = containers[b.id];
|
||||
b.date.cmp(&a.date)
|
||||
}
|
||||
(SortField::Date, SortOrder::Asc) => {
|
||||
let a = containers[a.id];
|
||||
let b = containers[b.id];
|
||||
a.date.cmp(&b.date)
|
||||
}
|
||||
(SortField::Subject, SortOrder::Desc) => {
|
||||
let a = containers[a.id].message();
|
||||
let b = containers[b.id].message();
|
||||
|
||||
if a.is_none() || b.is_none() {
|
||||
return Ordering::Equal;
|
||||
}
|
||||
let ma = &collection[a.unwrap()];
|
||||
let mb = &collection[b.unwrap()];
|
||||
ma.subject().cmp(&mb.subject())
|
||||
}
|
||||
(SortField::Subject, SortOrder::Asc) => {
|
||||
let a = containers[a.id].message();
|
||||
let b = containers[b.id].message();
|
||||
|
||||
if a.is_none() || b.is_none() {
|
||||
return Ordering::Equal;
|
||||
}
|
||||
let ma = &collection[a.unwrap()];
|
||||
let mb = &collection[b.unwrap()];
|
||||
mb.subject().cmp(&ma.subject())
|
||||
}
|
||||
});
|
||||
fn build_collection(&mut self, collection: &FnvHashMap<EnvelopeHash, Envelope>) {
|
||||
for i in &self.root_set {
|
||||
node_build(
|
||||
*i,
|
||||
&mut self.thread_nodes,
|
||||
0, /* indentation */
|
||||
*i, /* root_subject_idx */
|
||||
collection,
|
||||
);
|
||||
}
|
||||
self.inner_sort_by(*self.sort.borrow(), collection);
|
||||
self.inner_subsort_by(*self.subsort.borrow(), collection);
|
||||
}
|
||||
|
||||
fn inner_subsort_by(
|
||||
&self,
|
||||
subsort: (SortField, SortOrder),
|
||||
collection: &FnvHashMap<EnvelopeHash, Envelope>,
|
||||
) {
|
||||
}
|
||||
|
||||
fn inner_sort_by(
|
||||
&self,
|
||||
sort: (SortField, SortOrder),
|
||||
collection: &FnvHashMap<EnvelopeHash, Envelope>,
|
||||
) {
|
||||
}
|
||||
|
||||
pub fn sort_by(
|
||||
&self,
|
||||
sort: (SortField, SortOrder),
|
||||
subsort: (SortField, SortOrder),
|
||||
collection: &[Envelope],
|
||||
collection: &FnvHashMap<EnvelopeHash, Envelope>,
|
||||
) {
|
||||
/*
|
||||
if *self.sort.borrow() != sort {
|
||||
self.inner_sort_by(sort, collection);
|
||||
*self.sort.borrow_mut() = sort;
|
||||
|
@ -328,422 +255,193 @@ impl Threads {
|
|||
self.inner_subsort_by(subsort, collection);
|
||||
*self.subsort.borrow_mut() = subsort;
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
pub fn build_collection(&mut self, collection: &[Envelope]) {
|
||||
fn build_threaded(
|
||||
tree: &mut ContainerTree,
|
||||
containers: &mut Vec<Container>,
|
||||
indentation: usize,
|
||||
threaded: &mut Vec<usize>,
|
||||
i: usize,
|
||||
root_subject_idx: usize,
|
||||
collection: &[Envelope],
|
||||
) {
|
||||
let thread = containers[i];
|
||||
if let Some(msg_idx) = containers[root_subject_idx].message() {
|
||||
let root_subject = collection[msg_idx].subject();
|
||||
/* If the Container has no Message, but does have children, remove this container but
|
||||
* promote its children to this level (that is, splice them in to the current child
|
||||
* list.) */
|
||||
if indentation > 0 && thread.has_message() {
|
||||
let subject = collection[thread.message().unwrap()].subject();
|
||||
tree.has_unseen = !collection[thread.message().unwrap()].is_seen();
|
||||
if subject == root_subject
|
||||
|| subject.starts_with("Re: ")
|
||||
&& subject.as_ref().ends_with(root_subject.as_ref())
|
||||
{
|
||||
containers[i].set_show_subject(false);
|
||||
pub fn thread_to_mail(&self, i: usize) -> EnvelopeHash {
|
||||
let thread = &self.thread_nodes[self.root_set[i]];
|
||||
thread.message().unwrap()
|
||||
}
|
||||
|
||||
pub fn thread_nodes(&self) -> &Vec<ThreadNode> {
|
||||
&self.thread_nodes
|
||||
}
|
||||
|
||||
pub fn root_set(&self) -> &Vec<usize> {
|
||||
&self.root_set
|
||||
}
|
||||
|
||||
pub fn has_sibling(&self, i: usize) -> bool {
|
||||
if let Some(parent) = self[i].parent {
|
||||
self[parent].children.len() > 1
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn link_threads(
|
||||
thread_nodes: &mut Vec<ThreadNode>,
|
||||
message_ids: &mut FnvHashMap<String, usize>,
|
||||
collection: &mut FnvHashMap<EnvelopeHash, Envelope>,
|
||||
) {
|
||||
for (k, v) in collection.iter_mut() {
|
||||
let m_id = v.message_id_raw().to_string();
|
||||
|
||||
/* The index of this message's ThreadNode in thread_nodes */
|
||||
|
||||
let t_idx: usize = if message_ids.get(&m_id).is_some() {
|
||||
let node_idx = message_ids[&m_id];
|
||||
/* the already existing ThreadNote should be empty, since we're
|
||||
* seeing this message for the first time. otherwise it's a
|
||||
* duplicate. */
|
||||
if thread_nodes[node_idx].message.is_some() {
|
||||
continue;
|
||||
}
|
||||
thread_nodes[node_idx].date = v.date();
|
||||
thread_nodes[node_idx].message = Some(*k);
|
||||
v.set_thread(node_idx);
|
||||
|
||||
node_idx
|
||||
} else {
|
||||
/* Create a new ThreadNode object holding this message */
|
||||
thread_nodes.push(ThreadNode {
|
||||
message: Some(*k),
|
||||
date: v.date(),
|
||||
..Default::default()
|
||||
});
|
||||
v.set_thread(thread_nodes.len() - 1);
|
||||
message_ids.insert(m_id, thread_nodes.len() - 1);
|
||||
thread_nodes.len() - 1
|
||||
};
|
||||
|
||||
/* For each element in the message's References field:
|
||||
*
|
||||
* Find a ThreadNode object for the given Message-ID:
|
||||
* If there's one in message_ids use that;
|
||||
* Otherwise, make (and index) one with a null Message
|
||||
*
|
||||
* Link the References field's ThreadNode together in the order implied
|
||||
* by the References header.
|
||||
* If they are already linked, don't change the existing links.
|
||||
* Do not add a link if adding that link would introduce a loop: that
|
||||
* is, before asserting A->B, search down the children of B to see if A
|
||||
* is reachable, and also search down the children of A to see if B is
|
||||
* reachable. If either is already reachable as a child of the other,
|
||||
* don't add the link.
|
||||
*/
|
||||
|
||||
/* The index of the reference we are currently examining, start from current message */
|
||||
let mut ref_ptr = t_idx;
|
||||
|
||||
for &refn in v.references().iter().rev() {
|
||||
let r_id = String::from_utf8_lossy(refn.raw()).into();
|
||||
let parent_id = if message_ids.contains_key(&r_id) {
|
||||
let parent_id = message_ids[&r_id];
|
||||
if !(thread_nodes[parent_id].is_descendant(thread_nodes, &thread_nodes[ref_ptr])
|
||||
|| thread_nodes[ref_ptr].is_descendant(thread_nodes, &thread_nodes[parent_id]))
|
||||
{
|
||||
thread_nodes[ref_ptr].parent = Some(parent_id);
|
||||
if thread_nodes[parent_id].children.is_empty() {
|
||||
thread_nodes[parent_id].children.push(ref_ptr);
|
||||
}
|
||||
|
||||
let (left, right) = thread_nodes.split_at_mut(parent_id);
|
||||
let (parent, right) = right.split_first_mut().unwrap();
|
||||
for &c in &parent.children {
|
||||
if c > parent_id {
|
||||
right[c - parent_id - 1].parent = Some(parent_id);
|
||||
} else {
|
||||
left[c].parent = Some(parent_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if thread.has_parent() && !containers[thread.parent().unwrap()].has_message() {
|
||||
containers[i].parent = None;
|
||||
}
|
||||
let indentation = if thread.has_message() {
|
||||
containers[i].set_indentation(indentation);
|
||||
if !threaded.contains(&i) {
|
||||
threaded.push(i);
|
||||
}
|
||||
indentation + 1
|
||||
} else if indentation > 0 {
|
||||
indentation
|
||||
parent_id
|
||||
} else {
|
||||
indentation + 1
|
||||
/* Create a new ThreadNode object holding this reference */
|
||||
thread_nodes.push(ThreadNode {
|
||||
message: None,
|
||||
children: vec![ref_ptr; 1],
|
||||
date: v.date(),
|
||||
..Default::default()
|
||||
});
|
||||
if thread_nodes[ref_ptr].parent.is_none() {
|
||||
thread_nodes[ref_ptr].parent = Some(thread_nodes.len() - 1);
|
||||
}
|
||||
/* Can't avoid copy here since we have different lifetimes */
|
||||
message_ids.insert(r_id, thread_nodes.len() - 1);
|
||||
thread_nodes.len() - 1
|
||||
};
|
||||
|
||||
if thread.has_children() {
|
||||
let mut child_vec = Vec::new();
|
||||
/* Update thread's date */
|
||||
|
||||
let mut fc = thread.first_child().unwrap();
|
||||
|
||||
loop {
|
||||
let mut new_child_tree = ContainerTree::new(fc);
|
||||
build_threaded(
|
||||
&mut new_child_tree,
|
||||
containers,
|
||||
indentation,
|
||||
threaded,
|
||||
fc,
|
||||
i,
|
||||
collection,
|
||||
);
|
||||
tree.has_unseen |= new_child_tree.has_unseen;
|
||||
child_vec.push(new_child_tree);
|
||||
let thread_ = containers[fc];
|
||||
if !thread_.has_sibling() {
|
||||
break;
|
||||
}
|
||||
fc = thread_.next_sibling().unwrap();
|
||||
let mut parent_iter = parent_id;
|
||||
'date: loop {
|
||||
let p: &mut ThreadNode = &mut thread_nodes[parent_iter];
|
||||
if p.date < v.date() {
|
||||
p.date = v.date();
|
||||
}
|
||||
if let Some(p) = p.parent {
|
||||
parent_iter = p;
|
||||
} else {
|
||||
break 'date;
|
||||
}
|
||||
tree.len = child_vec.iter().map(|c| c.len).sum();
|
||||
tree.children = Some(child_vec);
|
||||
}
|
||||
ref_ptr = parent_id;
|
||||
}
|
||||
let mut tree = Vec::new();
|
||||
for i in &self.root_set {
|
||||
let mut tree_node = ContainerTree::new(*i);
|
||||
build_threaded(
|
||||
&mut tree_node,
|
||||
&mut self.containers,
|
||||
0,
|
||||
&mut self.threaded_collection,
|
||||
*i,
|
||||
*i,
|
||||
collection,
|
||||
);
|
||||
tree.push(tree_node);
|
||||
}
|
||||
self.tree.replace(tree);
|
||||
self.inner_sort_by(*self.sort.borrow(), collection);
|
||||
self.inner_subsort_by(*self.subsort.borrow(), collection);
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for Threads {
|
||||
type Output = Container;
|
||||
type Output = ThreadNode;
|
||||
|
||||
fn index(&self, index: usize) -> &Container {
|
||||
self.containers
|
||||
fn index(&self, index: usize) -> &ThreadNode {
|
||||
self.thread_nodes
|
||||
.get(index)
|
||||
.expect("thread index out of bounds")
|
||||
}
|
||||
}
|
||||
|
||||
impl Container {
|
||||
pub fn date(&self) -> UnixTimestamp {
|
||||
self.date
|
||||
}
|
||||
pub fn message(&self) -> Option<usize> {
|
||||
self.message
|
||||
}
|
||||
pub fn parent(&self) -> Option<usize> {
|
||||
self.parent
|
||||
}
|
||||
pub fn has_parent(&self) -> bool {
|
||||
self.parent.is_some()
|
||||
}
|
||||
pub fn first_child(&self) -> Option<usize> {
|
||||
self.first_child
|
||||
}
|
||||
pub fn next_sibling(&self) -> Option<usize> {
|
||||
self.next_sibling
|
||||
}
|
||||
pub fn has_children(&self) -> bool {
|
||||
self.first_child.is_some()
|
||||
}
|
||||
pub fn has_sibling(&self) -> bool {
|
||||
self.next_sibling.is_some()
|
||||
}
|
||||
pub fn has_message(&self) -> bool {
|
||||
self.message.is_some()
|
||||
}
|
||||
fn set_indentation(&mut self, i: usize) {
|
||||
self.indentation = i;
|
||||
}
|
||||
pub fn indentation(&self) -> usize {
|
||||
self.indentation
|
||||
}
|
||||
fn is_descendant(&self, threads: &[Container], other: &Container) -> bool {
|
||||
if self == other {
|
||||
return true;
|
||||
}
|
||||
|
||||
if let Some(v) = self.first_child {
|
||||
if threads[v].is_descendant(threads, other) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
if let Some(v) = self.next_sibling {
|
||||
if threads[v].is_descendant(threads, other) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
false
|
||||
}
|
||||
fn set_show_subject(&mut self, set: bool) -> () {
|
||||
self.show_subject = set;
|
||||
}
|
||||
pub fn show_subject(&self) -> bool {
|
||||
self.show_subject
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Container {
|
||||
fn eq(&self, other: &Container) -> bool {
|
||||
match (self.message, other.message) {
|
||||
(Some(s), Some(o)) => s == o,
|
||||
_ => self.id == other.id,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn build_collection(
|
||||
threads: &mut Vec<Container>,
|
||||
id_table: &mut FnvHashMap<Cow<str>, usize>,
|
||||
collection: &mut [Envelope],
|
||||
) -> () {
|
||||
for (i, x) in collection.iter_mut().enumerate() {
|
||||
let x_index; /* x's index in threads */
|
||||
let m_id = x.message_id_raw().into_owned();
|
||||
let m_id = Cow::from(m_id);
|
||||
if id_table.contains_key(&m_id) {
|
||||
let t = id_table[&m_id];
|
||||
/* the already existing Container should be empty, since we're
|
||||
* seeing this message for the first time */
|
||||
if threads[t].message.is_some() {
|
||||
/* skip duplicate message-id, but this should be handled instead */
|
||||
continue;
|
||||
}
|
||||
x_index = t;
|
||||
/* Store this message in the Container's message slot. */
|
||||
threads[t].date = x.date();
|
||||
x.set_thread(t);
|
||||
threads[t].message = Some(i);
|
||||
} else {
|
||||
/* Create a new Container object holding this message */
|
||||
x_index = threads.len();
|
||||
threads.push(Container {
|
||||
message: Some(i),
|
||||
id: x_index,
|
||||
date: x.date(),
|
||||
..Default::default()
|
||||
});
|
||||
x.set_thread(x_index);
|
||||
id_table.insert(m_id, x_index);
|
||||
}
|
||||
/* For each element in the message's References field:
|
||||
*
|
||||
* Find a Container object for the given Message-ID:
|
||||
* If there's one in id_table use that;
|
||||
* Otherwise, make (and index) one with a null Message
|
||||
*
|
||||
* Link the References field's Container together in the order implied by the References header.
|
||||
* If they are already linked, don't change the existing links.
|
||||
* Do not add a link if adding that link would introduce a loop: that is, before asserting A->B, search down the children of B to see if A is reachable, and also search down the children of A to see if B is reachable. If either is already reachable as a child of the other, don't add the link.
|
||||
*/
|
||||
let mut curr_ref = x_index;
|
||||
let mut iasf = 0;
|
||||
for &r in x.references().iter().rev() {
|
||||
if iasf == 1 {
|
||||
continue;
|
||||
}
|
||||
iasf += 1;
|
||||
let r = String::from_utf8_lossy(r.raw());
|
||||
let parent_id = if id_table.contains_key(&r) {
|
||||
let p = id_table[r.as_ref()];
|
||||
if !(threads[p].is_descendant(threads, &threads[curr_ref])
|
||||
|| threads[curr_ref].is_descendant(threads, &threads[p]))
|
||||
{
|
||||
threads[curr_ref].parent = Some(p);
|
||||
if threads[p].first_child.is_none() {
|
||||
threads[p].first_child = Some(curr_ref);
|
||||
} else {
|
||||
let mut child_iter = threads[p].first_child.unwrap();
|
||||
while threads[child_iter].next_sibling.is_some() {
|
||||
threads[child_iter].parent = Some(p);
|
||||
child_iter = threads[child_iter].next_sibling.unwrap();
|
||||
}
|
||||
threads[child_iter].next_sibling = Some(curr_ref);
|
||||
threads[child_iter].parent = Some(p);
|
||||
}
|
||||
}
|
||||
p
|
||||
} else {
|
||||
let idx = threads.len();
|
||||
threads.push(Container {
|
||||
message: None,
|
||||
id: idx,
|
||||
first_child: Some(curr_ref),
|
||||
date: x.date(),
|
||||
..Default::default()
|
||||
});
|
||||
if threads[curr_ref].parent.is_none() {
|
||||
threads[curr_ref].parent = Some(idx);
|
||||
}
|
||||
/* Can't avoid copy here since we have different lifetimes */
|
||||
id_table.insert(Cow::from(r.into_owned()), idx);
|
||||
idx
|
||||
};
|
||||
/* update thread date */
|
||||
let mut parent_iter = parent_id;
|
||||
'date: loop {
|
||||
let p = &mut threads[parent_iter];
|
||||
if p.date < x.date() {
|
||||
p.date = x.date();
|
||||
}
|
||||
match p.parent {
|
||||
Some(p) => {
|
||||
parent_iter = p;
|
||||
}
|
||||
None => {
|
||||
break 'date;
|
||||
}
|
||||
}
|
||||
}
|
||||
curr_ref = parent_id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Builds threads from a collection.
|
||||
pub fn build_threads(
|
||||
collection: &mut Vec<Envelope>,
|
||||
sent_folder: &Option<Result<Mailbox>>,
|
||||
) -> Threads {
|
||||
/* To reconstruct thread information from the mails we need: */
|
||||
|
||||
/* a vector to hold thread members */
|
||||
let mut threads: Vec<Container> = Vec::with_capacity((collection.len() as f64 * 1.2) as usize);
|
||||
/* A hash table of Message IDs */
|
||||
let mut id_table: FnvHashMap<Cow<str>, usize> =
|
||||
FnvHashMap::with_capacity_and_hasher(collection.len(), Default::default());
|
||||
|
||||
/* Add each message to id_table and threads, and link them together according to the
|
||||
* References / In-Reply-To headers */
|
||||
build_collection(&mut threads, &mut id_table, collection);
|
||||
let mut idx = collection.len();
|
||||
let mut tidx = threads.len();
|
||||
/* Link messages from Sent folder if they are relevant to this folder.
|
||||
* This means that
|
||||
* - if a message from Sent is a reply to a message in this folder, we will
|
||||
* add it to the threading (but not the collection; non-threading users shouldn't care
|
||||
* about this)
|
||||
* - if a message in this folder is a reply to a message we sent, we will add it to the
|
||||
* threading
|
||||
*/
|
||||
|
||||
if let Some(ref sent_box) = *sent_folder {
|
||||
if sent_box.is_ok() {
|
||||
let sent_mailbox = sent_box.as_ref();
|
||||
let sent_mailbox = sent_mailbox.unwrap();
|
||||
|
||||
for x in &sent_mailbox.collection {
|
||||
let m_id = x.message_id_raw();
|
||||
let x_r_id = x.in_reply_to_raw();
|
||||
if id_table.contains_key(&m_id)
|
||||
|| (!x.in_reply_to_raw().is_empty()
|
||||
&& id_table.contains_key(&x.in_reply_to_raw()))
|
||||
{
|
||||
let mut x: Envelope = (*x).clone();
|
||||
if id_table.contains_key(&m_id) {
|
||||
let c = id_table[&m_id];
|
||||
if threads[c].message.is_some() {
|
||||
/* skip duplicate message-id, but this should be handled instead */
|
||||
continue;
|
||||
}
|
||||
threads[c].message = Some(idx);
|
||||
assert!(threads[c].has_children());
|
||||
threads[c].date = x.date();
|
||||
x.set_thread(c);
|
||||
} else if !x.in_reply_to_raw().is_empty()
|
||||
&& id_table.contains_key(&x.in_reply_to_raw())
|
||||
{
|
||||
let p = id_table[&x_r_id];
|
||||
let c = if id_table.contains_key(&m_id) {
|
||||
id_table[&m_id]
|
||||
} else {
|
||||
threads.push(Container {
|
||||
message: Some(idx),
|
||||
id: tidx,
|
||||
parent: Some(p),
|
||||
date: x.date(),
|
||||
..Default::default()
|
||||
});
|
||||
id_table.insert(Cow::from(m_id.into_owned()), tidx);
|
||||
x.set_thread(tidx);
|
||||
tidx += 1;
|
||||
tidx - 1
|
||||
};
|
||||
threads[c].parent = Some(p);
|
||||
if threads[p].is_descendant(&threads, &threads[c])
|
||||
|| threads[c].is_descendant(&threads, &threads[p])
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if threads[p].first_child.is_none() {
|
||||
threads[p].first_child = Some(c);
|
||||
} else {
|
||||
let mut fc = threads[p].first_child.unwrap();
|
||||
while threads[fc].next_sibling.is_some() {
|
||||
threads[fc].parent = Some(p);
|
||||
fc = threads[fc].next_sibling.unwrap();
|
||||
}
|
||||
threads[fc].next_sibling = Some(c);
|
||||
threads[fc].parent = Some(p);
|
||||
}
|
||||
/* update thread date */
|
||||
let mut parent_iter = p;
|
||||
'date: loop {
|
||||
let p = &mut threads[parent_iter];
|
||||
if p.date < x.date() {
|
||||
p.date = x.date();
|
||||
}
|
||||
match p.parent {
|
||||
Some(p) => {
|
||||
parent_iter = p;
|
||||
}
|
||||
None => {
|
||||
break 'date;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
collection.push(x);
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Walk over the elements of id_table, and gather a list of the Container objects that have
|
||||
* no parents. These are the root messages of each thread */
|
||||
let mut root_set = Vec::with_capacity(collection.len());
|
||||
'root_set: for v in id_table.values() {
|
||||
if threads[*v].parent.is_none() {
|
||||
if !threads[*v].has_message()
|
||||
&& threads[*v].has_children()
|
||||
&& !threads[threads[*v].first_child.unwrap()].has_sibling()
|
||||
fn node_build(
|
||||
idx: usize,
|
||||
thread_nodes: &mut Vec<ThreadNode>,
|
||||
indentation: usize,
|
||||
root_subject_idx: usize,
|
||||
collection: &FnvHashMap<EnvelopeHash, Envelope>,
|
||||
) {
|
||||
if let Some(msg_idx) = thread_nodes[root_subject_idx].message().as_ref() {
|
||||
let root_subject = collection[msg_idx].subject();
|
||||
/* If the ThreadNode has no Message, but does have children, remove this container but
|
||||
* promote its children to this level (that is, splice them in to the current child
|
||||
* list.) */
|
||||
if indentation > 0 && thread_nodes[idx].has_message() {
|
||||
let subject = collection[thread_nodes[idx].message().as_ref().unwrap()].subject();
|
||||
thread_nodes[idx].has_unseen =
|
||||
!collection[thread_nodes[idx].message().as_ref().unwrap()].is_seen();
|
||||
if subject == root_subject
|
||||
|| subject.starts_with("Re: ") && subject.as_ref().ends_with(root_subject.as_ref())
|
||||
{
|
||||
/* Do not promote the children if doing so would promote them to the root set
|
||||
* -- unless there is only one child, in which case, do. */
|
||||
root_set.push(threads[*v].first_child.unwrap());
|
||||
|
||||
continue 'root_set;
|
||||
thread_nodes[idx].show_subject = false;
|
||||
}
|
||||
root_set.push(*v);
|
||||
}
|
||||
}
|
||||
root_set.sort_by(|a, b| threads[*b].date.cmp(&threads[*a].date));
|
||||
|
||||
/* Group messages together by thread in a collection so we can print them together */
|
||||
let threaded_collection: Vec<usize> = Vec::with_capacity(collection.len());
|
||||
|
||||
let mut t = Threads {
|
||||
containers: threads,
|
||||
threaded_collection,
|
||||
root_set,
|
||||
..Default::default()
|
||||
if thread_nodes[idx].has_parent()
|
||||
&& !thread_nodes[thread_nodes[idx].parent().unwrap()].has_message()
|
||||
{
|
||||
thread_nodes[idx].parent = None;
|
||||
}
|
||||
let indentation = if thread_nodes[idx].has_message() {
|
||||
thread_nodes[idx].indentation = indentation;
|
||||
indentation + 1
|
||||
} else if indentation > 0 {
|
||||
indentation
|
||||
} else {
|
||||
indentation + 1
|
||||
};
|
||||
t.build_collection(&collection);
|
||||
t
|
||||
|
||||
let mut has_unseen = thread_nodes[idx].has_unseen;
|
||||
for c in thread_nodes[idx].children.clone().iter() {
|
||||
node_build(*c, thread_nodes, indentation, *c, collection);
|
||||
has_unseen |= thread_nodes[*c].has_unseen;
|
||||
}
|
||||
thread_nodes[idx].has_unseen = has_unseen;
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ use melib::Draft;
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct Composer {
|
||||
reply_context: Option<((usize, usize), Box<ThreadView>)>, // (folder_index, container_index)
|
||||
reply_context: Option<((usize, usize), Box<ThreadView>)>, // (folder_index, thread_node_index)
|
||||
account_cursor: usize,
|
||||
|
||||
pager: Pager,
|
||||
|
@ -98,19 +98,19 @@ impl fmt::Display for Composer {
|
|||
|
||||
impl Composer {
|
||||
/*
|
||||
* coordinates: (account index, mailbox index, root set container index)
|
||||
* msg: index of message we reply to in containers
|
||||
* coordinates: (account index, mailbox index, root set thread_node index)
|
||||
* msg: index of message we reply to in thread_nodes
|
||||
* context: current context
|
||||
*/
|
||||
pub fn with_context(coordinates: (usize, usize, usize), msg: usize, context: &Context) -> Self {
|
||||
let mailbox = &context.accounts[coordinates.0][coordinates.1]
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let threads = &mailbox.threads;
|
||||
let containers = &threads.containers();
|
||||
let threads = &mailbox.collection.threads;
|
||||
let thread_nodes = &threads.thread_nodes();
|
||||
let mut ret = Composer::default();
|
||||
let p = containers[msg];
|
||||
let parent_message = &mailbox.collection[p.message().unwrap()];
|
||||
let p = &thread_nodes[msg];
|
||||
let parent_message = &mailbox.collection[&p.message().unwrap()];
|
||||
let mut op = context.accounts[coordinates.0]
|
||||
.backend
|
||||
.operation(parent_message.hash());
|
||||
|
@ -122,10 +122,10 @@ impl Composer {
|
|||
if p.show_subject() {
|
||||
format!(
|
||||
"Re: {}",
|
||||
mailbox.collection[p.message().unwrap()].subject().clone()
|
||||
mailbox.collection[&p.message().unwrap()].subject().clone()
|
||||
)
|
||||
} else {
|
||||
mailbox.collection[p.message().unwrap()].subject().into()
|
||||
mailbox.collection[&p.message().unwrap()].subject().into()
|
||||
},
|
||||
);
|
||||
|
||||
|
|
|
@ -121,7 +121,7 @@ impl CompactListing {
|
|||
.as_ref()
|
||||
.unwrap();
|
||||
|
||||
self.length = mailbox.threads.root_len();
|
||||
self.length = mailbox.collection.threads.root_set().len();
|
||||
self.content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' '));
|
||||
if self.length == 0 {
|
||||
write_string_to_grid(
|
||||
|
@ -134,24 +134,24 @@ impl CompactListing {
|
|||
);
|
||||
return;
|
||||
}
|
||||
let threads = &mailbox.threads;
|
||||
let threads = &mailbox.collection.threads;
|
||||
threads.sort_by(self.sort, self.subsort, &mailbox.collection);
|
||||
for (idx, (t, len, has_unseen)) in threads.root_set_iter().enumerate() {
|
||||
let container = &threads.containers()[t];
|
||||
let i = if let Some(i) = container.message() {
|
||||
for (idx, root_idx) in threads.root_set().iter().enumerate() {
|
||||
let thread_node = &threads.thread_nodes()[*root_idx];
|
||||
let i = if let Some(i) = thread_node.message() {
|
||||
i
|
||||
} else {
|
||||
threads.containers()[container.first_child().unwrap()]
|
||||
threads.thread_nodes()[thread_node.children()[0]]
|
||||
.message()
|
||||
.unwrap()
|
||||
};
|
||||
let root_envelope: &Envelope = &mailbox.collection[i];
|
||||
let fg_color = if has_unseen {
|
||||
let root_envelope: &Envelope = &mailbox.collection[&i];
|
||||
let fg_color = if thread_node.has_unseen() {
|
||||
Color::Byte(0)
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let bg_color = if has_unseen {
|
||||
let bg_color = if thread_node.has_unseen() {
|
||||
Color::Byte(251)
|
||||
} else if idx % 2 == 0 {
|
||||
Color::Byte(236)
|
||||
|
@ -159,7 +159,7 @@ impl CompactListing {
|
|||
Color::Default
|
||||
};
|
||||
let (x, _) = write_string_to_grid(
|
||||
&CompactListing::make_entry_string(root_envelope, len, idx),
|
||||
&CompactListing::make_entry_string(root_envelope, thread_node.len(), idx),
|
||||
&mut self.content,
|
||||
fg_color,
|
||||
bg_color,
|
||||
|
@ -179,17 +179,17 @@ impl CompactListing {
|
|||
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let threads = &mailbox.threads;
|
||||
let container = threads.root_set()[idx];
|
||||
let container = &threads.containers()[container];
|
||||
let i = if let Some(i) = container.message() {
|
||||
let threads = &mailbox.collection.threads;
|
||||
let thread_node = threads.root_set()[idx];
|
||||
let thread_node = &threads.thread_nodes()[thread_node];
|
||||
let i = if let Some(i) = thread_node.message() {
|
||||
i
|
||||
} else {
|
||||
threads.containers()[container.first_child().unwrap()]
|
||||
threads.thread_nodes()[thread_node.children()[0]]
|
||||
.message()
|
||||
.unwrap()
|
||||
};
|
||||
let root_envelope: &Envelope = &mailbox.collection[i];
|
||||
let root_envelope: &Envelope = &mailbox.collection[&i];
|
||||
let fg_color = if !root_envelope.is_seen() {
|
||||
Color::Byte(0)
|
||||
} else {
|
||||
|
|
|
@ -34,7 +34,7 @@ pub struct PlainListing {
|
|||
cursor_pos: (usize, usize, usize),
|
||||
new_cursor_pos: (usize, usize, usize),
|
||||
length: usize,
|
||||
local_collection: Vec<usize>,
|
||||
local_collection: Vec<EnvelopeHash>,
|
||||
sort: (SortField, SortOrder),
|
||||
subsort: (SortField, SortOrder),
|
||||
/// Cache current view.
|
||||
|
@ -118,11 +118,7 @@ impl PlainListing {
|
|||
.as_ref()
|
||||
.unwrap();
|
||||
|
||||
self.length = if threaded {
|
||||
mailbox.threads.threaded_collection().len()
|
||||
} else {
|
||||
mailbox.len()
|
||||
};
|
||||
self.length = mailbox.len();
|
||||
self.content = CellBuffer::new(MAX_COLS, self.length + 1, Cell::with_char(' '));
|
||||
if self.length == 0 {
|
||||
write_string_to_grid(
|
||||
|
@ -136,160 +132,68 @@ impl PlainListing {
|
|||
return;
|
||||
}
|
||||
|
||||
// TODO: Fix the threaded hell and refactor stuff into seperate functions and/or modules.
|
||||
if threaded {
|
||||
let mut indentations: Vec<bool> = Vec::with_capacity(6);
|
||||
let mut thread_idx = 0; // needed for alternate thread colors
|
||||
/* Draw threaded view. */
|
||||
let threads = &mailbox.threads;
|
||||
threads.sort_by(self.sort, self.subsort, &mailbox.collection);
|
||||
let containers: &Vec<Container> = &threads.containers();
|
||||
let mut iter = threads.into_iter().peekable();
|
||||
let len = threads
|
||||
.threaded_collection()
|
||||
.len()
|
||||
.to_string()
|
||||
.chars()
|
||||
.count();
|
||||
/* This is just a desugared for loop so that we can use .peek() */
|
||||
let mut idx = 0;
|
||||
while let Some(i) = iter.next() {
|
||||
let container = &containers[i];
|
||||
let indentation = container.indentation();
|
||||
|
||||
if indentation == 0 {
|
||||
thread_idx += 1;
|
||||
}
|
||||
|
||||
if !container.has_message() {
|
||||
continue;
|
||||
}
|
||||
|
||||
match iter.peek() {
|
||||
Some(&x) if threads[x].indentation() == indentation => {
|
||||
indentations.pop();
|
||||
indentations.push(true);
|
||||
}
|
||||
_ => {
|
||||
indentations.pop();
|
||||
indentations.push(false);
|
||||
}
|
||||
}
|
||||
if container.has_sibling() {
|
||||
indentations.pop();
|
||||
indentations.push(true);
|
||||
}
|
||||
let envelope: &Envelope = &mailbox.collection[container.message().unwrap()];
|
||||
let fg_color = if !envelope.is_seen() {
|
||||
Color::Byte(0)
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let bg_color = if !envelope.is_seen() {
|
||||
Color::Byte(251)
|
||||
} else if thread_idx % 2 == 0 {
|
||||
Color::Byte(236)
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let (x, _) = write_string_to_grid(
|
||||
&PlainListing::make_thread_entry(
|
||||
envelope,
|
||||
idx,
|
||||
indentation,
|
||||
container,
|
||||
&indentations,
|
||||
len,
|
||||
// context.accounts[self.cursor_pos.0].backend.operation(envelope.hash())
|
||||
),
|
||||
&mut self.content,
|
||||
fg_color,
|
||||
bg_color,
|
||||
((0, idx), (MAX_COLS - 1, idx)),
|
||||
false,
|
||||
);
|
||||
for x in x..MAX_COLS {
|
||||
self.content[(x, idx)].set_ch(' ');
|
||||
self.content[(x, idx)].set_bg(bg_color);
|
||||
}
|
||||
|
||||
match iter.peek() {
|
||||
Some(&x) if containers[x].indentation() > indentation => {
|
||||
indentations.push(false);
|
||||
}
|
||||
Some(&x) if containers[x].indentation() < indentation => {
|
||||
for _ in 0..(indentation - containers[x].indentation()) {
|
||||
indentations.pop();
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
idx += 1;
|
||||
// Populate `CellBuffer` with every entry.
|
||||
let mut idx = 0;
|
||||
for y in 0..=self.length {
|
||||
if idx >= self.length {
|
||||
/* No more entries left, so fill the rest of the area with empty space */
|
||||
clear_area(&mut self.content, ((0, y), (MAX_COLS - 1, self.length)));
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// Populate `CellBuffer` with every entry.
|
||||
let mut idx = 0;
|
||||
for y in 0..=self.length {
|
||||
if idx >= self.length {
|
||||
/* No more entries left, so fill the rest of the area with empty space */
|
||||
clear_area(&mut self.content, ((0, y), (MAX_COLS - 1, self.length)));
|
||||
break;
|
||||
/* Write an entire line for each envelope entry. */
|
||||
self.local_collection = mailbox.collection.keys().map(|v| *v).collect();
|
||||
let sort = self.sort;
|
||||
self.local_collection.sort_by(|a, b| match sort {
|
||||
(SortField::Date, SortOrder::Desc) => {
|
||||
let ma = &mailbox.collection[a];
|
||||
let mb = &mailbox.collection[b];
|
||||
mb.date().cmp(&ma.date())
|
||||
}
|
||||
/* Write an entire line for each envelope entry. */
|
||||
self.local_collection = (0..mailbox.collection.len()).collect();
|
||||
let sort = self.sort;
|
||||
self.local_collection.sort_by(|a, b| match sort {
|
||||
(SortField::Date, SortOrder::Desc) => {
|
||||
let ma = &mailbox.collection[*a];
|
||||
let mb = &mailbox.collection[*b];
|
||||
mb.date().cmp(&ma.date())
|
||||
}
|
||||
(SortField::Date, SortOrder::Asc) => {
|
||||
let ma = &mailbox.collection[*a];
|
||||
let mb = &mailbox.collection[*b];
|
||||
ma.date().cmp(&mb.date())
|
||||
}
|
||||
(SortField::Subject, SortOrder::Desc) => {
|
||||
let ma = &mailbox.collection[*a];
|
||||
let mb = &mailbox.collection[*b];
|
||||
ma.subject().cmp(&mb.subject())
|
||||
}
|
||||
(SortField::Subject, SortOrder::Asc) => {
|
||||
let ma = &mailbox.collection[*a];
|
||||
let mb = &mailbox.collection[*b];
|
||||
mb.subject().cmp(&ma.subject())
|
||||
}
|
||||
});
|
||||
let envelope: &Envelope = &mailbox.collection[self.local_collection[idx]];
|
||||
|
||||
let fg_color = if !envelope.is_seen() {
|
||||
Color::Byte(0)
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let bg_color = if !envelope.is_seen() {
|
||||
Color::Byte(251)
|
||||
} else if idx % 2 == 0 {
|
||||
Color::Byte(236)
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let (x, y) = write_string_to_grid(
|
||||
&PlainListing::make_entry_string(envelope, idx),
|
||||
&mut self.content,
|
||||
fg_color,
|
||||
bg_color,
|
||||
((0, y), (MAX_COLS - 1, y)),
|
||||
false,
|
||||
);
|
||||
|
||||
for x in x..MAX_COLS {
|
||||
self.content[(x, y)].set_ch(' ');
|
||||
self.content[(x, y)].set_bg(bg_color);
|
||||
(SortField::Date, SortOrder::Asc) => {
|
||||
let ma = &mailbox.collection[a];
|
||||
let mb = &mailbox.collection[b];
|
||||
ma.date().cmp(&mb.date())
|
||||
}
|
||||
(SortField::Subject, SortOrder::Desc) => {
|
||||
let ma = &mailbox.collection[a];
|
||||
let mb = &mailbox.collection[b];
|
||||
ma.subject().cmp(&mb.subject())
|
||||
}
|
||||
(SortField::Subject, SortOrder::Asc) => {
|
||||
let ma = &mailbox.collection[a];
|
||||
let mb = &mailbox.collection[b];
|
||||
mb.subject().cmp(&ma.subject())
|
||||
}
|
||||
});
|
||||
let envelope: &Envelope = &mailbox.collection[&self.local_collection[idx]];
|
||||
|
||||
idx += 1;
|
||||
let fg_color = if !envelope.is_seen() {
|
||||
Color::Byte(0)
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let bg_color = if !envelope.is_seen() {
|
||||
Color::Byte(251)
|
||||
} else if idx % 2 == 0 {
|
||||
Color::Byte(236)
|
||||
} else {
|
||||
Color::Default
|
||||
};
|
||||
let (x, y) = write_string_to_grid(
|
||||
&PlainListing::make_entry_string(envelope, idx),
|
||||
&mut self.content,
|
||||
fg_color,
|
||||
bg_color,
|
||||
((0, y), (MAX_COLS - 1, y)),
|
||||
false,
|
||||
);
|
||||
|
||||
for x in x..MAX_COLS {
|
||||
self.content[(x, y)].set_ch(' ');
|
||||
self.content[(x, y)].set_bg(bg_color);
|
||||
}
|
||||
|
||||
idx += 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -301,12 +205,7 @@ impl PlainListing {
|
|||
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let envelope: &Envelope = if threaded {
|
||||
let i = mailbox.threaded_mail(idx);
|
||||
&mailbox.collection[i]
|
||||
} else {
|
||||
&mailbox.collection[idx]
|
||||
};
|
||||
let envelope: &Envelope = &mailbox.collection[&self.local_collection[idx]];
|
||||
|
||||
let fg_color = if !envelope.is_seen() {
|
||||
Color::Byte(0)
|
||||
|
@ -336,12 +235,7 @@ impl PlainListing {
|
|||
let mailbox = &context.accounts[self.cursor_pos.0][self.cursor_pos.1]
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let envelope: &Envelope = if threaded {
|
||||
let i = mailbox.threaded_mail(idx);
|
||||
&mailbox.collection[i]
|
||||
} else {
|
||||
&mailbox.collection[idx]
|
||||
};
|
||||
let envelope: &Envelope = &mailbox.collection[&self.local_collection[idx]];
|
||||
|
||||
let fg_color = if !envelope.is_seen() {
|
||||
Color::Byte(0)
|
||||
|
@ -423,14 +317,15 @@ impl PlainListing {
|
|||
envelope: &Envelope,
|
||||
idx: usize,
|
||||
indent: usize,
|
||||
container: &Container,
|
||||
node_idx: usize,
|
||||
nodes: &Threads,
|
||||
indentations: &[bool],
|
||||
idx_width: usize,
|
||||
//op: Box<BackendOp>,
|
||||
) -> String {
|
||||
let has_sibling = container.has_sibling();
|
||||
let has_parent = container.has_parent();
|
||||
let show_subject = container.show_subject();
|
||||
let has_sibling = nodes.has_sibling(node_idx);
|
||||
let has_parent = nodes[node_idx].has_parent();
|
||||
let show_subject = nodes[node_idx].show_subject();
|
||||
|
||||
let mut s = format!(
|
||||
"{}{}{} ",
|
||||
|
@ -525,12 +420,10 @@ impl Component for PlainListing {
|
|||
let account = &mut context.accounts[self.cursor_pos.0];
|
||||
let (hash, is_seen) = {
|
||||
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
|
||||
let envelope: &mut Envelope = if threaded {
|
||||
let i = mailbox.threaded_mail(idx);
|
||||
&mut mailbox.collection[i]
|
||||
} else {
|
||||
&mut mailbox.collection[self.local_collection[idx]]
|
||||
};
|
||||
let envelope: &mut Envelope = &mut mailbox
|
||||
.collection
|
||||
.entry(self.local_collection[idx])
|
||||
.or_default();
|
||||
(envelope.hash(), envelope.is_seen())
|
||||
};
|
||||
if !is_seen {
|
||||
|
@ -539,12 +432,10 @@ impl Component for PlainListing {
|
|||
backend.operation(hash)
|
||||
};
|
||||
let mailbox = &mut account[self.cursor_pos.1].as_mut().unwrap();
|
||||
let envelope: &mut Envelope = if threaded {
|
||||
let i = mailbox.threaded_mail(idx);
|
||||
&mut mailbox.collection[i]
|
||||
} else {
|
||||
&mut mailbox.collection[self.local_collection[idx]]
|
||||
};
|
||||
let envelope: &mut Envelope = &mut mailbox
|
||||
.collection
|
||||
.entry(self.local_collection[idx])
|
||||
.or_default();
|
||||
envelope.set_seen(op).unwrap();
|
||||
true
|
||||
} else {
|
||||
|
@ -598,11 +489,11 @@ impl Component for PlainListing {
|
|||
let account = &context.accounts[self.cursor_pos.0];
|
||||
let mailbox = &account[self.cursor_pos.1].as_ref().unwrap();
|
||||
let mut coordinates = self.cursor_pos;
|
||||
coordinates.2 = if threaded {
|
||||
mailbox.threaded_mail(self.cursor_pos.2)
|
||||
} else {
|
||||
self.local_collection[self.cursor_pos.2]
|
||||
};
|
||||
let coordinates = (
|
||||
coordinates.0,
|
||||
coordinates.1,
|
||||
self.local_collection[self.cursor_pos.2],
|
||||
);
|
||||
self.view = Some(MailView::new(coordinates, None, None));
|
||||
}
|
||||
self.view.as_mut().unwrap().draw(
|
||||
|
|
|
@ -144,7 +144,7 @@ impl AccountMenu {
|
|||
.as_ref()
|
||||
.unwrap()
|
||||
.collection
|
||||
.iter()
|
||||
.values()
|
||||
.filter(|e| !e.is_seen())
|
||||
.count();
|
||||
s.insert_str(
|
||||
|
|
|
@ -61,7 +61,7 @@ impl ViewMode {
|
|||
/// menus
|
||||
#[derive(Debug, Default)]
|
||||
pub struct MailView {
|
||||
coordinates: (usize, usize, usize),
|
||||
coordinates: (usize, usize, EnvelopeHash),
|
||||
pager: Option<Pager>,
|
||||
subview: Option<Box<Component>>,
|
||||
dirty: bool,
|
||||
|
@ -79,7 +79,7 @@ impl fmt::Display for MailView {
|
|||
|
||||
impl MailView {
|
||||
pub fn new(
|
||||
coordinates: (usize, usize, usize),
|
||||
coordinates: (usize, usize, EnvelopeHash),
|
||||
pager: Option<Pager>,
|
||||
subview: Option<Box<Component>>,
|
||||
) -> Self {
|
||||
|
@ -217,7 +217,7 @@ impl Component for MailView {
|
|||
let mailbox = &mut accounts[self.coordinates.0][self.coordinates.1]
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let envelope: &Envelope = &mailbox.collection[self.coordinates.2];
|
||||
let envelope: &Envelope = &mailbox.collection[&self.coordinates.2];
|
||||
|
||||
if self.mode == ViewMode::Raw {
|
||||
clear_area(grid, area);
|
||||
|
@ -302,7 +302,7 @@ impl Component for MailView {
|
|||
let mailbox = &context.accounts[mailbox_idx.0][mailbox_idx.1]
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let envelope: &Envelope = &mailbox.collection[mailbox_idx.2];
|
||||
let envelope: &Envelope = &mailbox.collection[&mailbox_idx.2];
|
||||
let op = context.accounts[mailbox_idx.0]
|
||||
.backend
|
||||
.operation(envelope.hash());
|
||||
|
@ -419,7 +419,7 @@ impl Component for MailView {
|
|||
.as_ref()
|
||||
.unwrap();
|
||||
|
||||
let envelope: &Envelope = &mailbox.collection[self.coordinates.2];
|
||||
let envelope: &Envelope = &mailbox.collection[&self.coordinates.2];
|
||||
let op = context.accounts[self.coordinates.0]
|
||||
.backend
|
||||
.operation(envelope.hash());
|
||||
|
@ -514,7 +514,7 @@ impl Component for MailView {
|
|||
.as_ref()
|
||||
.unwrap();
|
||||
|
||||
let envelope: &Envelope = &mailbox.collection[self.coordinates.2];
|
||||
let envelope: &Envelope = &mailbox.collection[&self.coordinates.2];
|
||||
let finder = LinkFinder::new();
|
||||
let op = context.accounts[self.coordinates.0]
|
||||
.backend
|
||||
|
|
|
@ -25,9 +25,9 @@ use std::cmp;
|
|||
#[derive(Debug, Clone)]
|
||||
struct ThreadEntry {
|
||||
index: (usize, usize, usize),
|
||||
/// (indentation, container index, line number in listing)
|
||||
/// (indentation, thread_node index, line number in listing)
|
||||
indentation: usize,
|
||||
msg_idx: usize,
|
||||
msg_idx: EnvelopeHash,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
|
@ -47,7 +47,7 @@ pub struct ThreadView {
|
|||
|
||||
impl ThreadView {
|
||||
/*
|
||||
* coordinates: (account index, mailbox index, root set container index)
|
||||
* coordinates: (account index, mailbox index, root set thread_node index)
|
||||
* expanded_idx: optional position of expanded entry when we render the threadview. Default
|
||||
* expanded message is the last one.
|
||||
* context: current context
|
||||
|
@ -62,13 +62,13 @@ impl ThreadView {
|
|||
let mailbox = &context.accounts[coordinates.0][coordinates.1]
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let threads = &mailbox.threads;
|
||||
let container = &threads.containers()[threads.root_set()[coordinates.2]];
|
||||
let threads = &mailbox.collection.threads;
|
||||
let thread_node = &threads.thread_nodes()[threads.root_set()[coordinates.2]];
|
||||
|
||||
if container.message().is_some() {
|
||||
if thread_node.message().is_some() {
|
||||
stack.push((0, threads.root_set()[coordinates.2]));
|
||||
} else {
|
||||
stack.push((1, container.first_child().unwrap()));
|
||||
stack.push((1, thread_node.children()[0]));
|
||||
}
|
||||
let mut view = ThreadView {
|
||||
dirty: true,
|
||||
|
@ -95,13 +95,9 @@ impl ThreadView {
|
|||
}
|
||||
_ => {}
|
||||
}
|
||||
let container = &threads.containers()[idx];
|
||||
if let Some(i) = container.next_sibling() {
|
||||
stack.push((ind, i));
|
||||
}
|
||||
|
||||
if let Some(i) = container.first_child() {
|
||||
stack.push((ind + 1, i));
|
||||
let thread_node = &threads.thread_nodes()[idx];
|
||||
for &c in thread_node.children().iter() {
|
||||
stack.push((ind + 1, c));
|
||||
}
|
||||
}
|
||||
if expanded_idx.is_none() {
|
||||
|
@ -117,9 +113,9 @@ impl ThreadView {
|
|||
let mut highlight_reply_subjects: Vec<Option<usize>> =
|
||||
Vec::with_capacity(view.entries.len());
|
||||
for e in &view.entries {
|
||||
let envelope: &Envelope = &mailbox.collection[e.msg_idx];
|
||||
let container = &threads.containers()[e.index.1];
|
||||
let string = if container.show_subject() {
|
||||
let envelope: &Envelope = &mailbox.collection[&e.msg_idx];
|
||||
let thread_node = &threads.thread_nodes()[e.index.1];
|
||||
let string = if thread_node.show_subject() {
|
||||
let subject = envelope.subject();
|
||||
highlight_reply_subjects.push(Some(subject.len()));
|
||||
format!(
|
||||
|
@ -198,11 +194,11 @@ impl ThreadView {
|
|||
let mailbox = &context.accounts[self.coordinates.0][self.coordinates.1]
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let container = &mailbox.threads.containers()[idx];
|
||||
let msg_idx = if let Some(i) = container.message() {
|
||||
let thread_node = &mailbox.collection.threads.thread_nodes()[idx];
|
||||
let msg_idx = if let Some(i) = thread_node.message() {
|
||||
i
|
||||
} else {
|
||||
mailbox.threads.containers()[container.first_child().unwrap()]
|
||||
mailbox.collection.threads.thread_nodes()[thread_node.children()[0]]
|
||||
.message()
|
||||
.unwrap()
|
||||
};
|
||||
|
@ -316,16 +312,16 @@ impl ThreadView {
|
|||
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1]
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let threads = &mailbox.threads;
|
||||
let container = &threads.containers()[threads.root_set()[self.coordinates.2]];
|
||||
let i = if let Some(i) = container.message() {
|
||||
let threads = &mailbox.collection.threads;
|
||||
let thread_node = &threads.thread_nodes()[threads.root_set()[self.coordinates.2]];
|
||||
let i = if let Some(i) = thread_node.message() {
|
||||
i
|
||||
} else {
|
||||
threads.containers()[container.first_child().unwrap()]
|
||||
threads.thread_nodes()[thread_node.children()[0]]
|
||||
.message()
|
||||
.unwrap()
|
||||
};
|
||||
let envelope: &Envelope = &mailbox.collection[i];
|
||||
let envelope: &Envelope = &mailbox.collection[&i];
|
||||
|
||||
let (x, y) = write_string_to_grid(
|
||||
&envelope.subject(),
|
||||
|
@ -435,16 +431,16 @@ impl ThreadView {
|
|||
let mailbox = &mut context.accounts[self.coordinates.0][self.coordinates.1]
|
||||
.as_ref()
|
||||
.unwrap();
|
||||
let threads = &mailbox.threads;
|
||||
let container = &threads.containers()[threads.root_set()[self.coordinates.2]];
|
||||
let i = if let Some(i) = container.message() {
|
||||
let threads = &mailbox.collection.threads;
|
||||
let thread_node = &threads.thread_nodes()[threads.root_set()[self.coordinates.2]];
|
||||
let i = if let Some(i) = thread_node.message() {
|
||||
i
|
||||
} else {
|
||||
threads.containers()[container.first_child().unwrap()]
|
||||
threads.thread_nodes()[thread_node.children()[0]]
|
||||
.message()
|
||||
.unwrap()
|
||||
};
|
||||
let envelope: &Envelope = &mailbox.collection[i];
|
||||
let envelope: &Envelope = &mailbox.collection[&i];
|
||||
|
||||
let (x, y) = write_string_to_grid(
|
||||
&envelope.subject(),
|
||||
|
|
|
@ -562,7 +562,7 @@ impl Component for StatusBar {
|
|||
self.mode,
|
||||
m.folder.name(),
|
||||
m.collection.len(),
|
||||
m.collection.iter().filter(|e| !e.is_seen()).count()
|
||||
m.collection.values().filter(|e| !e.is_seen()).count()
|
||||
);
|
||||
self.dirty = true;
|
||||
}
|
||||
|
|
|
@ -185,7 +185,7 @@ impl State {
|
|||
thread::Builder::new()
|
||||
.name("startup-thread".to_string())
|
||||
.spawn(move || {
|
||||
let dur = time::Duration::from_millis(100);
|
||||
let dur = time::Duration::from_millis(800);
|
||||
loop {
|
||||
chan_select! {
|
||||
default => {},
|
||||
|
|
Loading…
Reference in New Issue