Problem: the `OpSet::export_key` method uses `query::ElemIdPos` to determine the index of sequence elements when exporting a key. This query returned `None` for invisible elements. The `Parents` iterator which is used to generate paths to objects in patches in `automerge-wasm` used `export_key`. The end result is that applying a remote change which deletes an object in a sequence would panic as it tries to generate a path for an invisible object. Solution: modify `query::ElemIdPos` to include invisible objects. This does mean that the path generated will refer to the previous visible object in the sequence as it's index, but this is probably fine as for an invisible object the path shouldn't be used anyway. While we're here also change the return value of `OpSet::export_key` to an `Option` and make `query::Index::ops` private as obeisance to the Lady of the Golden Blade.
74 lines
1.9 KiB
Rust
74 lines
1.9 KiB
Rust
use crate::{
|
|
op_tree::OpTreeNode,
|
|
types::{ElemId, ListEncoding, Op, OpId},
|
|
};
|
|
|
|
use super::{QueryResult, TreeQuery};
|
|
|
|
/// Lookup the index in the list that this elemid occupies, includes hidden elements.
|
|
#[derive(Clone, Debug)]
|
|
pub(crate) struct ElemIdPos {
|
|
elem_opid: OpId,
|
|
pos: usize,
|
|
found: bool,
|
|
encoding: ListEncoding,
|
|
}
|
|
|
|
impl ElemIdPos {
|
|
pub(crate) fn new(elemid: ElemId, encoding: ListEncoding) -> Self {
|
|
if elemid.is_head() {
|
|
Self {
|
|
elem_opid: elemid.0,
|
|
pos: 0,
|
|
found: true,
|
|
encoding,
|
|
}
|
|
} else {
|
|
Self {
|
|
elem_opid: elemid.0,
|
|
pos: 0,
|
|
found: false,
|
|
encoding,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn index(&self) -> Option<usize> {
|
|
if self.found {
|
|
Some(self.pos)
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> TreeQuery<'a> for ElemIdPos {
|
|
fn query_node(&mut self, child: &OpTreeNode, _ops: &[Op]) -> QueryResult {
|
|
if self.found {
|
|
return QueryResult::Finish;
|
|
}
|
|
// if index has our element then we can continue
|
|
if child.index.has_op(&self.elem_opid) {
|
|
// element is in this node somewhere
|
|
QueryResult::Descend
|
|
} else {
|
|
// not in this node, try the next one
|
|
self.pos += child.index.visible_len(self.encoding);
|
|
QueryResult::Next
|
|
}
|
|
}
|
|
|
|
fn query_element(&mut self, element: &crate::types::Op) -> QueryResult {
|
|
if self.found {
|
|
return QueryResult::Finish;
|
|
}
|
|
if element.elemid() == Some(ElemId(self.elem_opid)) {
|
|
// this is it
|
|
self.found = true;
|
|
return QueryResult::Finish;
|
|
} else if element.visible() {
|
|
self.pos += element.width(self.encoding);
|
|
}
|
|
QueryResult::Next
|
|
}
|
|
}
|