Compare commits

...

1 commit
main ... marks2

Author SHA1 Message Date
Orion Henry
06f0b201c9 Revert "remove marks"
This reverts commit c8c695618b.
2022-02-10 12:04:08 -05:00
13 changed files with 658 additions and 52 deletions

View file

@ -101,6 +101,11 @@ export class Automerge {
text(obj: ObjID, heads?: Heads): string;
length(obj: ObjID, heads?: Heads): number;
// experimental spans api - unstable!
mark(obj: ObjID, name: string, range: string, value: Value, datatype?: Datatype): void;
spans(obj: ObjID): any;
raw_spans(obj: ObjID): any;
// transactions
commit(message?: string, time?: number): Heads;
merge(other: Automerge): Heads;

View file

@ -2,6 +2,7 @@
use automerge as am;
use automerge::{Change, ObjId, Prop, Value, ROOT};
use js_sys::{Array, Object, Uint8Array};
use regex::Regex;
use std::convert::TryInto;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
@ -307,6 +308,78 @@ impl Automerge {
Ok(())
}
pub fn mark(
&mut self,
obj: String,
range: JsValue,
name: JsValue,
value: JsValue,
datatype: JsValue,
) -> Result<(), JsValue> {
let obj = self.import(obj)?;
let re = Regex::new(r"([\[\(])(\d+)\.\.(\d+)([\)\]])").unwrap();
let range = range.as_string().ok_or("range must be a string")?;
let cap = re.captures_iter(&range).next().ok_or("range must be in the form of (start..end] or [start..end) etc... () for sticky, [] for normal")?;
let start: usize = cap[2].parse().map_err(|_| to_js_err("invalid start"))?;
let end: usize = cap[3].parse().map_err(|_| to_js_err("invalid end"))?;
let start_sticky = &cap[1] == "(";
let end_sticky = &cap[4] == ")";
let name = name
.as_string()
.ok_or("invalid mark name")
.map_err(to_js_err)?;
let value = self
.import_scalar(&value, &datatype.as_string())
.ok_or_else(|| to_js_err("invalid value"))?;
self.0
.mark(&obj, start, start_sticky, end, end_sticky, &name, value)
.map_err(to_js_err)?;
Ok(())
}
pub fn spans(&mut self, obj: String) -> Result<JsValue, JsValue> {
let obj = self.import(obj)?;
let text = self.0.text(&obj).map_err(to_js_err)?;
let spans = self.0.spans(&obj).map_err(to_js_err)?;
let mut last_pos = 0;
let result = Array::new();
for s in spans {
let marks = Array::new();
for m in s.marks {
let mark = Array::new();
mark.push(&m.0.into());
mark.push(&datatype(&m.1).into());
mark.push(&ScalarValue(m.1).into());
marks.push(&mark.into());
}
let text_span = &text[last_pos..s.pos]; //.slice(last_pos, s.pos);
if !text_span.is_empty() {
result.push(&text_span.into());
}
result.push(&marks);
last_pos = s.pos;
//let obj = Object::new().into();
//js_set(&obj, "pos", s.pos as i32)?;
//js_set(&obj, "marks", marks)?;
//result.push(&obj.into());
}
let text_span = &text[last_pos..];
if !text_span.is_empty() {
result.push(&text_span.into());
}
Ok(result.into())
}
pub fn raw_spans(&mut self, obj: String) -> Result<Array, JsValue> {
let obj = self.import(obj)?;
let spans = self.0.raw_spans(&obj).map_err(to_js_err)?;
let result = Array::new();
for s in spans {
result.push(&JsValue::from_serde(&s).map_err(to_js_err)?);
}
Ok(result)
}
pub fn save(&mut self) -> Result<Uint8Array, JsValue> {
self.0
.save()

View file

@ -0,0 +1,138 @@
import { describe, it } from 'mocha';
//@ts-ignore
import assert from 'assert'
//@ts-ignore
import { create, loadDoc, Automerge, TEXT, encodeChange, decodeChange } from '../dev/index'
describe('Automerge', () => {
describe('marks', () => {
it('should handle marks [..]', () => {
let doc = create()
let list = doc.set("_root", "list", TEXT)
if (!list) throw new Error('should not be undefined')
doc.splice(list, 0, 0, "aaabbbccc")
doc.mark(list, "[3..6]", "bold" , true)
let spans = doc.spans(list);
assert.deepStrictEqual(spans, [ 'aaa', [ [ 'bold', 'boolean', true ] ], 'bbb', [], 'ccc' ]);
doc.insert(list, 6, "A")
doc.insert(list, 3, "A")
spans = doc.spans(list);
assert.deepStrictEqual(spans, [ 'aaaA', [ [ 'bold', 'boolean', true ] ], 'bbb', [], 'Accc' ]);
})
it('should handle marks with deleted ends [..]', () => {
let doc = create()
let list = doc.set("_root", "list", TEXT)
if (!list) throw new Error('should not be undefined')
doc.splice(list, 0, 0, "aaabbbccc")
doc.mark(list, "[3..6]", "bold" , true)
let spans = doc.spans(list);
assert.deepStrictEqual(spans, [ 'aaa', [ [ 'bold', 'boolean', true ] ], 'bbb', [], 'ccc' ]);
doc.del(list,5);
doc.del(list,5);
doc.del(list,2);
doc.del(list,2);
spans = doc.spans(list);
assert.deepStrictEqual(spans, [ 'aa', [ [ 'bold', 'boolean', true ] ], 'b', [], 'cc' ])
doc.insert(list, 3, "A")
doc.insert(list, 2, "A")
spans = doc.spans(list);
assert.deepStrictEqual(spans, [ 'aaA', [ [ 'bold', 'boolean', true ] ], 'b', [], 'Acc' ])
})
it('should handle sticky marks (..)', () => {
let doc = create()
let list = doc.set("_root", "list", TEXT)
if (!list) throw new Error('should not be undefined')
doc.splice(list, 0, 0, "aaabbbccc")
doc.mark(list, "(3..6)", "bold" , true)
let spans = doc.spans(list);
assert.deepStrictEqual(spans, [ 'aaa', [ [ 'bold', 'boolean', true ] ], 'bbb', [], 'ccc' ]);
doc.insert(list, 6, "A")
doc.insert(list, 3, "A")
spans = doc.spans(list);
assert.deepStrictEqual(spans, [ 'aaa', [ [ 'bold', 'boolean', true ] ], 'AbbbA', [], 'ccc' ]);
})
it('should handle sticky marks with deleted ends (..)', () => {
let doc = create()
let list = doc.set("_root", "list", TEXT)
if (!list) throw new Error('should not be undefined')
doc.splice(list, 0, 0, "aaabbbccc")
doc.mark(list, "(3..6)", "bold" , true)
let spans = doc.spans(list);
assert.deepStrictEqual(spans, [ 'aaa', [ [ 'bold', 'boolean', true ] ], 'bbb', [], 'ccc' ]);
doc.del(list,5);
doc.del(list,5);
doc.del(list,2);
doc.del(list,2);
spans = doc.spans(list);
assert.deepStrictEqual(spans, [ 'aa', [ [ 'bold', 'boolean', true ] ], 'b', [], 'cc' ])
doc.insert(list, 3, "A")
doc.insert(list, 2, "A")
spans = doc.spans(list);
assert.deepStrictEqual(spans, [ 'aa', [ [ 'bold', 'boolean', true ] ], 'AbA', [], 'cc' ])
// make sure save/load can handle marks
let doc2 = loadDoc(doc.save())
spans = doc2.spans(list);
assert.deepStrictEqual(spans, [ 'aa', [ [ 'bold', 'boolean', true ] ], 'AbA', [], 'cc' ])
assert.deepStrictEqual(doc.getHeads(), doc2.getHeads())
assert.deepStrictEqual(doc.save(), doc2.save())
})
it('should handle overlapping marks', () => {
let doc : Automerge = create("aabbcc")
let list = doc.set("_root", "list", TEXT)
if (!list) throw new Error('should not be undefined')
doc.splice(list, 0, 0, "the quick fox jumps over the lazy dog")
doc.mark(list, "[0..37]", "bold" , true)
doc.mark(list, "[4..19]", "itallic" , true)
doc.mark(list, "[10..13]", "comment" , "foxes are my favorite animal!")
doc.commit("marks",999);
let spans = doc.spans(list);
assert.deepStrictEqual(spans,
[
[ [ 'bold', 'boolean', true ] ],
'the ',
[ [ 'bold', 'boolean', true ], [ 'itallic', 'boolean', true ] ],
'quick ',
[
[ 'bold', 'boolean', true ],
[ 'comment', 'str', 'foxes are my favorite animal!' ],
[ 'itallic', 'boolean', true ]
],
'fox',
[ [ 'bold', 'boolean', true ], [ 'itallic', 'boolean', true ] ],
' jumps',
[ [ 'bold', 'boolean', true ] ],
' over the lazy dog',
[],
]
)
let text = doc.text(list);
assert.deepStrictEqual(text, "the quick fox jumps over the lazy dog");
let raw_spans = doc.raw_spans(list);
assert.deepStrictEqual(raw_spans,
[
{ id: "39@aabbcc", time: 999, start: 0, end: 37, type: 'bold', value: true },
{ id: "41@aabbcc", time: 999, start: 4, end: 19, type: 'itallic', value: true },
{ id: "43@aabbcc", time: 999, start: 10, end: 13, type: 'comment', value: 'foxes are my favorite animal!' }
]);
// mark sure encode decode can handle marks
let all = doc.getChanges([])
let decoded = all.map((c) => decodeChange(c))
let encoded = decoded.map((c) => encodeChange(c))
let doc2 = create();
doc2.applyChanges(encoded)
assert.deepStrictEqual(doc.spans(list) , doc2.spans(list))
assert.deepStrictEqual(doc.save(), doc2.save())
})
})
})

View file

@ -453,6 +453,90 @@ impl Automerge {
Ok(buffer)
}
pub fn spans(&self, obj: &ExId) -> Result<Vec<query::Span>, AutomergeError> {
let obj = self.exid_to_obj(obj)?;
let mut query = self.ops.search(obj, query::Spans::new());
query.check_marks();
Ok(query.spans)
}
pub fn raw_spans(&self, obj: &ExId) -> Result<Vec<SpanInfo>, AutomergeError> {
let obj = self.exid_to_obj(obj)?;
let query = self.ops.search(obj, query::RawSpans::new());
let result = query.spans.into_iter().map(|s| SpanInfo {
id: self.id_to_exid(s.id),
time: self.history[s.change].time,
start: s.start,
end: s.end,
span_type: s.name,
value: s.value,
}).collect();
Ok(result)
}
#[allow(clippy::too_many_arguments)]
pub fn mark(
&mut self,
obj: &ExId,
start: usize,
expand_start: bool,
end: usize,
expand_end: bool,
mark: &str,
value: ScalarValue,
) -> Result<(), AutomergeError> {
let obj = self.exid_to_obj(obj)?;
self.do_insert(obj, start, OpType::mark(mark.into(), expand_start, value))?;
self.do_insert(obj, end, OpType::MarkEnd(expand_end))?;
/*
let (a, b) = query.ops()?;
let (pos, key) = a;
let id = self.next_id();
let op = Op {
change: self.history.len(),
id,
action: OpType::Mark(MarkData { name: mark.into(), expand: expand_start, value}),
obj,
key,
succ: Default::default(),
pred: Default::default(),
insert: true,
};
self.ops.insert(pos, op.clone());
self.tx().operations.push(op);
let (pos, key) = b;
let id = self.next_id();
let op = Op {
change: self.history.len(),
id,
action: OpType::Unmark(expand_end),
obj,
key,
succ: Default::default(),
pred: Default::default(),
insert: true,
};
self.ops.insert(pos, op.clone());
self.tx().operations.push(op);
*/
Ok(())
}
pub fn unmark(
&self,
_obj: &ExId,
_start: usize,
_end: usize,
_inclusive: bool,
_mark: &str,
) -> Result<String, AutomergeError> {
unimplemented!()
}
// TODO - I need to return these OpId's here **only** to get
// the legacy conflicts format of { [opid]: value }
// Something better?
@ -1114,6 +1198,8 @@ impl Automerge {
OpType::Set(value) => format!("{}", value),
OpType::Make(obj) => format!("make({})", obj),
OpType::Inc(obj) => format!("inc({})", obj),
OpType::MarkBegin(m) => format!("mark({}={})", m.name, m.value),
OpType::MarkEnd(_) => "/mark".into(),
OpType::Del => format!("del{}", 0),
};
let pred: Vec<_> = i.pred.iter().map(|id| self.to_string(*id)).collect();
@ -1384,8 +1470,6 @@ mod tests {
assert!(doc.value_at(&list, 0, &heads2)?.unwrap().0 == Value::int(10));
assert!(doc.length_at(&list, &heads3) == 2);
doc.dump();
//log!("{:?}", doc.value_at(&list, 0, &heads3)?.unwrap().0);
assert!(doc.value_at(&list, 0, &heads3)?.unwrap().0 == Value::int(30));
assert!(doc.value_at(&list, 1, &heads3)?.unwrap().0 == Value::int(20));

View file

@ -134,6 +134,15 @@ impl<'a> Iterator for OperationIterator<'a> {
Action::MakeTable => OpType::Make(ObjType::Table),
Action::Del => OpType::Del,
Action::Inc => OpType::Inc(value.to_i64()?),
Action::MarkBegin => {
// mark has 3 things in the val column
let name = value.to_string()?;
let expand = self.value.next()?.to_bool()?;
let value = self.value.next()?;
OpType::mark(name, expand, value)
}
Action::MarkEnd => OpType::MarkEnd(value.to_bool()?),
Action::Unused => panic!("invalid action"),
};
Some(amp::Op {
action,
@ -175,6 +184,15 @@ impl<'a> Iterator for DocOpIterator<'a> {
Action::MakeTable => OpType::Make(ObjType::Table),
Action::Del => OpType::Del,
Action::Inc => OpType::Inc(value.to_i64()?),
Action::MarkBegin => {
// mark has 3 things in the val column
let name = value.to_string()?;
let expand = self.value.next()?.to_bool()?;
let value = self.value.next()?;
OpType::mark(name, expand, value)
}
Action::MarkEnd => OpType::MarkEnd(value.to_bool()?),
Action::Unused => panic!("invalid action"),
};
Some(DocOp {
actor,
@ -1064,6 +1082,16 @@ impl DocOpEncoder {
self.val.append_null();
Action::Del
}
amp::OpType::MarkBegin(m) => {
self.val.append_value(&m.name.clone().into(), actors);
self.val.append_value(&m.expand.into(), actors);
self.val.append_value(&m.value.clone(), actors);
Action::MarkBegin
}
amp::OpType::MarkEnd(s) => {
self.val.append_value(&(*s).into(), actors);
Action::MarkEnd
}
amp::OpType::Make(kind) => {
self.val.append_null();
match kind {
@ -1170,6 +1198,16 @@ impl ColumnEncoder {
self.val.append_null();
Action::Del
}
OpType::MarkBegin(m) => {
self.val.append_value2(&m.name.clone().into(), actors);
self.val.append_value2(&m.expand.into(), actors);
self.val.append_value2(&m.value.clone(), actors);
Action::MarkBegin
}
OpType::MarkEnd(s) => {
self.val.append_value2(&(*s).into(), actors);
Action::MarkEnd
}
OpType::Make(kind) => {
self.val.append_null();
match kind {
@ -1275,8 +1313,11 @@ pub(crate) enum Action {
MakeText,
Inc,
MakeTable,
MarkBegin,
Unused, // final bit is used to mask `Make` actions
MarkEnd,
}
const ACTIONS: [Action; 7] = [
const ACTIONS: [Action; 10] = [
Action::MakeMap,
Action::Set,
Action::MakeList,
@ -1284,6 +1325,9 @@ const ACTIONS: [Action; 7] = [
Action::MakeText,
Action::Inc,
Action::MakeTable,
Action::MarkBegin,
Action::Unused,
Action::MarkEnd,
];
impl Decodable for Action {

View file

@ -49,6 +49,12 @@ impl Serialize for Op {
match &self.action {
OpType::Inc(n) => op.serialize_field("value", &n)?,
OpType::Set(value) => op.serialize_field("value", &value)?,
OpType::MarkBegin(m) => {
op.serialize_field("name", &m.name)?;
op.serialize_field("expand", &m.expand)?;
op.serialize_field("value", &m.value)?;
}
OpType::MarkEnd(s) => op.serialize_field("expand", &s)?,
_ => {}
}
op.serialize_field("pred", &self.pred)?;
@ -70,6 +76,8 @@ pub(crate) enum RawOpType {
Del,
Inc,
Set,
MarkBegin,
MarkEnd,
}
impl Serialize for RawOpType {
@ -85,6 +93,8 @@ impl Serialize for RawOpType {
RawOpType::Del => "del",
RawOpType::Inc => "inc",
RawOpType::Set => "set",
RawOpType::MarkBegin => "mark_begin",
RawOpType::MarkEnd => "mark_end",
};
serializer.serialize_str(s)
}
@ -116,6 +126,8 @@ impl<'de> Deserialize<'de> for RawOpType {
"del" => Ok(RawOpType::Del),
"inc" => Ok(RawOpType::Inc),
"set" => Ok(RawOpType::Set),
"mark_begin" => Ok(RawOpType::MarkBegin),
"mark_end" => Ok(RawOpType::MarkEnd),
other => Err(Error::unknown_variant(other, VARIANTS)),
}
}
@ -188,6 +200,30 @@ impl<'de> Deserialize<'de> for Op {
RawOpType::MakeList => OpType::Make(ObjType::List),
RawOpType::MakeText => OpType::Make(ObjType::Text),
RawOpType::Del => OpType::Del,
RawOpType::MarkBegin => {
let name = name.ok_or_else(|| Error::missing_field("mark(name)"))?;
let expand = expand.unwrap_or(false);
let value = if let Some(datatype) = datatype {
let raw_value = value
.ok_or_else(|| Error::missing_field("value"))?
.unwrap_or(ScalarValue::Null);
raw_value.as_datatype(datatype).map_err(|e| {
Error::invalid_value(
Unexpected::Other(e.unexpected.as_str()),
&e.expected.as_str(),
)
})?
} else {
value
.ok_or_else(|| Error::missing_field("value"))?
.unwrap_or(ScalarValue::Null)
};
OpType::mark(name, expand, value)
}
RawOpType::MarkEnd => {
let expand = expand.unwrap_or(true);
OpType::MarkEnd(expand)
}
RawOpType::Set => {
let value = if let Some(datatype) = datatype {
let raw_value = value

View file

@ -15,6 +15,8 @@ impl Serialize for OpType {
OpType::Make(ObjType::Table) => RawOpType::MakeTable,
OpType::Make(ObjType::List) => RawOpType::MakeList,
OpType::Make(ObjType::Text) => RawOpType::MakeText,
OpType::MarkBegin(_) => RawOpType::MarkBegin,
OpType::MarkEnd(_) => RawOpType::MarkEnd,
OpType::Del => RawOpType::Del,
OpType::Inc(_) => RawOpType::Inc,
OpType::Set(_) => RawOpType::Set,

View file

@ -17,6 +17,8 @@ mod nth_at;
mod prop;
mod prop_at;
mod seek_op;
mod spans;
mod raw_spans;
pub(crate) use insert::InsertNth;
pub(crate) use keys::Keys;
@ -30,6 +32,8 @@ pub(crate) use nth_at::NthAt;
pub(crate) use prop::Prop;
pub(crate) use prop_at::PropAt;
pub(crate) use seek_op::SeekOp;
pub(crate) use spans::{Span, Spans};
pub(crate) use raw_spans::RawSpans;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct CounterData {

View file

@ -85,6 +85,10 @@ impl<const B: usize> TreeQuery<B> for InsertNth<B> {
self.last_seen = None;
self.last_insert = element.elemid();
}
if self.valid.is_some() && element.valid_mark_anchor() {
self.last_valid_insert = element.elemid();
self.valid = None;
}
if self.last_seen.is_none() && element.visible() {
if self.seen >= self.target {
return QueryResult::Finish;

View file

@ -0,0 +1,71 @@
use crate::query::{OpSetMetadata, QueryResult, TreeQuery};
use crate::types::{ElemId, Op, OpId, OpType, ScalarValue};
use std::fmt::Debug;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct RawSpans<const B: usize> {
pos: usize,
seen: usize,
last_seen: Option<ElemId>,
last_insert: Option<ElemId>,
changed: bool,
pub spans: Vec<RawSpan>,
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct RawSpan {
pub id: OpId,
pub change: usize,
pub start: usize,
pub end: usize,
pub name: String,
pub value: ScalarValue,
}
impl<const B: usize> RawSpans<B> {
pub fn new() -> Self {
RawSpans {
pos: 0,
seen: 0,
last_seen: None,
last_insert: None,
changed: false,
spans: Vec::new(),
}
}
}
impl<const B: usize> TreeQuery<B> for RawSpans<B> {
fn query_element_with_metadata(&mut self, element: &Op, m: &OpSetMetadata) -> QueryResult {
// find location to insert
// mark or set
if element.succ.is_empty() {
if let OpType::MarkBegin(md) = &element.action {
let pos = self
.spans
.binary_search_by(|probe| m.lamport_cmp(probe.id, element.id))
.unwrap_err();
self.spans.insert(pos, RawSpan { id: element.id, change: element.change, start: self.seen, end: 0, name: md.name.clone(), value: md.value.clone() });
}
if let OpType::MarkEnd(_) = &element.action {
for s in self.spans.iter_mut() {
if s.id == element.id.prev() {
s.end = self.seen;
break;
}
}
}
}
if element.insert {
self.last_seen = None;
self.last_insert = element.elemid();
}
if self.last_seen.is_none() && element.visible() {
self.seen += 1;
self.last_seen = element.elemid();
}
self.pos += 1;
QueryResult::Next
}
}

View file

@ -0,0 +1,108 @@
use crate::query::{OpSetMetadata, QueryResult, TreeQuery};
use crate::types::{ElemId, Op, OpType, ScalarValue};
use std::collections::HashMap;
use std::fmt::Debug;
#[derive(Debug, Clone, PartialEq)]
pub(crate) struct Spans<const B: usize> {
pos: usize,
seen: usize,
last_seen: Option<ElemId>,
last_insert: Option<ElemId>,
seen_at_this_mark: Option<ElemId>,
seen_at_last_mark: Option<ElemId>,
ops: Vec<Op>,
marks: HashMap<String, ScalarValue>,
changed: bool,
pub spans: Vec<Span>,
}
#[derive(Debug, Clone, PartialEq)]
pub struct Span {
pub pos: usize,
pub marks: Vec<(String, ScalarValue)>,
}
impl<const B: usize> Spans<B> {
pub fn new() -> Self {
Spans {
pos: 0,
seen: 0,
last_seen: None,
last_insert: None,
seen_at_last_mark: None,
seen_at_this_mark: None,
changed: false,
ops: Vec::new(),
marks: HashMap::new(),
spans: Vec::new(),
}
}
pub fn check_marks(&mut self) {
let mut new_marks = HashMap::new();
for op in &self.ops {
if let OpType::MarkBegin(m) = &op.action {
new_marks.insert(m.name.clone(), m.value.clone());
}
}
if new_marks != self.marks {
self.changed = true;
self.marks = new_marks;
}
if self.changed
&& (self.seen_at_last_mark != self.seen_at_this_mark
|| self.seen_at_last_mark.is_none() && self.seen_at_this_mark.is_none())
{
self.changed = false;
self.seen_at_last_mark = self.seen_at_this_mark;
let mut marks: Vec<_> = self
.marks
.iter()
.map(|(key, val)| (key.clone(), val.clone()))
.collect();
marks.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
self.spans.push(Span {
pos: self.seen,
marks,
});
}
}
}
impl<const B: usize> TreeQuery<B> for Spans<B> {
/*
fn query_node(&mut self, _child: &OpTreeNode<B>) -> QueryResult {
unimplemented!()
}
*/
fn query_element_with_metadata(&mut self, element: &Op, m: &OpSetMetadata) -> QueryResult {
// find location to insert
// mark or set
if element.succ.is_empty() {
if let OpType::MarkBegin(_) = &element.action {
let pos = self
.ops
.binary_search_by(|probe| m.lamport_cmp(probe.id, element.id))
.unwrap_err();
self.ops.insert(pos, element.clone());
}
if let OpType::MarkEnd(_) = &element.action {
self.ops.retain(|op| op.id != element.id.prev());
}
}
if element.insert {
self.last_seen = None;
self.last_insert = element.elemid();
}
if self.last_seen.is_none() && element.visible() {
self.check_marks();
self.seen += 1;
self.last_seen = element.elemid();
self.seen_at_this_mark = element.elemid();
}
self.pos += 1;
QueryResult::Next
}
}

View file

@ -158,6 +158,25 @@ pub enum OpType {
Del,
Inc(i64),
Set(ScalarValue),
MarkBegin(MarkData),
MarkEnd(bool),
}
impl OpType {
pub(crate) fn mark(name: String, expand: bool, value: ScalarValue) -> Self {
OpType::MarkBegin(MarkData {
name,
expand,
value,
})
}
}
#[derive(PartialEq, Debug, Clone)]
pub struct MarkData {
pub name: String,
pub value: ScalarValue,
pub expand: bool,
}
#[derive(Debug)]
@ -180,6 +199,10 @@ impl OpId {
pub fn actor(&self) -> usize {
self.1
}
#[inline]
pub fn prev(&self) -> OpId {
OpId(self.0 - 1, self.1)
}
}
impl Exportable for ObjId {
@ -376,7 +399,7 @@ impl Op {
}
pub fn visible(&self) -> bool {
if self.is_inc() {
if self.is_inc() || self.is_mark() {
false
} else if self.is_counter() {
self.succ.len() <= self.incs()
@ -401,6 +424,18 @@ impl Op {
matches!(&self.action, OpType::Inc(_))
}
pub fn valid_mark_anchor(&self) -> bool {
self.succ.is_empty()
&& matches!(
&self.action,
OpType::MarkBegin(MarkData { expand: true, .. }) | OpType::MarkEnd(false)
)
}
pub fn is_mark(&self) -> bool {
matches!(&self.action, OpType::MarkBegin(_) | OpType::MarkEnd(_))
}
pub fn is_counter(&self) -> bool {
matches!(&self.action, OpType::Set(ScalarValue::Counter(_)))
}
@ -435,6 +470,8 @@ impl Op {
OpType::Set(value) if self.insert => format!("i:{}", value),
OpType::Set(value) => format!("s:{}", value),
OpType::Make(obj) => format!("make{}", obj),
OpType::MarkBegin(m) => format!("mark{}={}", m.name, m.value),
OpType::MarkEnd(_) => "unmark".into(),
OpType::Inc(val) => format!("inc:{}", val),
OpType::Del => "del".to_string(),
}

View file

@ -54,10 +54,10 @@ fn repeated_map_assignment_which_resolves_conflict_not_ignored() {
let mut doc1 = new_doc();
let mut doc2 = new_doc();
doc1.set(&automerge::ROOT, "field", 123).unwrap();
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
doc2.set(&automerge::ROOT, "field", 456).unwrap();
doc1.set(&automerge::ROOT, "field", 789).unwrap();
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
assert_eq!(doc1.values(&automerge::ROOT, "field").unwrap().len(), 2);
doc1.set(&automerge::ROOT, "field", 123).unwrap();
@ -78,9 +78,9 @@ fn repeated_list_assignment_which_resolves_conflict_not_ignored() {
.unwrap()
.unwrap();
doc1.insert(&list_id, 0, 123).unwrap();
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
doc2.set(&list_id, 0, 456).unwrap();
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
doc1.set(&list_id, 0, 789).unwrap();
assert_doc!(
@ -123,7 +123,7 @@ fn merge_concurrent_map_prop_updates() {
let mut doc2 = new_doc();
doc1.set(&automerge::ROOT, "foo", "bar").unwrap();
doc2.set(&automerge::ROOT, "hello", "world").unwrap();
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
assert_eq!(
doc1.value(&automerge::ROOT, "foo").unwrap().unwrap().0,
"bar".into()
@ -135,7 +135,7 @@ fn merge_concurrent_map_prop_updates() {
"hello" => { "world" },
}
);
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
assert_doc!(
&doc2,
map! {
@ -152,10 +152,10 @@ fn add_concurrent_increments_of_same_property() {
let mut doc2 = new_doc();
doc1.set(&automerge::ROOT, "counter", mk_counter(0))
.unwrap();
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
doc1.inc(&automerge::ROOT, "counter", 1).unwrap();
doc2.inc(&automerge::ROOT, "counter", 2).unwrap();
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
assert_doc!(
&doc1,
map! {
@ -181,7 +181,7 @@ fn add_increments_only_to_preceeded_values() {
doc2.inc(&automerge::ROOT, "counter", 3).unwrap();
// The two values should be conflicting rather than added
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
assert_doc!(
&doc1,
@ -201,7 +201,7 @@ fn concurrent_updates_of_same_field() {
doc1.set(&automerge::ROOT, "field", "one").unwrap();
doc2.set(&automerge::ROOT, "field", "two").unwrap();
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
assert_doc!(
&doc1,
@ -223,11 +223,11 @@ fn concurrent_updates_of_same_list_element() {
.unwrap()
.unwrap();
doc1.insert(&list_id, 0, "finch").unwrap();
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
doc1.set(&list_id, 0, "greenfinch").unwrap();
doc2.set(&list_id, 0, "goldfinch").unwrap();
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
assert_doc!(
&doc1,
@ -252,8 +252,8 @@ fn assignment_conflicts_of_different_types() {
.unwrap();
doc3.set(&automerge::ROOT, "field", automerge::Value::map())
.unwrap();
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc3).unwrap();
doc1.merge(&mut doc2);
doc1.merge(&mut doc3);
assert_doc!(
&doc1,
@ -277,7 +277,7 @@ fn changes_within_conflicting_map_field() {
.unwrap()
.unwrap();
doc2.set(&map_id, "innerKey", 42).unwrap();
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
assert_doc!(
&doc1,
@ -304,7 +304,7 @@ fn changes_within_conflicting_list_element() {
.unwrap()
.unwrap();
doc1.insert(&list_id, 0, "hello").unwrap();
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
let map_in_doc1 = doc1
.set(&list_id, 0, automerge::Value::map())
@ -317,11 +317,11 @@ fn changes_within_conflicting_list_element() {
.set(&list_id, 0, automerge::Value::map())
.unwrap()
.unwrap();
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
doc2.set(&map_in_doc2, "map2", true).unwrap();
doc2.set(&map_in_doc2, "key", 2).unwrap();
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
assert_doc!(
&doc1,
@ -361,7 +361,7 @@ fn concurrently_assigned_nested_maps_should_not_merge() {
.unwrap();
doc2.set(&doc2_map_id, "logo_url", "logo.png").unwrap();
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
assert_doc!(
&doc1,
@ -392,11 +392,11 @@ fn concurrent_insertions_at_different_list_positions() {
doc1.insert(&list_id, 0, "one").unwrap();
doc1.insert(&list_id, 1, "three").unwrap();
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
doc1.splice(&list_id, 1, 0, vec!["two".into()]).unwrap();
doc2.insert(&list_id, 2, "four").unwrap();
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
assert_doc!(
&doc1,
@ -426,10 +426,10 @@ fn concurrent_insertions_at_same_list_position() {
.unwrap();
doc1.insert(&list_id, 0, "parakeet").unwrap();
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
doc1.insert(&list_id, 1, "starling").unwrap();
doc2.insert(&list_id, 1, "chaffinch").unwrap();
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
assert_doc!(
&doc1,
@ -456,11 +456,11 @@ fn concurrent_assignment_and_deletion_of_a_map_entry() {
let mut doc1 = new_doc();
let mut doc2 = new_doc();
doc1.set(&automerge::ROOT, "bestBird", "robin").unwrap();
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
doc1.del(&automerge::ROOT, "bestBird").unwrap();
doc2.set(&automerge::ROOT, "bestBird", "magpie").unwrap();
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
assert_doc!(
&doc1,
@ -483,7 +483,7 @@ fn concurrent_assignment_and_deletion_of_list_entry() {
doc1.insert(&list_id, 0, "blackbird").unwrap();
doc1.insert(&list_id, 1, "thrush").unwrap();
doc1.insert(&list_id, 2, "goldfinch").unwrap();
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
doc1.set(&list_id, 1, "starling").unwrap();
doc2.del(&list_id, 1).unwrap();
@ -508,7 +508,7 @@ fn concurrent_assignment_and_deletion_of_list_entry() {
}
);
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
assert_doc!(
&doc1,
@ -535,14 +535,14 @@ fn insertion_after_a_deleted_list_element() {
doc1.insert(&list_id, 1, "thrush").unwrap();
doc1.insert(&list_id, 2, "goldfinch").unwrap();
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
doc1.splice(&list_id, 1, 2, Vec::new()).unwrap();
doc2.splice(&list_id, 2, 0, vec!["starling".into()])
.unwrap();
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
assert_doc!(
&doc1,
@ -554,7 +554,7 @@ fn insertion_after_a_deleted_list_element() {
}
);
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
assert_doc!(
&doc2,
map! {
@ -579,13 +579,13 @@ fn concurrent_deletion_of_same_list_element() {
doc1.insert(&list_id, 1, "buzzard").unwrap();
doc1.insert(&list_id, 2, "cormorant").unwrap();
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
doc1.del(&list_id, 1).unwrap();
doc2.del(&list_id, 1).unwrap();
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
assert_doc!(
&doc1,
@ -597,7 +597,7 @@ fn concurrent_deletion_of_same_list_element() {
}
);
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
assert_doc!(
&doc2,
map! {
@ -631,12 +631,12 @@ fn concurrent_updates_at_different_levels() {
.unwrap();
doc1.insert(&mammals, 0, "badger").unwrap();
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
doc1.set(&birds, "brown", "sparrow").unwrap();
doc2.del(&animals, "birds").unwrap();
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
assert_obj!(
&doc1,
@ -676,13 +676,13 @@ fn concurrent_updates_of_concurrently_deleted_objects() {
.unwrap();
doc1.set(&blackbird, "feathers", "black").unwrap();
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
doc1.del(&birds, "blackbird").unwrap();
doc2.set(&blackbird, "beak", "orange").unwrap();
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
assert_doc!(
&doc1,
@ -704,7 +704,7 @@ fn does_not_interleave_sequence_insertions_at_same_position() {
.set(&automerge::ROOT, "wisdom", automerge::Value::list())
.unwrap()
.unwrap();
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
doc1.splice(
&wisdom,
@ -734,7 +734,7 @@ fn does_not_interleave_sequence_insertions_at_same_position() {
)
.unwrap();
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
assert_doc!(
&doc1,
@ -767,7 +767,7 @@ fn mutliple_insertions_at_same_list_position_with_insertion_by_greater_actor_id(
.unwrap()
.unwrap();
doc1.insert(&list, 0, "two").unwrap();
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
doc2.insert(&list, 0, "one").unwrap();
assert_doc!(
@ -793,7 +793,7 @@ fn mutliple_insertions_at_same_list_position_with_insertion_by_lesser_actor_id()
.unwrap()
.unwrap();
doc1.insert(&list, 0, "two").unwrap();
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
doc2.insert(&list, 0, "one").unwrap();
assert_doc!(
@ -817,11 +817,11 @@ fn insertion_consistent_with_causality() {
.unwrap()
.unwrap();
doc1.insert(&list, 0, "four").unwrap();
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
doc2.insert(&list, 0, "three").unwrap();
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
doc1.insert(&list, 0, "two").unwrap();
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
doc2.insert(&list, 0, "one").unwrap();
assert_doc!(
@ -861,11 +861,11 @@ fn save_restore_complex() {
doc1.set(&first_todo, "done", false).unwrap();
let mut doc2 = new_doc();
doc2.merge(&mut doc1).unwrap();
doc2.merge(&mut doc1);
doc2.set(&first_todo, "title", "weed plants").unwrap();
doc1.set(&first_todo, "title", "kill plants").unwrap();
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc2);
let reloaded = Automerge::load(&doc1.save().unwrap()).unwrap();
@ -918,8 +918,8 @@ fn list_counter_del() -> Result<(), automerge::AutomergeError> {
doc1.inc(&list, 1, 1)?;
doc1.inc(&list, 2, 1)?;
doc1.merge(&mut doc2).unwrap();
doc1.merge(&mut doc3).unwrap();
doc1.merge(&mut doc2);
doc1.merge(&mut doc3);
let values = doc1.values(&list, 1)?;
assert_eq!(values.len(), 3);