diff --git a/src/capture.rs b/src/capture.rs index 90168f22..1db1a286 100644 --- a/src/capture.rs +++ b/src/capture.rs @@ -1439,15 +1439,25 @@ impl EndpointReader { #[derive(Copy, Clone)] pub enum CompletionStatus { Complete, - Ongoing + Ongoing, + InterleavedComplete(u64), + InterleavedOngoing, } impl CompletionStatus { pub fn is_complete(&self) -> bool { use CompletionStatus::*; match self { - Complete => true, - Ongoing => false, + Complete | InterleavedComplete(_) => true, + Ongoing | InterleavedOngoing => false, + } + } + + pub fn is_interleaved(&self) -> bool { + use CompletionStatus::*; + match self { + InterleavedComplete(_) | InterleavedOngoing => true, + Complete | Ongoing => false, } } } diff --git a/src/tree_list_model.rs b/src/tree_list_model.rs index 63b8d79f..123bebcb 100644 --- a/src/tree_list_model.rs +++ b/src/tree_list_model.rs @@ -5,6 +5,7 @@ use std::collections::btree_map::Entry; use std::fmt::Debug; use std::marker::PhantomData; use std::rc::{Rc, Weak}; +use std::ops::Range; use anyhow::{Context, Error, bail}; @@ -15,7 +16,12 @@ use gtk::gio::prelude::ListModelExt; use derive_more::AddAssign; use itertools::Itertools; -use crate::capture::{CaptureReader, ItemSource}; +use crate::capture::{ + CaptureReader, + CompletionStatus, + ItemSource, + SearchResult +}; use crate::model::GenericModel; use crate::row_data::GenericRowData; use crate::item_widget::ItemWidget; @@ -41,8 +47,8 @@ trait Node { /// Whether the children of this node are displayed. fn expanded(&self) -> bool; - /// Mark this node as completed. - fn set_completed(&mut self); + /// Update this node's completion status. + fn set_completion(&mut self, completion: CompletionStatus); } struct Children { @@ -88,6 +94,9 @@ pub struct ItemNode { /// Index of this node below the parent Item. item_index: u64, + /// Completion status of this node. + completion: CompletionStatus, + /// Children of this item. children: Children, @@ -101,6 +110,11 @@ impl Children { self.expanded.contains_key(&index) } + /// Get the expanded child with the given index. + fn get_expanded(&self, index: u64) -> Option> { + self.expanded.get(&index).map(Rc::clone) + } + /// Set whether this child of the owning node is expanded. fn set_expanded(&mut self, child_rc: &ItemNodeRc, expanded: bool) { let child = child_rc.borrow(); @@ -152,8 +166,8 @@ impl Node for RootNode { true } - fn set_completed(&mut self) { - self.complete = true; + fn set_completion(&mut self, completion: CompletionStatus) { + self.complete = completion.is_complete(); } } @@ -188,14 +202,17 @@ impl Node for ItemNode where Item: Clone { } } - fn set_completed(&mut self) { - if let Some(parent_rc) = self.parent.upgrade() { - parent_rc - .borrow_mut() - .children_mut() - .incomplete - .remove(&self.item_index); + fn set_completion(&mut self, completion: CompletionStatus) { + if completion.is_complete() { + if let Some(parent_rc) = self.parent.upgrade() { + parent_rc + .borrow_mut() + .children_mut() + .incomplete + .remove(&self.item_index); + } } + self.completion = completion; } } @@ -274,10 +291,87 @@ impl ItemNode where Item: Clone { } } +#[derive(Clone)] +struct ItemNodeSet { + items: BTreeMap>, +} + +impl ItemNodeSet where Item: Clone { + fn new(node_rc: &ItemNodeRc) -> Self { + let mut new = Self { + items: BTreeMap::new() + }; + new.add(node_rc); + new + } + + fn add(&mut self, node_rc: &ItemNodeRc) { + let index = node_rc.borrow().item_index; + self.items.insert(index, node_rc.clone()); + } + + fn remove(&mut self, node_rc: &ItemNodeRc) { + let index = node_rc.borrow().item_index; + self.items.remove(&index); + } + + fn with(&self, node_rc: &ItemNodeRc) -> Self { + let mut new = self.clone(); + new.add(node_rc); + new + } + + fn without(&self, node_rc: &ItemNodeRc) -> Self { + let mut new = self.clone(); + new.remove(node_rc); + new + } + + fn any_incomplete(&self) -> bool { + self.items + .values() + .any(|node_rc| !node_rc.borrow().completion.is_complete()) + } + + fn is_empty(&self) -> bool { + self.items.is_empty() + } + + fn iter_items(&self) -> impl Iterator + '_ { + self.items + .iter() + .map(|(index, node_rc)| (*index, node_rc.borrow().item.clone())) + } +} + +impl PartialEq for ItemNodeSet { + fn eq(&self, other: &Self) -> bool { + self.items.len() == other.items.len() && + self.items.values() + .zip(other.items.values()) + .all(|(a, b)| Rc::ptr_eq(a, b)) + } +} + +impl Debug for ItemNodeSet +where Item: Clone + Debug +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) + -> Result<(), std::fmt::Error> + { + write!(f, "{:?}", + self.items + .values() + .map(|rc| rc.borrow().item.clone()) + .collect::>()) + } +} + #[derive(Clone)] enum Source { TopLevelItems(), ChildrenOf(ItemNodeRc), + InterleavedSearch(ItemNodeSet, Range), } use Source::*; @@ -301,6 +395,8 @@ where Item: Clone + Debug write!(f, "Top level items"), ChildrenOf(rc) => write!(f, "Children of {:?}", rc.borrow().item), + InterleavedSearch(expanded, range) => + write!(f, "Interleaved search in {range:?} from {expanded:?}"), }?; write!(f, ", offset {}, length {}", self.offset, self.length) } @@ -312,6 +408,16 @@ impl Region where Item: Clone { region_b: &Region ) -> Option> { match (®ion_a.source, ®ion_b.source) { + (InterleavedSearch(exp_a, range_a), + InterleavedSearch(exp_b, range_b)) if exp_a == exp_b => Some( + Region { + source: InterleavedSearch( + exp_a.clone(), + range_a.start..range_b.end), + offset: region_a.offset, + length: region_a.length + region_b.length, + } + ), (ChildrenOf(a_ref), ChildrenOf(b_ref)) if Rc::ptr_eq(a_ref, b_ref) => Some( Region { @@ -346,6 +452,17 @@ impl std::fmt::Display for ModelUpdate { } } +enum InterleavedUpdate { + AddedOngoing(ItemNodeRc, u64), + AddedComplete(ItemNodeRc, u64, u64), +} + +enum UpdateState { + Root(Vec>), + Interleaved(Option>), + Contiguous(), +} + pub struct TreeListModel { _marker: PhantomData<(Model, RowData)>, capture: RefCell, @@ -389,6 +506,10 @@ where Item: 'static + Clone + Debug, self.root.borrow().children().total_count } + fn item_count(&self) -> u64 { + self.root.borrow().children().direct_count + } + fn check(&self) -> Result<(), Error> { // Check that we have the expected number of rows in the region map. let expected_count = self.row_count(); @@ -399,8 +520,8 @@ where Item: 'static + Clone + Debug, .map(|(start, region)| start + region.length) .unwrap_or(0); if expected_count != actual_count { - bail!("Region map total row count is {}, expected {}", - actual_count, expected_count) + bail!("Region map total row count is {actual_count}, \ + expected {expected_count}") } else { Ok(()) } @@ -425,6 +546,7 @@ where Item: 'static + Clone + Debug, let rows_affected = node.children.direct_count; let expanded_children = node.children.expanded.clone(); + let interleaved = node.completion.is_interleaved(); // There cannot be any visible incomplete children at this point. node.children.incomplete.clear(); @@ -442,13 +564,39 @@ where Item: 'static + Clone + Debug, } else { #[cfg(feature="debug-region-map")] println!("\nCollapsing node at {}", position); - // If collapsing, first recursively collapse children of this node. - for (index, child_ref) in expanded_children { - let child_position = children_position + index; - #[cfg(feature="debug-region-map")] - println!("\nRecursively collapsing child at {}", - child_position); - self.set_expanded(model, &child_ref, child_position, false)?; + if interleaved { + // Collapse expanded children of this node, from last to first. + let mut end_position = self.row_count(); + for child_ref in expanded_children.into_values().rev() { + // Since this node's children are interleaved, we find each + // expanded child's position by searching the region map + // in reverse order, for the first region containing the + // expanded child's own children. The search window extends + // from the parent node to either the end of the region map, + // or the previous collapsed child, shrinking each time. + let child_position = + self.find_expanded(position..end_position, &child_ref)?; + // Update end position for next search. + end_position = child_position; + // Collapse this child. + #[cfg(feature="debug-region-map")] + println!("\nRecursively collapsing child at {}", + child_position); + self.set_expanded( + model, &child_ref, child_position, false)?; + } + } else { + // If this node's children are not interleaved, each child's + // position is at a simple offset, provided we collapse them + // from first to last. + for (index, child_ref) in expanded_children { + let child_position = children_position + index; + #[cfg(feature="debug-region-map")] + println!("\nRecursively collapsing child at {}", + child_position); + self.set_expanded( + model, &child_ref, child_position, false)?; + } } // Update the region map for the removed children. self.collapse(children_position, node_ref)? @@ -484,9 +632,67 @@ where Item: 'static + Clone + Debug, Ok(()) } + fn find_expanded(&self, range: Range, node_rc: &ItemNodeRc) + -> Result + { + self.regions + .borrow() + .range(range) + .rev() + .find_map(|(start, region)| { + match (region.offset, ®ion.source) { + (0, ChildrenOf(rc)) + if Rc::ptr_eq(rc, node_rc) + // The node appears on the row before + // its first children. + => Some(*start - 1), + _ => None + } + }) + .context("No region found for expanded child node") + } + + fn find_top_level_item(&self, node_rc: &ItemNodeRc) + -> Result + { + let index = node_rc.borrow().item_index; + for (start, region) in self.regions.borrow().iter() { + match ®ion.source { + TopLevelItems() if + region.offset <= index && + region.length > index - region.offset => + { + return Ok(start + index - region.offset); + }, + InterleavedSearch(expanded, range) if + range.start < index && + range.end > index => + { + let count_range = range.start..index; + let position = + self.count_items(&count_range)? + + self.count_within(expanded, &count_range)?; + if position >= region.offset && + position < region.offset + region.length + { + return Ok(start + position - region.offset) + } + } + _ => {} + } + } + bail!("Item not found") + } + fn expand(&self, position: u64, node_ref: &ItemNodeRc) -> Result { + // Extract some details of the node being expanded. + let node = node_ref.borrow(); + let node_start = node.item_index; + let next_item = node_start + 1; + let interleaved = node.completion.is_interleaved(); + // Find the start of the parent region. let (&parent_start, _) = self.regions .borrow() @@ -506,29 +712,179 @@ where Item: 'static + Clone + Debug, "Parent not found at position {parent_start}"))?; // Remove all following regions, to iterate over later. - let following_regions = self.regions + let mut following_regions = self.regions .borrow_mut() - .split_off(&parent_start) - .into_iter(); + .split_off(&parent_start); + + let more_after = !following_regions.is_empty(); // Split the parent region and construct a new region between. - let update = self.split_parent(parent_start, &parent, node_ref, - vec![Region { - source: parent.source.clone(), - offset: parent.offset, - length: relative_position, - }], - Region { - source: ChildrenOf(node_ref.clone()), - offset: 0, - length: node_ref.borrow().children.direct_count, + // + // Where the new region is an interleaved one, its overlap with the + // remainder of the parent is handled in the split_parent call. + let mut update = match (interleaved, &parent.source) { + // Self-contained region expanded. + (false, _) => { + self.split_parent(parent_start, &parent, node_ref, more_after, + vec![Region { + source: parent.source.clone(), + offset: parent.offset, + length: relative_position, + }], + Region { + source: ChildrenOf(node_ref.clone()), + offset: 0, + length: node.children.direct_count, + }, + vec![Region { + source: parent.source.clone(), + offset: parent.offset + relative_position, + length: parent.length - relative_position, + }] + )? }, - vec![Region { - source: parent.source.clone(), - offset: parent.offset + relative_position, - length: parent.length - relative_position, - }] - )?; + // Interleaved region expanded from within a root region. + (true, TopLevelItems()) => { + let expanded = ItemNodeSet::new(node_ref); + let range = node_start..next_item; + let added = self.count_within(&expanded, &range)?; + if parent.length == 1 && + parent_start + parent.length != self.row_count() + { + // There's nothing to split, and there must already be an + // interleaved region after this, so keep the parent as is. + let mut update = ModelUpdate::default(); + self.preserve_region( + &mut update, parent_start, &parent, false)?; + update + } else { + self.split_parent( + parent_start, &parent, node_ref, more_after, + vec![Region { + source: TopLevelItems(), + offset: parent.offset, + length: relative_position, + }], + Region { + source: InterleavedSearch(expanded, range), + offset: 0, + length: added, + }, + vec![Region { + source: TopLevelItems(), + offset: parent.offset + relative_position, + length: parent.length - relative_position, + }] + )? + } + }, + // New interleaved region expanded from within an existing one. + (true, InterleavedSearch(parent_expanded, parent_range)) => { + let range_1 = parent_range.start..node_start; + let range_2 = node_start..next_item; + let range_3 = next_item..parent_range.end; + let expanded_1 = parent_expanded.clone(); + let expanded_2 = parent_expanded.with(node_ref); + let expanded_3 = parent_expanded.clone(); + let total_changed = self.count_within(parent_expanded, &range_2)?; + let rows_present = parent.length - relative_position; + let (changed, added) = if rows_present >= total_changed { + let changed = total_changed; + let added = self.count_in_range(&range_2, node_ref)?; + (changed, added) + } else { + let changed = rows_present; + let added = self.count_to_item( + parent_expanded, &range_2, rows_present - 1, node_ref)?; + (changed, added) + }; + if parent.offset != 0 { + // Update the range end of previous parts of this parent. + for region in self.regions.borrow_mut().values_mut().rev() { + match &mut region.source { + TopLevelItems() => break, + ChildrenOf(..) => continue, + InterleavedSearch(_, range) + if range.end > node_start => + { + range.end = node_start; + }, + InterleavedSearch(..) => break + } + } + } + if total_changed > rows_present { + // Update the range start of following parts of this parent. + for region in following_regions.values_mut() { + match &mut region.source { + TopLevelItems() => break, + ChildrenOf(..) => continue, + InterleavedSearch(_, range) + if range.start < node_start => + { + range.start = node_start; + region.offset -= relative_position; + }, + InterleavedSearch(..) => break + } + } + } + self.split_parent(parent_start, &parent, node_ref, more_after, + vec![ + Region { + source: InterleavedSearch(expanded_1, range_1), + offset: parent.offset, + length: relative_position - 1, + }, + Region { + source: TopLevelItems(), + offset: node_start, + length: 1, + } + ], + Region { + source: InterleavedSearch(expanded_2, range_2), + offset: 0, + length: changed + added, + }, + if rows_present > changed { + vec![ + Region { + source: TopLevelItems(), + offset: next_item, + length: 1, + }, + Region { + source: InterleavedSearch(expanded_3, range_3), + offset: 0, + length: rows_present - changed - 1, + } + ] + } else { + vec![] + } + )? + }, + // Other combinations are not supported. + (..) => bail!("Unable to expand from {parent:?}") + }; + + drop(node); + + // For an interleaved source, update all regions that it overlaps. + let mut following_regions = following_regions.into_iter(); + if interleaved { + while let Some((start, region)) = following_regions.next() { + let more_after = following_regions.len() > 0; + // Do whatever is necessary to overlap this region. + if !self.overlap_region( + &mut update, start, ®ion, node_ref, more_after)? + { + // No further overlapping regions. + break; + } + } + } // Shift all remaining regions down by the added rows. for (start, region) in following_regions { @@ -571,6 +927,20 @@ where Item: 'static + Clone + Debug, rows_removed: node_ref.borrow().children.direct_count, rows_changed: 0, } + }, + // For an interleaved source, update all overlapped regions. + InterleavedSearch(..) => { + let mut update = ModelUpdate::default(); + for (start, region) in following_regions.by_ref() { + // Do whatever is necessary to unoverlap this region. + if !self.unoverlap_region( + &mut update, start, ®ion, node_ref)? + { + // No further overlapping regions. + break; + } + } + update } }; @@ -582,6 +952,295 @@ where Item: 'static + Clone + Debug, Ok(update) } + fn overlap_region(&self, + update: &mut ModelUpdate, + start: u64, + region: &Region, + node_ref: &ItemNodeRc, + more_after: bool) + -> Result + { + use Source::*; + + let node_range = self.node_range(node_ref); + + Ok(match ®ion.source { + TopLevelItems() if region.offset >= node_range.end => { + // This region is not overlapped. + self.preserve_region(update, start, region, false)? + }, + InterleavedSearch(_, range) if range.start >= node_range.end => { + // This region is not overlapped. + self.preserve_region(update, start, region, false)? + }, + ChildrenOf(_) => { + // This region is overlapped but self-contained. + self.preserve_region(update, start, region, true)? + }, + TopLevelItems() if region.length == 1 => { + // This region includes only a single root item. Check + // whether it is overlapped by the new node. + let overlapped = region.offset < node_range.end; + // Either way, the region itself is unchanged. + self.preserve_region(update, start, region, overlapped)?; + // We may need to add a new interleaved region after this item + // if it is overlapped, is the last item, and there is not + // already an interleaved region to follow it. + let item_count = self.item_count(); + let last_item = region.offset + 1 == item_count; + if overlapped && last_item && !more_after { + let range = region.offset..item_count; + let added = self.count_in_range(&range, node_ref)?; + let expanded = ItemNodeSet::new(node_ref); + let trailing_region = Region { + source: InterleavedSearch(expanded, range), + offset: 0, + length: added, + }; + #[cfg(feature="debug-region-map")] { + println!(); + println!("Inserting: {:?}", trailing_region); + } + self.insert_region( + start + region.length + update.rows_added, + trailing_region + )?; + update.rows_added += added; + } + overlapped + }, + TopLevelItems() + if region.offset + region.length <= node_range.end => + { + // This region is fully overlapped by the new node. + // Replace with a new interleaved region. + let end = region.offset + region.length; + let contains_last_item = end == self.item_count(); + let expanded = ItemNodeSet::new(node_ref); + if contains_last_item && !more_after { + // This region contains the last item, and there is not + // currently a following interleaved region. Create one + // that runs to the end of the model. + let range = region.offset..end; + let added = self.count_in_range(&range, node_ref)?; + self.replace_region(update, start, region, + vec![ + Region { + source: TopLevelItems(), + offset: region.offset, + length: 1, + }, + Region { + source: InterleavedSearch(expanded, range), + offset: 0, + length: region.length - 1 + added, + } + ], + )?; + false + } else { + // There are further regions after this one, so just + // overlap up to the end of these top level items. + let range = region.offset..(end - 1); + let added = self.count_in_range(&range, node_ref)?; + self.replace_region(update, start, region, + vec![ + Region { + source: TopLevelItems(), + offset: region.offset, + length: 1, + }, + Region { + source: InterleavedSearch(expanded, range), + offset: 0, + length: region.length - 2 + added, + }, + Region { + source: TopLevelItems(), + offset: region.offset + region.length - 1, + length: 1 + } + ] + )?; + true + } + }, + TopLevelItems() => { + // This region is partially overlapped by the new node. + // Split it into overlapped and unoverlapped parts. + let expanded = ItemNodeSet::new(node_ref); + let range = region.offset..node_range.end; + let changed = self.count_items(&range)?; + let added = self.count_in_range(&range, node_ref)?; + self.partial_overlap(update, start, region, + vec![ + Region { + source: TopLevelItems(), + offset: region.offset, + length: 1, + }, + Region { + source: InterleavedSearch(expanded, range), + offset: 0, + length: changed + added + } + ], + vec![ + Region { + source: TopLevelItems(), + offset: region.offset + changed + 1, + length: region.length - changed - 1, + } + ] + )?; + // No longer overlapping. + false + }, + InterleavedSearch(expanded, range) + if range.end <= node_range.end => + { + // This region is fully overlapped by the new node. + // Replace with a new interleaved region. + let (added_before_offset, added_after_offset) = + self.count_around_offset( + expanded, range, node_ref, + region.offset, + region.offset + region.length)?; + let range = range.clone(); + let more_expanded = expanded.with(node_ref); + self.replace_region(update, start, region, + vec![ + Region { + source: InterleavedSearch(more_expanded, range), + offset: region.offset + added_before_offset, + length: region.length + added_after_offset, + } + ] + )?; + true + }, + InterleavedSearch(expanded, range) => { + // This region may be partially overlapped by the new node, + // depending on its starting offset and length. + let range_1 = range.start..node_range.end; + let range_2 = node_range.end..range.end; + // Work out the offset at which this source would be split. + let split_offset = self.count_items(&range_1)? + + self.count_within(expanded, &range_1)?; + if region.offset >= split_offset { + // This region begins after the split, so isn't overlapped. + self.preserve_region(update, start, region, false)? + } else if region.offset + region.length <= split_offset { + // This region ends before the split, so is fully overlapped. + let (added_before_offset, added_after_offset) = + self.count_around_offset( + expanded, range, node_ref, + region.offset, + region.offset + region.length)?; + let range = range.clone(); + let more_expanded = expanded.with(node_ref); + self.replace_region(update, start, region, + vec![ + Region { + source: InterleavedSearch(more_expanded, range), + offset: region.offset + added_before_offset, + length: region.length + added_after_offset, + } + ] + )?; + true + } else { + // This region straddles the split. Break it into overlapped + // and unoverlapped parts. + let changed = split_offset - region.offset; + let (added_before_offset, added_after_offset) = + self.count_around_offset( + expanded, range, node_ref, + region.offset, split_offset)?; + let expanded_1 = expanded.with(node_ref); + let expanded_2 = expanded.clone(); + self.partial_overlap(update, start, region, + vec![ + Region { + source: InterleavedSearch(expanded_1, range_1), + offset: region.offset + added_before_offset, + length: changed + added_after_offset, + } + ], + vec![ + Region { + source: TopLevelItems(), + offset: node_range.end, + length: 1, + }, + Region { + source: InterleavedSearch(expanded_2, range_2), + offset: 0, + length: region.length - changed - 1, + } + ] + )?; + // No longer overlapping. + false + } + } + }) + } + + fn unoverlap_region(&self, + update: &mut ModelUpdate, + start: u64, + region: &Region, + node_ref: &ItemNodeRc) + -> Result + { + use Source::*; + + let node_range = self.node_range(node_ref); + + Ok(match ®ion.source { + TopLevelItems() if region.offset >= node_range.end => { + // This region is not overlapped. + self.preserve_region(update, start, region, false)? + }, + InterleavedSearch(_, range) if range.start >= node_range.end => { + // This region is not overlapped. + self.preserve_region(update, start, region, false)? + }, + ChildrenOf(_) | TopLevelItems() => { + // This region is overlapped but self-contained. + self.preserve_region(update, start, region, true)? + }, + InterleavedSearch(expanded, range) => { + // This region is overlapped. Replace with a new one. + let less_expanded = expanded.without(node_ref); + let new_region = if less_expanded.is_empty() { + // This node was the last expanded one in this region. + Region { + source: TopLevelItems(), + offset: range.start + 1, + length: self.count_items(range)?, + } + } else { + // There are other nodes expanded in this region. + let (removed_before_offset, removed_after_offset) = + self.count_around_offset( + expanded, range, node_ref, + region.offset, + region.offset + region.length)?; + let range = range.clone(); + Region { + source: InterleavedSearch(less_expanded, range), + offset: region.offset - removed_before_offset, + length: region.length - removed_after_offset, + } + }; + self.replace_region(update, start, region, vec![new_region])?; + true + } + }) + } + fn insert_region(&self, position: u64, region: Region) -> Result<(), Error> { @@ -602,13 +1261,158 @@ where Item: 'static + Clone + Debug, } } + fn preserve_region(&self, + update: &mut ModelUpdate, + start: u64, + region: &Region, + include_as_changed: bool) + -> Result + { + let new_position = start + + update.rows_added + - update.rows_removed; + + self.insert_region(new_position, region.clone())?; + + if include_as_changed { + update.rows_changed += region.length; + } + + Ok(include_as_changed) + } + + fn replace_region(&self, + update: &mut ModelUpdate, + start: u64, + region: &Region, + new_regions: Vec>) + -> Result<(), Error> + { + use std::cmp::Ordering::*; + + let new_length: u64 = new_regions + .iter() + .map(|region| region.length) + .sum(); + + let effect = match new_length.cmp(®ion.length) { + Greater => ModelUpdate { + rows_added: new_length - region.length, + rows_removed: 0, + rows_changed: region.length + }, + Less => ModelUpdate { + rows_added: 0, + rows_removed: region.length - new_length, + rows_changed: new_length + }, + Equal => ModelUpdate { + rows_added: 0, + rows_removed: 0, + rows_changed: region.length + }, + }; + + #[cfg(feature="debug-region-map")] + { + println!(); + println!("Replacing: {:?}", region); + for (i, new_region) in new_regions.iter().enumerate() { + if i == 0 { + println!(" with: {:?}", new_region); + } else { + println!(" {:?}", new_region); + } + } + println!(" {}", effect); + } + + let mut position = start + + update.rows_added + - update.rows_removed; + + for region in new_regions { + let length = region.length; + self.insert_region(position, region)?; + position += length; + } + + *update += effect; + + Ok(()) + } + + fn partial_overlap(&self, + update: &mut ModelUpdate, + start: u64, + region: &Region, + changed_regions: Vec>, + unchanged_regions: Vec>) + -> Result<(), Error> + { + let changed_length: u64 = changed_regions + .iter() + .map(|region| region.length) + .sum(); + + let unchanged_length: u64 = unchanged_regions + .iter() + .map(|region| region.length) + .sum(); + + let total_length = changed_length + unchanged_length; + + let effect = ModelUpdate { + rows_added: total_length - region.length, + rows_removed: 0, + rows_changed: region.length - unchanged_length, + }; + + #[cfg(feature="debug-region-map")] + { + println!(); + println!("Splitting: {:?}", region); + for (i, changed_region) in changed_regions.iter().enumerate() { + if i == 0 { + println!(" changed: {:?}", changed_region); + } else { + println!(" : {:?}", changed_region); + } + } + for (i, unchanged_region) in unchanged_regions.iter().enumerate() { + if i == 0 { + println!("unchanged: {:?}", unchanged_region); + } else { + println!(" : {:?}", unchanged_region); + } + } + println!(" {}", effect); + } + + let mut position = start + update.rows_added - update.rows_removed; + for region in changed_regions + .into_iter() + .chain(unchanged_regions) + { + let length = region.length; + self.insert_region(position, region)?; + position += length; + } + + *update += effect; + + Ok(()) + } + + #[allow(clippy::too_many_arguments)] fn split_parent(&self, parent_start: u64, parent: &Region, - _node_ref: &ItemNodeRc, + node_ref: &ItemNodeRc, + more_after: bool, parts_before: Vec>, new_region: Region, - parts_after: Vec>) + mut parts_after: Vec>) -> Result { let length_before: u64 = parts_before @@ -626,7 +1430,7 @@ where Item: 'static + Clone + Debug, let rows_added = total_length - parent.length; let rows_changed = parent.length - length_before - length_after; - let update = ModelUpdate { + let mut update = ModelUpdate { rows_added, rows_removed: 0, rows_changed, @@ -657,6 +1461,7 @@ where Item: 'static + Clone + Debug, println!(" {}", &update); } + let interleaved = matches!(&new_region.source, InterleavedSearch(..)); let new_position = parent_start + length_before; let position_after = new_position + new_region.length; @@ -670,19 +1475,32 @@ where Item: 'static + Clone + Debug, self.insert_region(new_position, new_region)?; position = position_after; - for region in parts_after - .into_iter() - .filter(|region| region.length > 0) - { + + parts_after.retain(|region| region.length > 0); + let mut add_after = parts_after.into_iter(); + let mut overlap = interleaved; + while let Some(region) = add_after.next() { let length = region.length; - self.insert_region(position, region)?; + if overlap { + let more_after = more_after || add_after.len() > 0; + overlap = self.overlap_region(&mut update, + position - rows_added, + ®ion, + node_ref, + more_after)?; + } else { + self.insert_region(position, region)?; + } position += length; } Ok(update) } - fn merge_regions(&self) { + fn pre_merge(&self, printed: &mut bool) { + if *printed { + return + } #[cfg(feature="debug-region-map")] { println!(); println!("Before merge:"); @@ -690,14 +1508,18 @@ where Item: 'static + Clone + Debug, println!("{}: {:?}", start, region); } } + *printed = true; + } + fn merge_pairs(&self, printed: &mut bool) { let new_regions = self.regions - .borrow_mut() - .split_off(&0) + .borrow() + .clone() .into_iter() .coalesce(|(start_a, region_a), (start_b, region_b)| match Region::merge(®ion_a, ®ion_b) { Some(region_c) => { + self.pre_merge(printed); #[cfg(feature="debug-region-map")] { println!(); println!("Merging: {:?}", region_a); @@ -713,6 +1535,145 @@ where Item: 'static + Clone + Debug, self.regions.replace(new_regions); } + fn merge_regions(&self) { + let mut printed = false; + + // Merge adjacent regions with the same source. + self.merge_pairs(&mut printed); + + // Find starts and lengths of superfluous root regions. + let superfluous_regions: Vec<(u64, u64)> = self.regions + .borrow() + .iter() + .tuple_windows() + .filter_map(|((_, a), (b_start, b), (_, c))| + match (&a.source, &b.source, &c.source) { + (InterleavedSearch(exp_a, _), + TopLevelItems(), + InterleavedSearch(exp_c, _)) if exp_a == exp_c => { + self.pre_merge(&mut printed); + #[cfg(feature="debug-region-map")] + { + println!(); + println!("Dropping: {:?}", b); + } + Some((*b_start, b.length)) + }, + _ => None + }) + .collect(); + + // Remove superfluous regions. + for (start, length) in superfluous_regions { + let mut regions = self.regions.borrow_mut(); + regions.remove(&start); + let (_, prev_region) = regions + .range_mut(..start) + .next_back() + .unwrap(); + prev_region.length += length; + } + + // Once again merge adjacent regions with the same source. + self.merge_pairs(&mut printed); + } + + fn node_range(&self, node_ref: &ItemNodeRc) -> Range { + use CompletionStatus::*; + let node = node_ref.borrow(); + let start = node.item_index; + let end = match node.completion { + InterleavedComplete(index) => index, + InterleavedOngoing => self.item_count(), + _ => start, + }; + start..end + } + + fn count_items(&self, range: &Range) -> Result { + let length = range.end - range.start; + if length == 0 { + bail!("Range is empty") + } else { + Ok(length - 1) + } + } + + fn count_to_item(&self, + expanded: &ItemNodeSet, + range: &Range, + to_index: u64, + node_ref: &ItemNodeRc) + -> Result + { + use SearchResult::*; + let node = node_ref.borrow(); + let item_index = node.item_index; + let item = &node.item; + let mut expanded = expanded.iter_items(); + let mut cap = self.capture.borrow_mut(); + let search_result = cap.find_child(&mut expanded, range, to_index)?; + Ok(match search_result { + TopLevelItem(found_index, _) => { + let range_to_item = range.start..found_index; + cap.count_within(item_index, item, &range_to_item)? + }, + NextLevelItem(span_index, .., child) => { + let range_to_span = range.start..span_index; + cap.count_within(item_index, item, &range_to_span)? + + cap.count_before(item_index, item, span_index, &child)? + }, + }) + } + + fn count_in_range(&self, + range: &Range, + node_ref: &ItemNodeRc) + -> Result + { + let mut cap = self.capture.borrow_mut(); + let node = node_ref.borrow(); + cap.count_within(node.item_index, &node.item, range) + } + + fn count_around_offset(&self, + expanded: &ItemNodeSet, + range: &Range, + node_ref: &ItemNodeRc, + offset: u64, + end: u64) + -> Result<(u64, u64), Error> + { + let length = + self.count_items(range)? + self.count_within(expanded, range)?; + let rows_before_offset = if offset == 0 { + 0 + } else { + self.count_to_item(expanded, range, offset - 1, node_ref)? + }; + let rows_before_end = if end == length { + self.count_in_range(range, node_ref)? + } else { + self.count_to_item(expanded, range, end, node_ref)? + }; + let rows_after_offset = rows_before_end - rows_before_offset; + Ok((rows_before_offset, rows_after_offset)) + } + + fn count_within(&self, + expanded: &ItemNodeSet, + range: &Range) + -> Result + { + let mut cap = self.capture.borrow_mut(); + Ok(expanded + .iter_items() + .map(|(index, item)| cap.count_within(index, &item, range)) + .collect::, Error>>()? + .iter() + .sum()) + } + pub fn update(&self, model: &Model) -> Result { #[cfg(feature="debug-region-map")] let rows_before = self.row_count(); @@ -738,10 +1699,13 @@ where Item: 'static + Clone + Debug, node_rc: &Rc>, mut position: u64, model: &Model) - -> Result + -> Result<(u64, Option>), Error> where T: Node + 'static, Rc>: NodeRcOps, { + use InterleavedUpdate::*; + use UpdateState::*; + // Extract details about the current node. let node = node_rc.borrow(); let expanded = node.expanded(); @@ -756,11 +1720,10 @@ where Item: 'static + Clone + Debug, let mut cap = self.capture.borrow_mut(); let (completion, new_direct_count) = cap.item_children(node.item(), self.view_mode)?; - let completed = completion.is_complete(); let children_added = new_direct_count - old_direct_count; drop(node); - if let Some(item_node_rc) = node_rc.item_node_rc() { + let mut state = if let Some(item_node_rc) = node_rc.item_node_rc() { // This is an item node. let mut item_node = item_node_rc.borrow_mut(); @@ -797,29 +1760,81 @@ where Item: 'static + Clone + Debug, // Advance past this node's own row. position += 1; - } + + // Construct node state for remaining steps. + use CompletionStatus::*; + let children_to_add = if expanded && children_added > 0 { + Some(children_added) + } else { + None + }; + match (item_node.completion, children_to_add) { + (InterleavedComplete(end), Some(count)) => { + drop(item_node); + Interleaved(Some(AddedComplete(item_node_rc, count, end))) + }, + (InterleavedOngoing, Some(count)) => { + drop(item_node); + Interleaved(Some(AddedOngoing(item_node_rc, count))) + }, + (InterleavedComplete(_) | InterleavedOngoing, None) => + Interleaved(None), + (..) => + Contiguous() + } + } else { + Root(Vec::new()) + }; drop(cap); - // If completed, remove from incomplete node list. - if completed { - node_rc.borrow_mut().set_completed(); - } + // Update node's completion status. + node_rc.borrow_mut().set_completion(completion); if expanded { // Deal with incomplete children of this node. let mut last_index = 0; for (index, child_weak) in incomplete_children { if let Some(child_rc) = child_weak.upgrade() { - // Advance position up to this child. - position += node_rc - .borrow() - .children() - .rows_between(last_index, index); - // Recursively update this child. - position = self.update_node::>( - &child_rc, position, model)?; - last_index = index + 1; + // Advance position to this child. + position = match state { + Root(..) => + // Find position of top level item from region map. + self.find_top_level_item(&child_rc)?, + Interleaved(..) => { + // There can be only one incomplete child of an + // item whose children are interleaved. If it is + // expanded, we can find it from the region map. + // Otherwise, it must always be at the last row. + let end = self.row_count(); + if child_rc.borrow().expanded() { + self.find_expanded(position..end, &child_rc)? + } else { + end - 1 + } + }, + Contiguous(..) => { + // If the children of this node are contiguous, we + // can advance to another child by summing the + // total rows of the intermediate children. + let delta = node_rc + .borrow() + .children() + .rows_between(last_index, index); + last_index = index + 1; + position + delta + }, + }; + // Recursively update the child. + let (new_position, update) = + self.update_node::>( + &child_rc, position, model)?; + // If the child has a pending interleaved update, store it. + if let (Root(todo), Some(update)) = (&mut state, update) { + todo.push(update); + } + // Advance position to after this child and its children. + position = new_position; } else { // Child no longer referenced, remove it. node_rc @@ -830,26 +1845,244 @@ where Item: 'static + Clone + Debug, } } - // Advance to the end of this node's existing children. - position += node_rc - .borrow_mut() - .children_mut() - .rows_between(last_index, old_direct_count); + if matches!(state, Contiguous()) { + // Advance to the end of this node's existing children. + position += node_rc + .borrow_mut() + .children_mut() + .rows_between(last_index, old_direct_count); + } } // Now deal with any new children of this node. - if children_added > 0 { - // Update this node's child counts. + if let Root(interleaved_updates) = &state { + // Updating the root node. New rows are added after all others. + let initial_position = self.row_count(); + position = initial_position; + + // Running totals of rows pending and added. + let mut top_level_added = 0; + let mut top_level_pending = children_added; + let mut second_level_added = 0; + let mut second_level_pending = 0; + + // Collect completed items, and count pending second level items. + let mut completed = BTreeMap::new(); + for update in interleaved_updates { + match update { + AddedComplete(child_rc, children_added, end) => { + completed.insert(*end, child_rc); + second_level_pending += children_added; + }, + AddedOngoing(_child_rc, children_added) => { + second_level_pending += children_added; + } + } + } + + // Handle completed items. + let mut completed = completed.into_iter(); + if let Some((child_end, child_rc)) = completed.next() { + + // Find the last interleaved region. + let (mut expanded, range, old_length) = self.regions + .borrow_mut() + .values_mut() + .rev() + .find_map(|region| + if let InterleavedSearch(expanded, range) = + &mut region.source + { + // Copy all the fields. + let fields = ( + expanded.clone(), + range.clone(), + region.offset + region.length, + ); + // Extend the search range to the new end. + range.end = child_end; + // Return the copied fields. + Some(fields) + } else { + None + } + ) + .context("No interleaved region found")?; + + // Add a new region with the additional rows. + let full_range = range.start..child_end; + let old_top = self.count_items(&range)?; + let full_top = self.count_items(&full_range)?; + let full_second = self.count_within(&expanded, &full_range)?; + let full_length = full_top + full_second; + let new_length = full_length - old_length; + let new_top = full_top - old_top; + let new_second = new_length - new_top; + let new_region = Region { + source: InterleavedSearch(expanded.clone(), full_range), + offset: old_length, + length: new_length, + }; + self.insert_region(position, new_region)?; + position += new_length; + top_level_added += new_top; + top_level_pending -= new_top; + second_level_added += new_second; + second_level_pending -= new_second; + + // Update for next child completion. + let mut last_end = child_end; + expanded = expanded.without(child_rc); + + // Now handle any further completed children. + for (child_end, child_rc) in completed { + // Add a region for the in-between top level item. + self.insert_region(position, Region { + source: TopLevelItems(), + offset: last_end, + length: 1, + })?; + position += 1; + top_level_added += 1; + top_level_pending -= 1; + + // Add a new interleaved region. + let range = last_end..child_end; + let new_top = self.count_items(&range)?; + let new_second = self.count_within(&expanded, &range)?; + let new_length = new_top + new_second; + self.insert_region(position, Region { + source: InterleavedSearch( + expanded.clone(), range.clone()), + offset: 0, + length: new_length, + })?; + position += new_length; + top_level_added += new_top; + top_level_pending -= new_top; + second_level_added += new_second; + second_level_pending -= new_second; + + // Update for next completion. + last_end = child_end; + expanded = expanded.without(child_rc); + } + } + + // Add any further pending items at the end. + if top_level_pending > 0 || second_level_pending > 0 { + // Find the source and offset needed to extend the last region + // that contains top level items, or default to a new top level + // source if the map is empty. + let (source, offset, old_end, old_length) = self.regions + .borrow_mut() + .values_mut() + .rev() + .find_map(|region| { + let old_length = region.offset + region.length; + Some(match &mut region.source { + TopLevelItems() if second_level_pending == 0 => { + let source = region.source.clone(); + let old_end = region.offset + region.length; + let offset = old_end; + (source, offset, old_end, old_length) + }, + InterleavedSearch(expanded, range) => { + let old_end = range.end; + if expanded.any_incomplete() { + // Still ongoing. Extend its range + // and continue it with a new region. + range.end += top_level_pending; + let source = InterleavedSearch( + expanded.clone(), range.clone()); + let offset = region.offset + region.length; + (source, offset, old_end, old_length) + } else { + // Region has ended, start a new top + // level region after it. + let source = TopLevelItems(); + let offset = old_end; + (source, offset, old_end, old_length) + } + }, + _ => return None + }) + }) + .unwrap_or((TopLevelItems(), 0, 0, 0)); + + // Add a region with the new rows. + let (new_length, new_top, new_second) = match &source { + InterleavedSearch(expanded, full_range) => { + let old_range = full_range.start..old_end; + let old_top = + self.count_items(&old_range).unwrap_or(0); + let full_top = + self.count_items(full_range)?; + let full_second = + self.count_within(expanded, full_range)?; + let full_length = full_top + full_second; + let new_length = full_length - old_length; + let new_top = full_top - old_top; + let new_second = new_length - new_top; + (new_length, new_top, new_second) + }, + TopLevelItems() => + (top_level_pending, top_level_pending, 0), + _ => + unreachable!() + }; + let region = Region {source, offset, length: new_length}; + self.insert_region(position, region)?; + position += new_length; + top_level_added += new_top; + top_level_pending -= new_top; + second_level_added += new_second; + second_level_pending -= new_second; + + #[cfg(feature="debug-region-map")] { + println!(); + println!("Region map after root node update:"); + for (start, region) in self.regions.borrow().iter() { + println!("{}: {:?}", start, region); + } + } + + // We should now have added all pending rows. + assert!(top_level_pending == 0); + assert!(second_level_pending == 0); + + self.merge_regions(); + + // Update child counts. + let mut root = self.root.borrow_mut(); + root.children.direct_count += top_level_added; + root.children.total_count += + top_level_added + second_level_added; + drop(root); + + // Apply a single update to cover all the regions added. + self.apply_update(model, initial_position, ModelUpdate { + rows_added: top_level_added + second_level_added, + rows_removed: 0, + rows_changed: 0 + }); + } + } else if children_added > 0 { + // This is an item node. Update child counts. let mut node = node_rc.borrow_mut(); let children = node.children_mut(); children.direct_count += children_added; children.total_count += children_added; drop(node); - if expanded { - #[cfg(feature="debug-region-map")] - println!("\nAdding {} new children at {}", - children_added, position); + let interleaved = matches!(state, Interleaved(..)); + + if expanded && !interleaved { + #[cfg(feature="debug-region-map")] { + println!(); + println!("Adding {} new children at {}", + children_added, position); + } // Move the following regions down to make space. let following_regions = self.regions @@ -885,8 +2118,12 @@ where Item: 'static + Clone + Debug, } } - // Return the position after all of this node's rows. - Ok(position) + // Return the position after all of this node's rows, and any pending + // interleaved update to do for this node. + Ok(match state { + Interleaved(update) => (position, update), + _ => (position, None) + }) } fn fetch(&self, position: u64) -> Result, Error> { @@ -899,13 +2136,50 @@ where Item: 'static + Clone + Debug, .with_context(|| format!( "No region before position {position}"))?; - // Get the index of this row relative to the start of that region. - let relative_position = region.offset + (position - start); + // Get the index of this row relative to the start of the region source. + let row_index = region.offset + (position - start); // Get the parent for this row, according to the type of region. - let parent_ref: AnyNodeRc = match region.source { - TopLevelItems() => self.root.clone(), - ChildrenOf(node_ref) => node_ref, + let mut cap = self.capture.borrow_mut(); + let (parent_ref, item_index, item): + (AnyNodeRc, u64, Item) = + match region.source + { + TopLevelItems() => ( + self.root.clone(), + row_index, + cap.item(None, self.view_mode, row_index)?), + ChildrenOf(node_ref) => ( + node_ref.clone(), + row_index, + cap.item( + Some(&node_ref.borrow().item), + self.view_mode, + row_index)?), + InterleavedSearch(expanded, range) => { + // Run the interleaved search. + let mut expanded_items = expanded.iter_items(); + let search_result = cap.find_child( + &mut expanded_items, &range, row_index)?; + // Return a node corresponding to the search result. + use SearchResult::*; + match search_result { + // Search found a top level item. + TopLevelItem(index, item) => { + (self.root.clone(), index, item) + }, + // Search found a child of an expanded top level item. + NextLevelItem(_, parent_index, child_index, item) => { + // There must already be a node for its parent. + let parent_ref = self.root + .borrow() + .children() + .get_expanded(parent_index) + .context("Parent dropped")?; + (parent_ref, child_index, item) + } + } + } }; // Check if we already have a node for this item in the parent's @@ -914,7 +2188,7 @@ where Item: 'static + Clone + Debug, .borrow() .children() .expanded - .get(&relative_position) + .get(&item_index) { return Ok(node_rc.clone()) } @@ -923,30 +2197,28 @@ where Item: 'static + Clone + Debug, if let Some(node_rc) = parent_ref .borrow() .children() - .fetch_incomplete(relative_position) + .fetch_incomplete(item_index) { return Ok(node_rc) } // Otherwise, fetch it from the database. - let mut cap = self.capture.borrow_mut(); - let mut parent = parent_ref.borrow_mut(); - let item = - cap.item(parent.item(), self.view_mode, relative_position)?; let (completion, child_count) = cap.item_children(Some(&item), self.view_mode)?; let node = ItemNode { item, parent: Rc::downgrade(&parent_ref), - item_index: relative_position, + item_index, + completion, children: Children::new(child_count), widgets: RefCell::new(HashSet::new()), }; let node_rc = Rc::new(RefCell::new(node)); if !completion.is_complete() { - parent + parent_ref + .borrow_mut() .children_mut() - .add_incomplete(relative_position, &node_rc); + .add_incomplete(item_index, &node_rc); } Ok(node_rc) }