diff --git a/melib/src/mailbox/backends/maildir/mod.rs b/melib/src/mailbox/backends/maildir/mod.rs index 2957afb00..08daea782 100644 --- a/melib/src/mailbox/backends/maildir/mod.rs +++ b/melib/src/mailbox/backends/maildir/mod.rs @@ -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), } } diff --git a/melib/src/mailbox/collection.rs b/melib/src/mailbox/collection.rs new file mode 100644 index 000000000..a446c0a32 --- /dev/null +++ b/melib/src/mailbox/collection.rs @@ -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, + date_index: BTreeMap, + subject_index: Option>, + pub threads: Threads, +} + +impl Collection { + pub fn new(vec: Vec) -> Collection { + let mut envelopes: FnvHashMap = + 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; + + fn deref(&self) -> &FnvHashMap { + &self.envelopes + } +} + +impl DerefMut for Collection { + fn deref_mut(&mut self) -> &mut FnvHashMap { + &mut self.envelopes + } +} diff --git a/melib/src/mailbox/mod.rs b/melib/src/mailbox/mod.rs index 49a2cb337..010e4aa92 100644 --- a/melib/src/mailbox/mod.rs +++ b/melib/src/mailbox/mod.rs @@ -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, - 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>, - collection: Result>, + envelopes: Result>, ) -> Result { - let mut collection: Vec = collection?; - collection.sort_by(|a, b| a.date().cmp(&b.date())); - let threads = build_threads(&mut collection, sent_folder); + let mut envelopes: Vec = 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); } } diff --git a/melib/src/mailbox/thread.rs b/melib/src/mailbox/thread.rs index d10f8377f..b1e02680a 100644 --- a/melib/src/mailbox/thread.rs +++ b/melib/src/mailbox/thread.rs @@ -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`). -// -/// `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, +#[derive(Clone, Debug)] +pub struct ThreadNode { + message: Option, parent: Option, - first_child: Option, - next_sibling: Option, + children: Vec, 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>, 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 { + self.message + } + + pub fn has_message(&self) -> bool { + self.message.is_some() + } + + pub fn parent(&self) -> Option { + 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, - threaded_collection: Vec, + thread_nodes: Vec, root_set: Vec, - tree: RefCell>, + + message_ids: FnvHashMap, sort: RefCell<(SortField, SortOrder)>, subsort: RefCell<(SortField, SortOrder)>, } -pub struct ThreadIterator<'a> { - pos: usize, - stack: Vec, - tree: Ref<'a, Vec>, -} -impl<'a> Iterator for ThreadIterator<'a> { - type Item = usize; - fn next(&mut self) -> Option { - { - 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>, -} - -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 { - &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 { - &self.threaded_collection - } - pub fn containers(&self) -> &Vec { - &self.containers - } + pub fn new(collection: &mut FnvHashMap) -> 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 = + Vec::with_capacity((collection.len() as f64 * 1.2) as usize); + /* A hash table of Message IDs */ + let mut message_ids: FnvHashMap = + 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 = 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) { + 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, + ) { + } + + fn inner_sort_by( + &self, + sort: (SortField, SortOrder), + collection: &FnvHashMap, + ) { + } + pub fn sort_by( &self, sort: (SortField, SortOrder), subsort: (SortField, SortOrder), - collection: &[Envelope], + collection: &FnvHashMap, ) { + /* 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, - indentation: usize, - threaded: &mut Vec, - 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 { + &self.thread_nodes + } + + pub fn root_set(&self) -> &Vec { + &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, + message_ids: &mut FnvHashMap, + collection: &mut FnvHashMap, +) { + 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 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 { - self.message - } - pub fn parent(&self) -> Option { - self.parent - } - pub fn has_parent(&self) -> bool { - self.parent.is_some() - } - pub fn first_child(&self) -> Option { - self.first_child - } - pub fn next_sibling(&self) -> Option { - 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, - id_table: &mut FnvHashMap, 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, - sent_folder: &Option>, -) -> Threads { - /* To reconstruct thread information from the mails we need: */ - - /* a vector to hold thread members */ - let mut threads: Vec = Vec::with_capacity((collection.len() as f64 * 1.2) as usize); - /* A hash table of Message IDs */ - let mut id_table: FnvHashMap, 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, + indentation: usize, + root_subject_idx: usize, + collection: &FnvHashMap, +) { + 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 = 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; } diff --git a/ui/src/components/mail/compose.rs b/ui/src/components/mail/compose.rs index 21bdc1875..f044078e5 100644 --- a/ui/src/components/mail/compose.rs +++ b/ui/src/components/mail/compose.rs @@ -25,7 +25,7 @@ use melib::Draft; #[derive(Debug)] pub struct Composer { - reply_context: Option<((usize, usize), Box)>, // (folder_index, container_index) + reply_context: Option<((usize, usize), Box)>, // (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() }, ); diff --git a/ui/src/components/mail/listing/compact.rs b/ui/src/components/mail/listing/compact.rs index 026fc6f34..c6280f7f4 100644 --- a/ui/src/components/mail/listing/compact.rs +++ b/ui/src/components/mail/listing/compact.rs @@ -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 { diff --git a/ui/src/components/mail/listing/mod.rs b/ui/src/components/mail/listing/mod.rs index 5fa122403..be1a2ef8c 100644 --- a/ui/src/components/mail/listing/mod.rs +++ b/ui/src/components/mail/listing/mod.rs @@ -34,7 +34,7 @@ pub struct PlainListing { cursor_pos: (usize, usize, usize), new_cursor_pos: (usize, usize, usize), length: usize, - local_collection: Vec, + local_collection: Vec, 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 = 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 = &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, ) -> 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( diff --git a/ui/src/components/mail/mod.rs b/ui/src/components/mail/mod.rs index 0c46a561c..62f9243ce 100644 --- a/ui/src/components/mail/mod.rs +++ b/ui/src/components/mail/mod.rs @@ -144,7 +144,7 @@ impl AccountMenu { .as_ref() .unwrap() .collection - .iter() + .values() .filter(|e| !e.is_seen()) .count(); s.insert_str( diff --git a/ui/src/components/mail/view/mod.rs b/ui/src/components/mail/view/mod.rs index 6d28e0a38..53e58537a 100644 --- a/ui/src/components/mail/view/mod.rs +++ b/ui/src/components/mail/view/mod.rs @@ -61,7 +61,7 @@ impl ViewMode { /// menus #[derive(Debug, Default)] pub struct MailView { - coordinates: (usize, usize, usize), + coordinates: (usize, usize, EnvelopeHash), pager: Option, subview: Option>, 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, subview: Option>, ) -> 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 diff --git a/ui/src/components/mail/view/thread.rs b/ui/src/components/mail/view/thread.rs index e434cd08f..f04b1667d 100644 --- a/ui/src/components/mail/view/thread.rs +++ b/ui/src/components/mail/view/thread.rs @@ -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> = 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(), diff --git a/ui/src/components/utilities.rs b/ui/src/components/utilities.rs index 620d3028a..cd24635d2 100644 --- a/ui/src/components/utilities.rs +++ b/ui/src/components/utilities.rs @@ -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; } diff --git a/ui/src/state.rs b/ui/src/state.rs index 566d4d9ca..1098925dd 100644 --- a/ui/src/state.rs +++ b/ui/src/state.rs @@ -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 => {},