diff --git a/javascript/src/stable.ts b/javascript/src/stable.ts index ae26812b..e83b127f 100644 --- a/javascript/src/stable.ts +++ b/javascript/src/stable.ts @@ -517,30 +517,6 @@ export function loadIncremental( return progressDocument(doc, heads, opts.patchCallback || state.patchCallback) } -/** - * Create binary save data to be appended to a save file or fed into {@link loadIncremental} - * - * @typeParam T - The type of the value which is contained in the document. - * Note that no validation is done to make sure this type is in - * fact the type of the contained value so be a bit careful - * - * This function is useful for incrementally saving state. The data can be appended to a - * automerge save file, or passed to a document replicating its state. - * - */ -export function saveIncremental(doc: Doc): Uint8Array { - const state = _state(doc) - if (state.heads) { - throw new RangeError( - "Attempting to change an out of date document - set at: " + _trace(doc) - ) - } - if (_is_proxy(doc)) { - throw new RangeError("Calls to Automerge.change cannot be nested") - } - return state.handle.saveIncremental() -} - /** * Export the contents of a document to a compressed format * diff --git a/javascript/src/types.ts b/javascript/src/types.ts index 1166ccea..beb5cf70 100644 --- a/javascript/src/types.ts +++ b/javascript/src/types.ts @@ -4,8 +4,8 @@ export { Counter } from "./counter" export { Int, Uint, Float64 } from "./numbers" import { Counter } from "./counter" -import type { Patch, PatchInfo } from "@automerge/automerge-wasm" -export type { Patch, Mark } from "@automerge/automerge-wasm" +import type { Patch } from "@automerge/automerge-wasm" +export type { Patch } from "@automerge/automerge-wasm" export type AutomergeValue = | ScalarValue @@ -36,9 +36,11 @@ export type Doc = { readonly [P in keyof T]: T[P] } * Callback which is called by various methods in this library to notify the * user of what changes have been made. * @param patch - A description of the changes made - * @param info - An object that has the "before" and "after" document state, and the "from" and "to" heads + * @param before - The document before the change was made + * @param after - The document after the change was made */ export type PatchCallback = ( patches: Array, - info: PatchInfo + before: Doc, + after: Doc ) => void diff --git a/javascript/src/unstable.ts b/javascript/src/unstable.ts index 9ce92698..7c73afb9 100644 --- a/javascript/src/unstable.ts +++ b/javascript/src/unstable.ts @@ -44,12 +44,11 @@ export { Float64, type Patch, type PatchCallback, - type Mark, type AutomergeValue, type ScalarValue, } from "./unstable_types" -import type { ScalarValue, Mark, PatchCallback } from "./stable" +import type { PatchCallback } from "./stable" import { type UnstableConflicts as Conflicts } from "./conflicts" import { unstableConflictAt } from "./conflicts" @@ -198,11 +197,7 @@ export function load( ): Doc { const opts = importOpts(_opts) opts.enableTextV2 = true - if (opts.patchCallback) { - return stable.loadIncremental(stable.init(opts), data) - } else { - return stable.load(data, opts) - } + return stable.load(data, opts) } function importOpts( @@ -238,66 +233,6 @@ export function splice( } } -export function mark( - doc: Doc, - prop: stable.Prop, - name: string, - range: string, - value: ScalarValue -) { - if (!_is_proxy(doc)) { - throw new RangeError("object cannot be modified outside of a change block") - } - const state = _state(doc, false) - const objectId = _obj(doc) - if (!objectId) { - throw new RangeError("invalid object for mark") - } - const obj = `${objectId}/${prop}` - try { - return state.handle.mark(obj, range, name, value) - } catch (e) { - throw new RangeError(`Cannot mark: ${e}`) - } -} - -export function unmark( - doc: Doc, - prop: stable.Prop, - name: string, - start: number, - end: number -) { - if (!_is_proxy(doc)) { - throw new RangeError("object cannot be modified outside of a change block") - } - const state = _state(doc, false) - const objectId = _obj(doc) - if (!objectId) { - throw new RangeError("invalid object for unmark") - } - const obj = `${objectId}/${prop}` - try { - return state.handle.unmark(obj, name, start, end) - } catch (e) { - throw new RangeError(`Cannot unmark: ${e}`) - } -} - -export function marks(doc: Doc, prop: stable.Prop): Mark[] { - const state = _state(doc, false) - const objectId = _obj(doc) - if (!objectId) { - throw new RangeError("invalid object for unmark") - } - const obj = `${objectId}/${prop}` - try { - return state.handle.marks(obj) - } catch (e) { - throw new RangeError(`Cannot call marks(): ${e}`) - } -} - /** * Get the conflicts associated with a property * diff --git a/javascript/src/unstable_types.ts b/javascript/src/unstable_types.ts index a89fc8e3..071e2cc4 100644 --- a/javascript/src/unstable_types.ts +++ b/javascript/src/unstable_types.ts @@ -8,7 +8,6 @@ export { Float64, type Patch, type PatchCallback, - type Mark, } from "./types" import { RawString } from "./raw_string" diff --git a/javascript/test/legacy_tests.ts b/javascript/test/legacy_tests.ts index a078e30b..8c2e552e 100644 --- a/javascript/test/legacy_tests.ts +++ b/javascript/test/legacy_tests.ts @@ -340,7 +340,8 @@ describe("Automerge", () => { const s2 = Automerge.change( s1, { - patchCallback: (patches, info) => callbacks.push({ patches, info }), + patchCallback: (patches, before, after) => + callbacks.push({ patches, before, after }), }, doc => { doc.birds = ["Goldfinch"] @@ -362,8 +363,8 @@ describe("Automerge", () => { path: ["birds", 0, 0], value: "Goldfinch", }) - assert.strictEqual(callbacks[0].info.before, s1) - assert.strictEqual(callbacks[0].info.after, s2) + assert.strictEqual(callbacks[0].before, s1) + assert.strictEqual(callbacks[0].after, s2) }) it("should call a patchCallback set up on document initialisation", () => { @@ -373,7 +374,8 @@ describe("Automerge", () => { after: Automerge.Doc }> = [] s1 = Automerge.init({ - patchCallback: (patches, info) => callbacks.push({ patches, info }), + patchCallback: (patches, before, after) => + callbacks.push({ patches, before, after }), }) const s2 = Automerge.change(s1, doc => (doc.bird = "Goldfinch")) assert.strictEqual(callbacks.length, 1) @@ -387,8 +389,8 @@ describe("Automerge", () => { path: ["bird", 0], value: "Goldfinch", }) - assert.strictEqual(callbacks[0].info.before, s1) - assert.strictEqual(callbacks[0].info.after, s2) + assert.strictEqual(callbacks[0].before, s1) + assert.strictEqual(callbacks[0].after, s2) }) }) @@ -1568,7 +1570,7 @@ describe("Automerge", () => { assert.deepStrictEqual(doc, { list: expected }) }) - it("should call patchCallback if supplied to load", () => { + it.skip("should call patchCallback if supplied to load", () => { const s1 = Automerge.change( Automerge.init(), doc => (doc.birds = ["Goldfinch"]) @@ -1577,19 +1579,40 @@ describe("Automerge", () => { const callbacks: Array = [], actor = Automerge.getActorId(s1) const reloaded = Automerge.load(Automerge.save(s2), { - patchCallback(patches, opts) { - callbacks.push({ patches, opts }) + patchCallback(patch, before, after) { + callbacks.push({ patch, before, after }) }, }) assert.strictEqual(callbacks.length, 1) - assert.deepStrictEqual(callbacks[0].patches, [ - { action: "put", path: ["birds"], value: [] }, - { action: "insert", path: ["birds", 0], values: ["", ""] }, - { action: "splice", path: ["birds", 0, 0], value: "Goldfinch" }, - { action: "splice", path: ["birds", 1, 0], value: "Chaffinch" }, - ]) - assert.deepStrictEqual(callbacks[0].opts.before, {}) - assert.strictEqual(callbacks[0].opts.after, reloaded) + assert.deepStrictEqual(callbacks[0].patch, { + maxOp: 3, + deps: [decodeChange(Automerge.getAllChanges(s2)[1]).hash], + clock: { [actor]: 2 }, + pendingChanges: 0, + diffs: { + objectId: "_root", + type: "map", + props: { + birds: { + [`1@${actor}`]: { + objectId: `1@${actor}`, + type: "list", + edits: [ + { + action: "multi-insert", + index: 0, + elemId: `2@${actor}`, + values: ["Goldfinch", "Chaffinch"], + }, + ], + }, + }, + }, + }, + }) + assert.deepStrictEqual(callbacks[0].before, {}) + assert.strictEqual(callbacks[0].after, reloaded) + assert.strictEqual(callbacks[0].local, false) }) }) @@ -1789,8 +1812,8 @@ describe("Automerge", () => { before, Automerge.getAllChanges(s1), { - patchCallback(patch, info) { - callbacks.push({ patch, info }) + patchCallback(patch, before, after) { + callbacks.push({ patch, before, after }) }, } ) @@ -1810,8 +1833,8 @@ describe("Automerge", () => { path: ["birds", 0, 0], value: "Goldfinch", }) - assert.strictEqual(callbacks[0].info.before, before) - assert.strictEqual(callbacks[0].info.after, after) + assert.strictEqual(callbacks[0].before, before) + assert.strictEqual(callbacks[0].after, after) }) it("should merge multiple applied changes into one patch", () => { diff --git a/javascript/test/marks.ts b/javascript/test/marks.ts deleted file mode 100644 index 538c2db1..00000000 --- a/javascript/test/marks.ts +++ /dev/null @@ -1,63 +0,0 @@ -import * as assert from "assert" -import { unstable as Automerge } from "../src" -import * as WASM from "@automerge/automerge-wasm" - -describe("Automerge", () => { - describe("marks", () => { - it("should allow marks that can be seen in patches", () => { - let callbacks = [] - let doc1 = Automerge.init({ - patchCallback: (patches, info) => callbacks.push(patches), - }) - doc1 = Automerge.change(doc1, d => { - d.x = "the quick fox jumps over the lazy dog" - }) - doc1 = Automerge.change(doc1, d => { - Automerge.mark(d, "x", "font-weight", "[5..10]", "bold") - }) - - doc1 = Automerge.change(doc1, d => { - Automerge.unmark(d, "x", "font-weight", 7, 9) - }) - - assert.deepStrictEqual(callbacks[1], [ - { - action: "mark", - path: ["x"], - marks: [{ name: "font-weight", start: 5, end: 10, value: "bold" }], - }, - ]) - - assert.deepStrictEqual(callbacks[2], [ - { - action: "unmark", - path: ["x"], - name: "font-weight", - start: 7, - end: 9, - }, - ]) - - callbacks = [] - - let doc2 = Automerge.init({ - patchCallback: (patches, info) => callbacks.push(patches), - }) - doc2 = Automerge.loadIncremental(doc2, Automerge.save(doc1)) - - assert.deepStrictEqual(callbacks[0][2], { - action: "mark", - path: ["x"], - marks: [ - { name: "font-weight", start: 5, end: 7, value: "bold" }, - { name: "font-weight", start: 9, end: 10, value: "bold" }, - ], - }) - - assert.deepStrictEqual(Automerge.marks(doc2, "x"), [ - { name: "font-weight", value: "bold", start: 5, end: 7 }, - { name: "font-weight", value: "bold", start: 9, end: 10 }, - ]) - }) - }) -}) diff --git a/rust/automerge-wasm/index.d.ts b/rust/automerge-wasm/index.d.ts index 4fb1c0c1..be12e4c1 100644 --- a/rust/automerge-wasm/index.d.ts +++ b/rust/automerge-wasm/index.d.ts @@ -94,7 +94,7 @@ export type Op = { pred: string[], } -export type Patch = PutPatch | DelPatch | SpliceTextPatch | IncPatch | InsertPatch | MarkPatch | UnmarkPatch; +export type Patch = PutPatch | DelPatch | SpliceTextPatch | IncPatch | InsertPatch; export type PutPatch = { action: 'put' @@ -103,20 +103,6 @@ export type PutPatch = { conflict: boolean } -export type MarkPatch = { - action: 'mark' - path: Prop[], - marks: Mark[] -} - -export type UnmarkPatch = { - action: 'unmark' - path: Prop[], - name: string, - start: number, - end: number -} - export type IncPatch = { action: 'inc' path: Prop[], @@ -141,13 +127,6 @@ export type InsertPatch = { values: Value[], } -export type Mark = { - name: string, - value: Value, - start: number, - end: number, -} - export function encodeChange(change: ChangeToEncode): Change; export function create(text_v2: boolean, actor?: Actor): Automerge; export function load(data: Uint8Array, text_v2: boolean, actor?: Actor): Automerge; @@ -186,11 +165,6 @@ export class Automerge { increment(obj: ObjID, prop: Prop, value: number): void; delete(obj: ObjID, prop: Prop): void; - // marks - mark(obj: ObjID, name: string, range: string, value: Value, datatype?: Datatype): void; - unmark(obj: ObjID, name: string, start: number, end: number): void; - marks(obj: ObjID, heads?: Heads): Mark[]; - // returns a single value - if there is a conflict return the winner get(obj: ObjID, prop: Prop, heads?: Heads): Value | undefined; getWithType(obj: ObjID, prop: Prop, heads?: Heads): FullValue | null; @@ -243,14 +217,7 @@ export class Automerge { dump(): void; // experimental api can go here - applyPatches(obj: Doc, meta?: unknown, callback?: (patch: Array, info: PatchInfo) => void): Doc; -} - -export interface PatchInfo { - before: T, - after: T, - from: Heads, - to: Heads, + applyPatches(obj: Doc, meta?: unknown, callback?: (patch: Array, before: Doc, after: Doc) => void): Doc; } export interface JsSyncState { @@ -269,4 +236,3 @@ export class SyncState { sentHashes: Heads; readonly sharedHeads: Heads; } - diff --git a/rust/automerge-wasm/package.json b/rust/automerge-wasm/package.json index 61209706..80b39fd4 100644 --- a/rust/automerge-wasm/package.json +++ b/rust/automerge-wasm/package.json @@ -30,7 +30,6 @@ "scripts": { "lint": "eslint test/*.ts index.d.ts", "debug": "cross-env PROFILE=dev TARGET_DIR=debug yarn buildall", - "dev": "cross-env PROFILE=dev TARGET_DIR=debug FEATURES='' TARGET=nodejs yarn target", "build": "cross-env PROFILE=dev TARGET_DIR=debug FEATURES='' yarn buildall", "release": "cross-env PROFILE=release TARGET_DIR=release yarn buildall", "buildall": "cross-env TARGET=nodejs yarn target && cross-env TARGET=bundler yarn target && cross-env TARGET=deno yarn target", @@ -43,7 +42,6 @@ "devDependencies": { "@types/mocha": "^10.0.1", "@types/node": "^18.11.13", - "@types/uuid": "^9.0.1", "@typescript-eslint/eslint-plugin": "^5.46.0", "@typescript-eslint/parser": "^5.46.0", "cross-env": "^7.0.3", @@ -53,8 +51,7 @@ "pako": "^2.1.0", "rimraf": "^3.0.2", "ts-mocha": "^10.0.0", - "typescript": "^4.9.4", - "uuid": "^9.0.0" + "typescript": "^4.9.4" }, "exports": { "browser": "./bundler/automerge_wasm.js", diff --git a/rust/automerge-wasm/src/interop.rs b/rust/automerge-wasm/src/interop.rs index fbf252f3..1546ff10 100644 --- a/rust/automerge-wasm/src/interop.rs +++ b/rust/automerge-wasm/src/interop.rs @@ -12,7 +12,7 @@ use std::fmt::Display; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; -use am::{ObjId, Patch, PatchAction, Value}; +use crate::{observer::Patch, ObjId, Value}; const RAW_DATA_SYMBOL: &str = "_am_raw_value_"; const DATATYPE_SYMBOL: &str = "_am_datatype_"; @@ -28,12 +28,6 @@ impl From for JsValue { } } -impl From for Array { - fn from(ar: AR) -> Self { - ar.0 - } -} - impl From for JsValue { fn from(js: JS) -> Self { js.0 @@ -340,20 +334,11 @@ impl TryFrom for am::sync::Message { } } -impl From> for AR { - fn from(values: Vec) -> Self { - AR(values - .iter() - .map(|h| JsValue::from_str(&h.to_string())) - .collect()) - } -} - impl From<&[ChangeHash]> for AR { fn from(value: &[ChangeHash]) -> Self { AR(value .iter() - .map(|h| JsValue::from_str(&h.to_string())) + .map(|h| JsValue::from_str(&hex::encode(h.0))) .collect()) } } @@ -761,13 +746,13 @@ impl Automerge { pub(crate) fn apply_patch_to_array( &self, array: &Object, - patch: &Patch, + patch: &Patch, meta: &JsValue, exposed: &mut HashSet, ) -> Result { let result = Array::from(array); // shallow copy - match &patch.action { - PatchAction::PutSeq { + match patch { + Patch::PutSeq { index, value, expose, @@ -783,13 +768,13 @@ impl Automerge { } Ok(result.into()) } - PatchAction::DeleteSeq { index, length, .. } => { + Patch::DeleteSeq { index, length, .. } => { Ok(self.sub_splice(result, *index, *length, vec![], meta)?) } - PatchAction::Insert { index, values, .. } => { + Patch::Insert { index, values, .. } => { Ok(self.sub_splice(result, *index, 0, values, meta)?) } - PatchAction::Increment { prop, value, .. } => { + Patch::Increment { prop, value, .. } => { if let Prop::Seq(index) = prop { let index = *index as f64; let old_val = js_get(&result, index)?.0; @@ -810,9 +795,9 @@ impl Automerge { Err(error::ApplyPatch::IncrementKeyInSeq) } } - PatchAction::DeleteMap { .. } => Err(error::ApplyPatch::DeleteKeyFromSeq), - PatchAction::PutMap { .. } => Err(error::ApplyPatch::PutKeyInSeq), - PatchAction::SpliceText { index, value, .. } => { + Patch::DeleteMap { .. } => Err(error::ApplyPatch::DeleteKeyFromSeq), + Patch::PutMap { .. } => Err(error::ApplyPatch::PutKeyInSeq), + Patch::SpliceText { index, value, .. } => { match self.text_rep { TextRepresentation::String => Err(error::ApplyPatch::SpliceTextInSeq), TextRepresentation::Array => { @@ -834,20 +819,19 @@ impl Automerge { } } } - PatchAction::Mark { .. } | PatchAction::Unmark { .. } => Ok(result.into()), } } pub(crate) fn apply_patch_to_map( &self, map: &Object, - patch: &Patch, + patch: &Patch, meta: &JsValue, exposed: &mut HashSet, ) -> Result { let result = Object::assign(&Object::new(), map); // shallow copy - match &patch.action { - PatchAction::PutMap { + match patch { + Patch::PutMap { key, value, expose, .. } => { if *expose && value.0.is_object() { @@ -860,7 +844,7 @@ impl Automerge { } Ok(result) } - PatchAction::DeleteMap { key, .. } => { + Patch::DeleteMap { key, .. } => { Reflect::delete_property(&result, &key.into()).map_err(|e| { error::Export::Delete { prop: key.to_string(), @@ -869,7 +853,7 @@ impl Automerge { })?; Ok(result) } - PatchAction::Increment { prop, value, .. } => { + Patch::Increment { prop, value, .. } => { if let Prop::Map(key) = prop { let old_val = js_get(&result, key)?.0; let old_val = self.unwrap_scalar(old_val)?; @@ -889,30 +873,27 @@ impl Automerge { Err(error::ApplyPatch::IncrementIndexInMap) } } - PatchAction::Insert { .. } => Err(error::ApplyPatch::InsertInMap), - PatchAction::DeleteSeq { .. } => Err(error::ApplyPatch::SpliceInMap), - //PatchAction::SpliceText { .. } => Err(to_js_err("cannot Splice into map")), - PatchAction::SpliceText { .. } => Err(error::ApplyPatch::SpliceTextInMap), - PatchAction::PutSeq { .. } => Err(error::ApplyPatch::PutIdxInMap), - PatchAction::Mark { .. } | PatchAction::Unmark { .. } => { - Err(error::ApplyPatch::MarkInMap) - } + Patch::Insert { .. } => Err(error::ApplyPatch::InsertInMap), + Patch::DeleteSeq { .. } => Err(error::ApplyPatch::SpliceInMap), + //Patch::SpliceText { .. } => Err(to_js_err("cannot Splice into map")), + Patch::SpliceText { .. } => Err(error::ApplyPatch::SpliceTextInMap), + Patch::PutSeq { .. } => Err(error::ApplyPatch::PutIdxInMap), } } pub(crate) fn apply_patch( &self, obj: Object, - patch: &Patch, + patch: &Patch, depth: usize, meta: &JsValue, exposed: &mut HashSet, ) -> Result { let (inner, datatype, id) = self.unwrap_object(&obj)?; - let prop = patch.path.get(depth).map(|p| prop_to_js(&p.1)); + let prop = patch.path().get(depth).map(|p| prop_to_js(&p.1)); let result = if let Some(prop) = prop { let subval = js_get(&inner, &prop)?.0; - if subval.is_string() && patch.path.len() - 1 == depth { + if subval.is_string() && patch.path().len() - 1 == depth { if let Ok(s) = subval.dyn_into::() { let new_value = self.apply_patch_to_text(&s, patch)?; let result = shallow_copy(&inner); @@ -933,12 +914,12 @@ impl Automerge { return Ok(obj); } } else if Array::is_array(&inner) { - if id == patch.obj { + if &id == patch.obj() { self.apply_patch_to_array(&inner, patch, meta, exposed) } else { Ok(Array::from(&inner).into()) } - } else if id == patch.obj { + } else if &id == patch.obj() { self.apply_patch_to_map(&inner, patch, meta, exposed) } else { Ok(Object::assign(&Object::new(), &inner)) @@ -951,17 +932,17 @@ impl Automerge { fn apply_patch_to_text( &self, string: &JsString, - patch: &Patch, + patch: &Patch, ) -> Result { - match &patch.action { - PatchAction::DeleteSeq { index, length, .. } => { + match patch { + Patch::DeleteSeq { index, length, .. } => { let index = *index as u32; let before = string.slice(0, index); let after = string.slice(index + *length as u32, string.length()); let result = before.concat(&after); Ok(result.into()) } - PatchAction::SpliceText { index, value, .. } => { + Patch::SpliceText { index, value, .. } => { let index = *index as u32; let length = string.length(); let before = string.slice(0, index); @@ -1224,148 +1205,6 @@ fn set_hidden_value>( Ok(()) } -pub(crate) struct JsPatch(pub(crate) Patch); - -fn export_path(path: &[(ObjId, Prop)], end: &Prop) -> Array { - let result = Array::new(); - for p in path { - result.push(&prop_to_js(&p.1)); - } - result.push(&prop_to_js(end)); - result -} - -fn export_just_path(path: &[(ObjId, Prop)]) -> Array { - let result = Array::new(); - for p in path { - result.push(&prop_to_js(&p.1)); - } - result -} - -impl TryFrom for JsValue { - type Error = error::Export; - - fn try_from(p: JsPatch) -> Result { - let result = Object::new(); - let path = &p.0.path; - match p.0.action { - PatchAction::PutMap { key, value, .. } => { - js_set(&result, "action", "put")?; - js_set( - &result, - "path", - export_path(path.as_slice(), &Prop::Map(key)), - )?; - js_set( - &result, - "value", - alloc(&value.0, TextRepresentation::String).1, - )?; - Ok(result.into()) - } - PatchAction::PutSeq { index, value, .. } => { - js_set(&result, "action", "put")?; - js_set( - &result, - "path", - export_path(path.as_slice(), &Prop::Seq(index)), - )?; - js_set( - &result, - "value", - alloc(&value.0, TextRepresentation::String).1, - )?; - Ok(result.into()) - } - PatchAction::Insert { index, values, .. } => { - js_set(&result, "action", "insert")?; - js_set( - &result, - "path", - export_path(path.as_slice(), &Prop::Seq(index)), - )?; - js_set( - &result, - "values", - values - .iter() - .map(|v| alloc(&v.0, TextRepresentation::String).1) - .collect::(), - )?; - Ok(result.into()) - } - PatchAction::SpliceText { index, value, .. } => { - js_set(&result, "action", "splice")?; - js_set( - &result, - "path", - export_path(path.as_slice(), &Prop::Seq(index)), - )?; - let bytes: Vec = value.iter().cloned().collect(); - js_set(&result, "value", String::from_utf16_lossy(bytes.as_slice()))?; - Ok(result.into()) - } - PatchAction::Increment { prop, value, .. } => { - js_set(&result, "action", "inc")?; - js_set(&result, "path", export_path(path.as_slice(), &prop))?; - js_set(&result, "value", &JsValue::from_f64(value as f64))?; - Ok(result.into()) - } - PatchAction::DeleteMap { key, .. } => { - js_set(&result, "action", "del")?; - js_set( - &result, - "path", - export_path(path.as_slice(), &Prop::Map(key)), - )?; - Ok(result.into()) - } - PatchAction::DeleteSeq { index, length, .. } => { - js_set(&result, "action", "del")?; - js_set( - &result, - "path", - export_path(path.as_slice(), &Prop::Seq(index)), - )?; - if length > 1 { - js_set(&result, "length", length)?; - } - Ok(result.into()) - } - PatchAction::Mark { marks, .. } => { - js_set(&result, "action", "mark")?; - js_set(&result, "path", export_just_path(path.as_slice()))?; - let marks_array = Array::new(); - for m in marks.iter() { - let mark = Object::new(); - js_set(&mark, "name", m.name())?; - js_set( - &mark, - "value", - &alloc(&m.value().into(), TextRepresentation::String).1, - )?; - js_set(&mark, "start", m.start as i32)?; - js_set(&mark, "end", m.end as i32)?; - marks_array.push(&mark); - } - js_set(&result, "marks", marks_array)?; - Ok(result.into()) - } - PatchAction::Unmark { - name, start, end, .. - } => { - js_set(&result, "action", "unmark")?; - js_set(&result, "path", export_just_path(path.as_slice()))?; - js_set(&result, "name", name)?; - js_set(&result, "start", start as i32)?; - js_set(&result, "end", end as i32)?; - Ok(result.into()) - } - } - } -} - fn shallow_copy(obj: &Object) -> Object { if Array::is_array(obj) { Array::from(obj).into() @@ -1567,8 +1406,6 @@ pub(crate) mod error { SpliceTextInMap, #[error("cannot put a seq index in a map")] PutIdxInMap, - #[error("cannot mark a span in a map")] - MarkInMap, #[error(transparent)] GetProp(#[from] GetProp), #[error(transparent)] diff --git a/rust/automerge-wasm/src/lib.rs b/rust/automerge-wasm/src/lib.rs index 37ef737b..09072ca7 100644 --- a/rust/automerge-wasm/src/lib.rs +++ b/rust/automerge-wasm/src/lib.rs @@ -29,10 +29,8 @@ use am::transaction::CommitOptions; use am::transaction::{Observed, Transactable, UnObserved}; use am::ScalarValue; use automerge as am; -use automerge::{sync::SyncDoc, Change, Prop, ReadDoc, TextEncoding, Value, ROOT}; -use automerge::{ToggleObserver, VecOpObserver16}; +use automerge::{sync::SyncDoc, Change, ObjId, Prop, ReadDoc, TextEncoding, Value, ROOT}; use js_sys::{Array, Function, Object, Uint8Array}; -use regex::Regex; use serde::ser::Serialize; use std::borrow::Cow; use std::collections::HashMap; @@ -42,9 +40,13 @@ use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; mod interop; +mod observer; +mod sequence_tree; mod sync; mod value; +use observer::Observer; + use interop::{alloc, get_heads, import_obj, js_set, to_js_err, to_prop, AR, JS}; use sync::SyncState; use value::Datatype; @@ -58,7 +60,7 @@ macro_rules! log { }; } -type AutoCommit = am::AutoCommitWithObs>>; +type AutoCommit = am::AutoCommitWithObs>; #[cfg(feature = "wee_alloc")] #[global_allocator] @@ -80,15 +82,6 @@ impl std::default::Default for TextRepresentation { } } -impl From for am::op_observer::TextRepresentation { - fn from(tr: TextRepresentation) -> Self { - match tr { - TextRepresentation::Array => am::op_observer::TextRepresentation::Array, - TextRepresentation::String => am::op_observer::TextRepresentation::String, - } - } -} - #[wasm_bindgen] #[derive(Debug)] pub struct Automerge { @@ -104,10 +97,7 @@ impl Automerge { actor: Option, text_rep: TextRepresentation, ) -> Result { - let mut doc = AutoCommit::default() - .with_observer(ToggleObserver::default().with_text_rep(text_rep.into())) - .with_encoding(TextEncoding::Utf16); - doc.observer().set_text_rep(text_rep.into()); + let mut doc = AutoCommit::default().with_encoding(TextEncoding::Utf16); if let Some(a) = actor { let a = automerge::ActorId::from(hex::decode(a)?.to_vec()); doc.set_actor(a); @@ -555,9 +545,8 @@ impl Automerge { let enable = enable .as_bool() .ok_or_else(|| to_js_err("must pass a bool to enablePatches"))?; - let heads = self.doc.get_heads(); - let old_enabled = self.doc.observer().enable(enable, heads); - self.doc.observer().set_text_rep(self.text_rep.into()); + let old_enabled = self.doc.observer().enable(enable); + self.doc.observer().set_text_rep(self.text_rep); Ok(old_enabled.into()) } @@ -582,12 +571,11 @@ impl Automerge { object: JsValue, meta: JsValue, callback: JsValue, - ) -> Result { + ) -> Result { let mut object = object .dyn_into::() .map_err(|_| error::ApplyPatch::NotObjectd)?; - let end_heads = self.doc.get_heads(); - let (patches, begin_heads) = self.doc.observer().take_patches(end_heads.clone()); + let patches = self.doc.observer().take_patches(); let callback = callback.dyn_into::().ok(); // even if there are no patches we may need to update the meta object @@ -606,24 +594,19 @@ impl Automerge { object = self.apply_patch(object, p, 0, &meta, &mut exposed)?; } - self.finalize_exposed(&object, exposed, &meta)?; - if let Some(c) = &callback { if !patches.is_empty() { let patches: Array = patches .into_iter() - .map(interop::JsPatch) .map(JsValue::try_from) .collect::>()?; - let info = Object::new(); - js_set(&info, "before", &before)?; - js_set(&info, "after", &object)?; - js_set(&info, "from", AR::from(begin_heads))?; - js_set(&info, "to", AR::from(end_heads))?; - c.call2(&JsValue::undefined(), &patches.into(), &info)?; + c.call3(&JsValue::undefined(), &patches.into(), &before, &object) + .map_err(error::ApplyPatch::PatchCallback)?; } } + self.finalize_exposed(&object, exposed, &meta)?; + Ok(object.into()) } @@ -633,11 +616,10 @@ impl Automerge { // committed. // If we pop the patches then we won't be able to revert them. - let heads = self.doc.get_heads(); - let (patches, _heads) = self.doc.observer().take_patches(heads); + let patches = self.doc.observer().take_patches(); let result = Array::new(); for p in patches { - result.push(&interop::JsPatch(p).try_into()?); + result.push(&p.try_into()?); } Ok(result) } @@ -720,12 +702,17 @@ impl Automerge { #[wasm_bindgen(js_name = getHeads)] pub fn get_heads(&mut self) -> Array { let heads = self.doc.get_heads(); - AR::from(heads).into() + let heads: Array = heads + .iter() + .map(|h| JsValue::from_str(&hex::encode(h.0))) + .collect(); + heads } #[wasm_bindgen(js_name = getActorId)] pub fn get_actor_id(&self) -> String { - self.doc.get_actor().to_string() + let actor = self.doc.get_actor(); + actor.to_string() } #[wasm_bindgen(js_name = getLastLocalChange)] @@ -788,8 +775,7 @@ impl Automerge { ) -> Result { let (obj, obj_type) = self.import(obj).unwrap_or((ROOT, am::ObjType::Map)); let heads = get_heads(heads)?; - let current_heads = self.doc.get_heads(); - let _patches = self.doc.observer().take_patches(current_heads); // throw away patches + let _patches = self.doc.observer().take_patches(); // throw away patches Ok(self.export_object(&obj, obj_type.into(), heads.as_ref(), &meta)?) } @@ -800,75 +786,6 @@ impl Automerge { let hash = self.doc.empty_change(options); JsValue::from_str(&hex::encode(hash)) } - - pub fn mark( - &mut self, - obj: JsValue, - 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(format!("(range={}) range must be in the form of (start..end] or [start..end) etc... () for sticky, [] for normal",range))?; - 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 left_sticky = &cap[1] == "("; - let right_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.doc - .mark( - &obj, - am::marks::Mark::new(name, value, start, end), - am::marks::ExpandMark::from(left_sticky, right_sticky), - ) - .map_err(to_js_err)?; - Ok(()) - } - - pub fn unmark( - &mut self, - obj: JsValue, - key: JsValue, - start: f64, - end: f64, - ) -> Result<(), JsValue> { - let (obj, _) = self.import(obj)?; - let key = key.as_string().ok_or("key must be a string")?; - self.doc - .unmark(&obj, &key, start as usize, end as usize) - .map_err(to_js_err)?; - Ok(()) - } - - pub fn marks(&mut self, obj: JsValue, heads: Option) -> Result { - let (obj, _) = self.import(obj)?; - let heads = get_heads(heads)?; - let marks = if let Some(heads) = heads { - self.doc.marks_at(obj, &heads).map_err(to_js_err)? - } else { - self.doc.marks(obj).map_err(to_js_err)? - }; - let result = Array::new(); - for m in marks { - let mark = Object::new(); - let (_datatype, value) = alloc(&m.value().clone().into(), self.text_rep); - js_set(&mark, "name", m.name())?; - js_set(&mark, "value", value)?; - js_set(&mark, "start", m.start as i32)?; - js_set(&mark, "end", m.end as i32)?; - result.push(&mark.into()); - } - Ok(result.into()) - } } #[wasm_bindgen(js_name = create)] @@ -895,7 +812,7 @@ pub fn load( TextRepresentation::Array }; let mut doc = am::AutoCommitWithObs::::load(&data)? - .with_observer(ToggleObserver::default().with_text_rep(text_rep.into())) + .with_observer(Observer::default().with_text_rep(text_rep)) .with_encoding(TextEncoding::Utf16); if let Some(s) = actor { let actor = diff --git a/rust/automerge-wasm/src/observer.rs b/rust/automerge-wasm/src/observer.rs new file mode 100644 index 00000000..2351c762 --- /dev/null +++ b/rust/automerge-wasm/src/observer.rs @@ -0,0 +1,518 @@ +#![allow(dead_code)] + +use std::borrow::Cow; + +use crate::{ + interop::{self, alloc, js_set}, + TextRepresentation, +}; +use automerge::{ObjId, OpObserver, Prop, ReadDoc, ScalarValue, Value}; +use js_sys::{Array, Object}; +use wasm_bindgen::prelude::*; + +use crate::sequence_tree::SequenceTree; + +#[derive(Debug, Clone, Default)] +pub(crate) struct Observer { + enabled: bool, + patches: Vec, + text_rep: TextRepresentation, +} + +impl Observer { + pub(crate) fn take_patches(&mut self) -> Vec { + std::mem::take(&mut self.patches) + } + pub(crate) fn enable(&mut self, enable: bool) -> bool { + if self.enabled && !enable { + self.patches.truncate(0) + } + let old_enabled = self.enabled; + self.enabled = enable; + old_enabled + } + + fn get_path(&mut self, doc: &R, obj: &ObjId) -> Option> { + match doc.parents(obj) { + Ok(parents) => parents.visible_path(), + Err(e) => { + automerge::log!("error generating patch : {:?}", e); + None + } + } + } + + pub(crate) fn with_text_rep(mut self, text_rep: TextRepresentation) -> Self { + self.text_rep = text_rep; + self + } + + pub(crate) fn set_text_rep(&mut self, text_rep: TextRepresentation) { + self.text_rep = text_rep; + } +} + +#[derive(Debug, Clone)] +pub(crate) enum Patch { + PutMap { + obj: ObjId, + path: Vec<(ObjId, Prop)>, + key: String, + value: (Value<'static>, ObjId), + expose: bool, + }, + PutSeq { + obj: ObjId, + path: Vec<(ObjId, Prop)>, + index: usize, + value: (Value<'static>, ObjId), + expose: bool, + }, + Insert { + obj: ObjId, + path: Vec<(ObjId, Prop)>, + index: usize, + values: SequenceTree<(Value<'static>, ObjId)>, + }, + SpliceText { + obj: ObjId, + path: Vec<(ObjId, Prop)>, + index: usize, + value: SequenceTree, + }, + Increment { + obj: ObjId, + path: Vec<(ObjId, Prop)>, + prop: Prop, + value: i64, + }, + DeleteMap { + obj: ObjId, + path: Vec<(ObjId, Prop)>, + key: String, + }, + DeleteSeq { + obj: ObjId, + path: Vec<(ObjId, Prop)>, + index: usize, + length: usize, + }, +} + +impl OpObserver for Observer { + fn insert( + &mut self, + doc: &R, + obj: ObjId, + index: usize, + tagged_value: (Value<'_>, ObjId), + ) { + if self.enabled { + let value = (tagged_value.0.to_owned(), tagged_value.1); + if let Some(Patch::Insert { + obj: tail_obj, + index: tail_index, + values, + .. + }) = self.patches.last_mut() + { + let range = *tail_index..=*tail_index + values.len(); + if tail_obj == &obj && range.contains(&index) { + values.insert(index - *tail_index, value); + return; + } + } + if let Some(path) = self.get_path(doc, &obj) { + let mut values = SequenceTree::new(); + values.push(value); + let patch = Patch::Insert { + path, + obj, + index, + values, + }; + self.patches.push(patch); + } + } + } + + fn splice_text(&mut self, doc: &R, obj: ObjId, index: usize, value: &str) { + if self.enabled { + if self.text_rep == TextRepresentation::Array { + for (i, c) in value.chars().enumerate() { + self.insert( + doc, + obj.clone(), + index + i, + ( + Value::Scalar(Cow::Owned(ScalarValue::Str(c.to_string().into()))), + ObjId::Root, // We hope this is okay + ), + ); + } + return; + } + if let Some(Patch::SpliceText { + obj: tail_obj, + index: tail_index, + value: prev_value, + .. + }) = self.patches.last_mut() + { + let range = *tail_index..=*tail_index + prev_value.len(); + if tail_obj == &obj && range.contains(&index) { + let i = index - *tail_index; + for (n, ch) in value.encode_utf16().enumerate() { + prev_value.insert(i + n, ch) + } + return; + } + } + if let Some(path) = self.get_path(doc, &obj) { + let mut v = SequenceTree::new(); + for ch in value.encode_utf16() { + v.push(ch) + } + let patch = Patch::SpliceText { + path, + obj, + index, + value: v, + }; + self.patches.push(patch); + } + } + } + + fn delete_seq(&mut self, doc: &R, obj: ObjId, index: usize, length: usize) { + if self.enabled { + match self.patches.last_mut() { + Some(Patch::SpliceText { + obj: tail_obj, + index: tail_index, + value, + .. + }) => { + let range = *tail_index..*tail_index + value.len(); + if tail_obj == &obj + && range.contains(&index) + && range.contains(&(index + length - 1)) + { + for _ in 0..length { + value.remove(index - *tail_index); + } + return; + } + } + Some(Patch::Insert { + obj: tail_obj, + index: tail_index, + values, + .. + }) => { + let range = *tail_index..*tail_index + values.len(); + if tail_obj == &obj + && range.contains(&index) + && range.contains(&(index + length - 1)) + { + for _ in 0..length { + values.remove(index - *tail_index); + } + return; + } + } + Some(Patch::DeleteSeq { + obj: tail_obj, + index: tail_index, + length: tail_length, + .. + }) => { + if tail_obj == &obj && index == *tail_index { + *tail_length += length; + return; + } + } + _ => {} + } + if let Some(path) = self.get_path(doc, &obj) { + let patch = Patch::DeleteSeq { + path, + obj, + index, + length, + }; + self.patches.push(patch) + } + } + } + + fn delete_map(&mut self, doc: &R, obj: ObjId, key: &str) { + if self.enabled { + if let Some(path) = self.get_path(doc, &obj) { + let patch = Patch::DeleteMap { + path, + obj, + key: key.to_owned(), + }; + self.patches.push(patch) + } + } + } + + fn put( + &mut self, + doc: &R, + obj: ObjId, + prop: Prop, + tagged_value: (Value<'_>, ObjId), + _conflict: bool, + ) { + if self.enabled { + let expose = false; + if let Some(path) = self.get_path(doc, &obj) { + let value = (tagged_value.0.to_owned(), tagged_value.1); + let patch = match prop { + Prop::Map(key) => Patch::PutMap { + path, + obj, + key, + value, + expose, + }, + Prop::Seq(index) => Patch::PutSeq { + path, + obj, + index, + value, + expose, + }, + }; + self.patches.push(patch); + } + } + } + + fn expose( + &mut self, + doc: &R, + obj: ObjId, + prop: Prop, + tagged_value: (Value<'_>, ObjId), + _conflict: bool, + ) { + if self.enabled { + let expose = true; + if let Some(path) = self.get_path(doc, &obj) { + let value = (tagged_value.0.to_owned(), tagged_value.1); + let patch = match prop { + Prop::Map(key) => Patch::PutMap { + path, + obj, + key, + value, + expose, + }, + Prop::Seq(index) => Patch::PutSeq { + path, + obj, + index, + value, + expose, + }, + }; + self.patches.push(patch); + } + } + } + + fn increment( + &mut self, + doc: &R, + obj: ObjId, + prop: Prop, + tagged_value: (i64, ObjId), + ) { + if self.enabled { + if let Some(path) = self.get_path(doc, &obj) { + let value = tagged_value.0; + self.patches.push(Patch::Increment { + path, + obj, + prop, + value, + }) + } + } + } + + fn text_as_seq(&self) -> bool { + self.text_rep == TextRepresentation::Array + } +} + +impl automerge::op_observer::BranchableObserver for Observer { + fn merge(&mut self, other: &Self) { + self.patches.extend_from_slice(other.patches.as_slice()) + } + + fn branch(&self) -> Self { + Observer { + patches: vec![], + enabled: self.enabled, + text_rep: self.text_rep, + } + } +} + +fn prop_to_js(p: &Prop) -> JsValue { + match p { + Prop::Map(key) => JsValue::from_str(key), + Prop::Seq(index) => JsValue::from_f64(*index as f64), + } +} + +fn export_path(path: &[(ObjId, Prop)], end: &Prop) -> Array { + let result = Array::new(); + for p in path { + result.push(&prop_to_js(&p.1)); + } + result.push(&prop_to_js(end)); + result +} + +impl Patch { + pub(crate) fn path(&self) -> &[(ObjId, Prop)] { + match &self { + Self::PutMap { path, .. } => path.as_slice(), + Self::PutSeq { path, .. } => path.as_slice(), + Self::Increment { path, .. } => path.as_slice(), + Self::Insert { path, .. } => path.as_slice(), + Self::SpliceText { path, .. } => path.as_slice(), + Self::DeleteMap { path, .. } => path.as_slice(), + Self::DeleteSeq { path, .. } => path.as_slice(), + } + } + + pub(crate) fn obj(&self) -> &ObjId { + match &self { + Self::PutMap { obj, .. } => obj, + Self::PutSeq { obj, .. } => obj, + Self::Increment { obj, .. } => obj, + Self::Insert { obj, .. } => obj, + Self::SpliceText { obj, .. } => obj, + Self::DeleteMap { obj, .. } => obj, + Self::DeleteSeq { obj, .. } => obj, + } + } +} + +impl TryFrom for JsValue { + type Error = interop::error::Export; + + fn try_from(p: Patch) -> Result { + let result = Object::new(); + match p { + Patch::PutMap { + path, key, value, .. + } => { + js_set(&result, "action", "put")?; + js_set( + &result, + "path", + export_path(path.as_slice(), &Prop::Map(key)), + )?; + js_set( + &result, + "value", + alloc(&value.0, TextRepresentation::String).1, + )?; + Ok(result.into()) + } + Patch::PutSeq { + path, index, value, .. + } => { + js_set(&result, "action", "put")?; + js_set( + &result, + "path", + export_path(path.as_slice(), &Prop::Seq(index)), + )?; + js_set( + &result, + "value", + alloc(&value.0, TextRepresentation::String).1, + )?; + Ok(result.into()) + } + Patch::Insert { + path, + index, + values, + .. + } => { + js_set(&result, "action", "insert")?; + js_set( + &result, + "path", + export_path(path.as_slice(), &Prop::Seq(index)), + )?; + js_set( + &result, + "values", + values + .iter() + .map(|v| alloc(&v.0, TextRepresentation::String).1) + .collect::(), + )?; + Ok(result.into()) + } + Patch::SpliceText { + path, index, value, .. + } => { + js_set(&result, "action", "splice")?; + js_set( + &result, + "path", + export_path(path.as_slice(), &Prop::Seq(index)), + )?; + let bytes: Vec = value.iter().cloned().collect(); + js_set(&result, "value", String::from_utf16_lossy(bytes.as_slice()))?; + Ok(result.into()) + } + Patch::Increment { + path, prop, value, .. + } => { + js_set(&result, "action", "inc")?; + js_set(&result, "path", export_path(path.as_slice(), &prop))?; + js_set(&result, "value", &JsValue::from_f64(value as f64))?; + Ok(result.into()) + } + Patch::DeleteMap { path, key, .. } => { + js_set(&result, "action", "del")?; + js_set( + &result, + "path", + export_path(path.as_slice(), &Prop::Map(key)), + )?; + Ok(result.into()) + } + Patch::DeleteSeq { + path, + index, + length, + .. + } => { + js_set(&result, "action", "del")?; + js_set( + &result, + "path", + export_path(path.as_slice(), &Prop::Seq(index)), + )?; + if length > 1 { + js_set(&result, "length", length)?; + } + Ok(result.into()) + } + } + } +} diff --git a/rust/automerge-wasm/test/marks.ts b/rust/automerge-wasm/test/marks.ts deleted file mode 100644 index 0a2d4433..00000000 --- a/rust/automerge-wasm/test/marks.ts +++ /dev/null @@ -1,572 +0,0 @@ -import { describe, it } from 'mocha'; -//@ts-ignore -import assert from 'assert' -//@ts-ignore -import { create, load, Automerge, encodeChange, decodeChange } from '..' -import { v4 as uuid } from "uuid" - - -let util = require('util') - -describe('Automerge', () => { - describe('marks', () => { - it('should handle marks [..]', () => { - let doc = create(true) - let list = doc.putObject("_root", "list", "") - doc.splice(list, 0, 0, "aaabbbccc") - doc.mark(list, "[3..6]", "bold" , true) - let text = doc.text(list) - let marks = doc.marks(list); - assert.deepStrictEqual(marks, [{ name: 'bold', value: true, start: 3, end: 6 }]) - doc.insert(list, 6, "A") - doc.insert(list, 3, "A") - marks = doc.marks(list); - assert.deepStrictEqual(marks, [{ name: 'bold', value: true, start: 4, end: 7 }]) - }) - - it('should handle mark and unmark', () => { - let doc = create(true) - let list = doc.putObject("_root", "list", "") - doc.splice(list, 0, 0, "aaabbbccc") - doc.mark(list, "[2..8]", "bold" , true) - let marks = doc.marks(list); - assert.deepStrictEqual(marks, [{ name: 'bold', value: true, start: 2, end: 8 }]) - doc.unmark(list, 'bold', 4, 6) - doc.insert(list, 7, "A") - doc.insert(list, 3, "A") - marks = doc.marks(list); - assert.deepStrictEqual(marks, [ - { name: 'bold', value: true, start: 2, end: 5 }, - { name: 'bold', value: true, start: 7, end: 10 }, - ]) - }) - - it('should handle mark and unmark of overlapping marks', () => { - let doc = create(true) - let list = doc.putObject("_root", "list", "") - doc.splice(list, 0, 0, "aaabbbccc") - doc.mark(list, "[2..6]", "bold" , true) - doc.mark(list, "[5..8]", "bold" , true) - doc.mark(list, "[3..6]", "underline" , true) - let marks = doc.marks(list); - assert.deepStrictEqual(marks, [ - { name: 'underline', value: true, start: 3, end: 6 }, - { name: 'bold', value: true, start: 2, end: 8 }, - ]) - doc.unmark(list, 'bold', 4, 6) - doc.insert(list, 7, "A") - doc.insert(list, 3, "A") - marks = doc.marks(list); - assert.deepStrictEqual(marks, [ - { name: 'bold', value: true, start: 2, end: 5 }, - { name: 'underline', value: true, start: 4, end: 7 }, - { name: 'bold', value: true, start: 7, end: 10 }, - ]) - doc.unmark(list, 'bold', 0, 11) - marks = doc.marks(list); - assert.deepStrictEqual(marks, [ - { name: 'underline', value: true, start: 4, end: 7 } - ]) - }) - - it('should handle marks [..] at the beginning of a string', () => { - let doc = create(true) - let list = doc.putObject("_root", "list", "") - doc.splice(list, 0, 0, "aaabbbccc") - doc.mark(list, "[0..3]", "bold", true) - let marks = doc.marks(list); - assert.deepStrictEqual(marks, [{ name: 'bold', value: true, start: 0, end: 3 }]) - - let doc2 = doc.fork() - doc2.insert(list, 0, "A") - doc2.insert(list, 4, "B") - doc.merge(doc2) - marks = doc.marks(list); - assert.deepStrictEqual(marks, [{ name: 'bold', value: true, start: 1, end: 4 }]) - }) - - it('should handle marks [..] with splice', () => { - let doc = create(true) - let list = doc.putObject("_root", "list", "") - doc.splice(list, 0, 0, "aaabbbccc") - doc.mark(list, "[0..3]", "bold", true) - let marks = doc.marks(list); - assert.deepStrictEqual(marks, [{ name: 'bold', value: true, start: 0, end: 3 }]) - - let doc2 = doc.fork() - doc2.splice(list, 0, 2, "AAA") - doc2.splice(list, 4, 0, "BBB") - doc.merge(doc2) - marks = doc.marks(list); - assert.deepStrictEqual(marks, [{ name: 'bold', value: true, start: 3, end: 4 }]) - }) - - it('should handle marks across multiple forks', () => { - let doc = create(true) - let list = doc.putObject("_root", "list", "") - doc.splice(list, 0, 0, "aaabbbccc") - doc.mark(list, "[0..3]", "bold", true) - let marks = doc.marks(list); - assert.deepStrictEqual(marks, [{ name: 'bold', value: true, start: 0, end: 3 }]) - - let doc2 = doc.fork() - doc2.splice(list, 1, 1, "Z") // replace 'aaa' with 'aZa' inside mark. - - let doc3 = doc.fork() - doc3.insert(list, 0, "AAA") // should not be included in mark. - - doc.merge(doc2) - doc.merge(doc3) - - marks = doc.marks(list); - assert.deepStrictEqual(marks, [{ name: 'bold', value: true, start: 3, end: 6 }]) - }) - - it('should handle marks with deleted ends [..]', () => { - let doc = create(true) - let list = doc.putObject("_root", "list", "") - - doc.splice(list, 0, 0, "aaabbbccc") - doc.mark(list, "[3..6]", "bold" , true) - let marks = doc.marks(list); - assert.deepStrictEqual(marks, [{ name: 'bold', value: true, start: 3, end: 6 }]) - doc.delete(list,5); - doc.delete(list,5); - doc.delete(list,2); - doc.delete(list,2); - marks = doc.marks(list); - assert.deepStrictEqual(marks, [{ name: 'bold', value: true, start: 2, end: 3 }]) - doc.insert(list, 3, "A") - doc.insert(list, 2, "A") - marks = doc.marks(list); - assert.deepStrictEqual(marks, [{ name: 'bold', value: true, start: 3, end: 4 }]) - }) - - it('should handle sticky marks (..)', () => { - let doc = create(true) - let list = doc.putObject("_root", "list", "") - doc.splice(list, 0, 0, "aaabbbccc") - doc.mark(list, "(3..6)", "bold" , true) - let marks = doc.marks(list); - assert.deepStrictEqual(marks, [{ name: 'bold', value: true, start: 3, end: 6 }]) - doc.insert(list, 6, "A") - doc.insert(list, 3, "A") - marks = doc.marks(list); - assert.deepStrictEqual(marks, [{ name: 'bold', value: true, start: 3, end: 8 }]) - }) - - it('should handle sticky marks with deleted ends (..)', () => { - let doc = create(true) - let list = doc.putObject("_root", "list", "") - doc.splice(list, 0, 0, "aaabbbccc") - doc.mark(list, "(3..6)", "bold" , true) - let marks = doc.marks(list); - assert.deepStrictEqual(marks, [{ name: 'bold', value: true, start: 3, end: 6 }]) - doc.delete(list,5); - doc.delete(list,5); - doc.delete(list,2); - doc.delete(list,2); - marks = doc.marks(list); - assert.deepStrictEqual(marks, [{ name: 'bold', value: true, start: 2, end: 3 }]) - doc.insert(list, 3, "A") - doc.insert(list, 2, "A") - marks = doc.marks(list); - assert.deepStrictEqual(marks, [{ name: 'bold', value: true, start: 2, end: 5 }]) - - // make sure save/load can handle marks - - let saved = doc.save() - let doc2 = load(saved,true) - marks = doc2.marks(list); - assert.deepStrictEqual(marks, [{ name: 'bold', value: true, start: 2, end: 5 }]) - - assert.deepStrictEqual(doc.getHeads(), doc2.getHeads()) - assert.deepStrictEqual(doc.save(), doc2.save()) - }) - - it('should handle overlapping marks', () => { - let doc : Automerge = create(true, "aabbcc") - let list = doc.putObject("_root", "list", "") - 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) - let id = uuid(); // we want each comment to be unique so give it a unique id - doc.mark(list, "[10..13]", `comment:${id}` , "foxes are my favorite animal!") - doc.commit("marks"); - let marks = doc.marks(list); - assert.deepStrictEqual(marks, [ - { name: `comment:${id}`, start: 10, end: 13, value: 'foxes are my favorite animal!' }, - { name: 'itallic', start: 4, end: 19, value: true }, - { name: 'bold', start: 0, end: 37, value: true } - ]) - let text = doc.text(list); - assert.deepStrictEqual(text, "the quick fox jumps over the lazy dog"); - - let all = doc.getChanges([]) - let decoded = all.map((c) => decodeChange(c)) - let util = require('util'); - let encoded = decoded.map((c) => encodeChange(c)) - let decoded2 = encoded.map((c) => decodeChange(c)) - let doc2 = create(true); - doc2.applyChanges(encoded) - - assert.deepStrictEqual(doc.marks(list) , doc2.marks(list)) - assert.deepStrictEqual(doc.save(), doc2.save()) - }) - - it('generates patches for marks made locally', () => { - let doc : Automerge = create(true, "aabbcc") - doc.enablePatches(true) - let list = doc.putObject("_root", "list", "") - doc.splice(list, 0, 0, "the quick fox jumps over the lazy dog") - let h1 = doc.getHeads() - doc.mark(list, "[0..37]", "bold" , true) - doc.mark(list, "[4..19]", "itallic" , true) - let id = uuid(); // we want each comment to be unique so give it a unique id - doc.mark(list, "[10..13]", `comment:${id}` , "foxes are my favorite animal!") - doc.commit("marks"); - let h2 = doc.getHeads() - let patches = doc.popPatches(); - let util = require('util') - assert.deepEqual(patches, [ - { action: 'put', path: [ 'list' ], value: '' }, - { - action: 'splice', path: [ 'list', 0 ], - value: 'the quick fox jumps over the lazy dog' - }, - { - action: 'mark', path: [ 'list' ], - marks: [ - { name: 'bold', value: true, start: 0, end: 37 }, - { name: 'itallic', value: true, start: 4, end: 19 }, - { name: `comment:${id}`, value: 'foxes are my favorite animal!', start: 10, end: 13 } - ] - } - ]); - }) - - it('marks should create patches that respect marks that supersede it', () => { - - let doc1 : Automerge = create(true, "aabbcc") - let list = doc1.putObject("_root", "list", "") - doc1.splice(list, 0, 0, "the quick fox jumps over the lazy dog") - - let doc2 = load(doc1.save(),true); - - let doc3 = load(doc1.save(),true); - doc3.enablePatches(true) - - doc1.put("/","foo", "bar"); // make a change to our op counter is higher than doc2 - doc1.mark(list, "[0..5]", "x", "a") - doc1.mark(list, "[8..11]", "x", "b") - - doc2.mark(list, "[4..13]", "x", "c"); - - doc3.merge(doc1) - doc3.merge(doc2) - - let patches = doc3.popPatches(); - - assert.deepEqual(patches, [ - { action: 'put', path: [ 'foo' ], value: 'bar' }, - { - action: 'mark', - path: [ 'list' ], - marks: [ - { name: 'x', value: 'a', start: 0, end: 5 }, - { name: 'x', value: 'b', start: 8, end: 11 }, - { name: 'x', value: 'c', start: 5, end: 8 }, - { name: 'x', value: 'c', start: 11, end: 13 }, - ] - }, - ]); - }) - }) - describe('loading marks', () => { - it('a mark will appear on load', () => { - let doc1 : Automerge = create(true, "aabbcc") - doc1.enablePatches(true) - - let list = doc1.putObject("_root", "list", "") - doc1.splice(list, 0, 0, "the quick fox jumps over the lazy dog") - doc1.mark(list, "[5..10]", "xxx", "aaa") - - let patches1 = doc1.popPatches().filter((p:any) => p.action == "mark") - - assert.deepEqual(patches1, [{ - action: 'mark', path: [ 'list' ], marks: [ { name: 'xxx', value: 'aaa', start: 5, end: 10 }], - }]); - - let doc2 : Automerge = create(true); - doc2.enablePatches(true) - doc2.loadIncremental(doc1.save()) - - let patches2 = doc2.popPatches().filter((p:any) => p.action == "mark") - - assert.deepEqual(patches2, [{ - action: 'mark', path: ['list'], marks: [ { name: 'xxx', value: 'aaa', start: 5, end: 10}], - }]); - }) - - it('a overlapping marks will coalesse on load', () => { - let doc1 : Automerge = create(true, "aabbcc") - doc1.enablePatches(true) - - let list = doc1.putObject("_root", "list", "") - doc1.splice(list, 0, 0, "the quick fox jumps over the lazy dog") - doc1.mark(list, "[5..15]", "xxx", "aaa") - doc1.mark(list, "[10..20]", "xxx", "aaa") - doc1.mark(list, "[15..25]", "xxx", "aaa") - - let patches1 = doc1.popPatches().filter((p:any) => p.action == "mark") - - assert.deepEqual(patches1, [ - { action: 'mark', path: [ 'list' ], marks: [ - { name: 'xxx', value: 'aaa', start: 5, end: 15 }, - { name: 'xxx', value: 'aaa', start: 10, end: 20 }, - { name: 'xxx', value: 'aaa', start: 15, end: 25 }, - ] }, - ]); - - let doc2 : Automerge = create(true); - doc2.enablePatches(true) - doc2.loadIncremental(doc1.save()) - - let patches2 = doc2.popPatches().filter((p:any) => p.action == "mark") - - assert.deepEqual(patches2, [ - { action: 'mark', path: ['list'], marks: [ { name: 'xxx', value: 'aaa', start: 5, end: 25}] }, - ]); - }) - - it('coalesse handles different values', () => { - let doc1 : Automerge = create(true, "aabbcc") - doc1.enablePatches(true) - - let list = doc1.putObject("_root", "list", "") - doc1.splice(list, 0, 0, "the quick fox jumps over the lazy dog") - doc1.mark(list, "[5..15]", "xxx", "aaa") - doc1.mark(list, "[10..20]", "xxx", "bbb") - doc1.mark(list, "[15..25]", "xxx", "aaa") - - let patches1 = doc1.popPatches().filter((p:any) => p.action == "mark") - - assert.deepEqual(patches1, [ - { action: 'mark', path: [ 'list' ], marks: [ - { name: 'xxx', value: 'aaa', start: 5, end: 15 }, - { name: 'xxx', value: 'bbb', start: 10, end: 20 }, - { name: 'xxx', value: 'aaa', start: 15, end: 25 }, - ]} - ]); - - let doc2 : Automerge = create(true); - doc2.enablePatches(true) - doc2.loadIncremental(doc1.save()) - - let patches2 = doc2.popPatches().filter((p:any) => p.action == "mark") - - assert.deepEqual(patches2, [ - { action: 'mark', path: ['list'], marks: [ - { name: 'xxx', value: 'aaa', start: 5, end: 10 }, - { name: 'xxx', value: 'bbb', start: 10, end: 15 }, - { name: 'xxx', value: 'aaa', start: 15, end: 25 }, - ]}, - ]); - }) - - it('wont coalesse handles different names', () => { - let doc1 : Automerge = create(true, "aabbcc") - doc1.enablePatches(true) - - let list = doc1.putObject("_root", "list", "") - doc1.splice(list, 0, 0, "the quick fox jumps over the lazy dog") - doc1.mark(list, "[5..15]", "xxx", "aaa") - doc1.mark(list, "[10..20]", "yyy", "aaa") - doc1.mark(list, "[15..25]", "zzz", "aaa") - - let patches1 = doc1.popPatches().filter((p:any) => p.action == "mark") - - assert.deepEqual(patches1, [ - { action: 'mark', path: [ 'list' ], marks: [ - { name: 'xxx', value: 'aaa', start: 5, end:15 }, - { name: 'yyy', value: 'aaa', start: 10, end: 20 }, - { name: 'zzz', value: 'aaa', start: 15, end: 25 }, - ]} - ]); - - let doc2 : Automerge = create(true); - doc2.enablePatches(true) - doc2.loadIncremental(doc1.save()) - - let patches2 = doc2.popPatches().filter((p:any) => p.action == "mark") - - assert.deepEqual(patches2, [ - { action: 'mark', path: [ 'list' ], marks: [ - { name: 'xxx', value: 'aaa', start: 5, end: 15 }, - { name: 'yyy', value: 'aaa', start: 10, end: 20 }, - { name: 'zzz', value: 'aaa', start: 15, end: 25 }, - ]} - ]); - }) - - it('coalesse handles async merge', () => { - let doc1 : Automerge = create(true, "aabbcc") - doc1.enablePatches(true) - - let list = doc1.putObject("_root", "list", "") - doc1.splice(list, 0, 0, "the quick fox jumps over the lazy dog") - - let doc2 = doc1.fork() - - doc1.put("/", "key1", "value"); // incrementing op counter so we win vs doc2 - doc1.put("/", "key2", "value"); // incrementing op counter so we win vs doc2 - doc1.mark(list, "[10..20]", "xxx", "aaa") - doc1.mark(list, "[15..25]", "xxx", "aaa") - - doc2.mark(list, "[5..30]" , "xxx", "bbb") - - doc1.merge(doc2) - - let patches1 = doc1.popPatches().filter((p:any) => p.action == "mark") - - assert.deepEqual(patches1, [ - { action: 'mark', path: [ 'list' ], marks: [ - { name: 'xxx', value: 'aaa', start: 10, end: 20 }, - { name: 'xxx', value: 'aaa', start: 15, end: 25 }, - { name: 'xxx', value: 'bbb', start: 5, end: 10 }, - { name: 'xxx', value: 'bbb', start: 25, end: 30 }, - ] - }, - ]); - - let doc3 : Automerge = create(true); - doc3.enablePatches(true) - doc3.loadIncremental(doc1.save()) - - let patches2 = doc3.popPatches().filter((p:any) => p.action == "mark") - - let marks = doc3.marks(list) - - assert.deepEqual(marks, [ - { name: 'xxx', value: 'bbb', start: 5, end: 10 }, - { name: 'xxx', value: 'aaa', start: 10, end: 25 }, - { name: 'xxx', value: 'bbb', start: 25, end: 30 }, - ]); - - assert.deepEqual(patches2, [{ action: 'mark', path: [ 'list' ], marks }]); - }) - - it('does not show marks hidden in merge', () => { - let doc1 : Automerge = create(true, "aabbcc") - doc1.enablePatches(true) - - let list = doc1.putObject("_root", "list", "") - doc1.splice(list, 0, 0, "the quick fox jumps over the lazy dog") - - let doc2 = doc1.fork() - - doc1.put("/", "key1", "value"); // incrementing op counter so we win vs doc2 - doc1.put("/", "key2", "value"); // incrementing op counter so we win vs doc2 - doc1.mark(list, "[10..20]", "xxx", "aaa") - doc1.mark(list, "[15..25]", "xxx", "aaa") - - doc2.mark(list, "[11..24]" , "xxx", "bbb") - - doc1.merge(doc2) - - let patches1 = doc1.popPatches().filter((p:any) => p.action == "mark") - - assert.deepEqual(patches1, [ - { action: 'mark', path: [ 'list' ], marks: [ - { name: 'xxx', value: 'aaa', start: 10, end: 20 }, - { name: 'xxx', value: 'aaa', start: 15, end: 25 }, - ] - }, - ]); - - let doc3 : Automerge = create(true); - doc3.enablePatches(true) - doc3.loadIncremental(doc1.save()) - - let patches2 = doc3.popPatches().filter((p:any) => p.action == "mark") - - assert.deepEqual(patches2, [ - { action: 'mark', path: [ 'list' ], marks: [ - { name: 'xxx', value: 'aaa', start: 10, end: 25 }, - ]} - ]); - }) - - it('coalesse disconnected marks with async merge', () => { - let doc1 : Automerge = create(true, "aabbcc") - doc1.enablePatches(true) - - let list = doc1.putObject("_root", "list", "") - doc1.splice(list, 0, 0, "the quick fox jumps over the lazy dog") - - let doc2 = doc1.fork() - - doc1.put("/", "key1", "value"); // incrementing op counter so we win vs doc2 - doc1.put("/", "key2", "value"); // incrementing op counter so we win vs doc2 - doc1.mark(list, "[5..11]", "xxx", "aaa") - doc1.mark(list, "[19..25]", "xxx", "aaa") - - doc2.mark(list, "[10..20]" , "xxx", "aaa") - - doc1.merge(doc2) - - let patches1 = doc1.popPatches().filter((p:any) => p.action == "mark") - - assert.deepEqual(patches1, [ - { action: 'mark', path: [ 'list' ], marks: [ - { name: 'xxx', value: 'aaa', start: 5, end: 11 }, - { name: 'xxx', value: 'aaa', start: 19, end: 25 }, - { name: 'xxx', value: 'aaa', start: 11, end: 19 }, - ] - }, - ]); - - let doc3 : Automerge = create(true); - doc3.enablePatches(true) - doc3.loadIncremental(doc1.save()) - - let patches2 = doc3.popPatches().filter((p:any) => p.action == "mark") - - assert.deepEqual(patches2, [ - { action: 'mark', path: [ 'list' ], marks: [ - { name: 'xxx', value: 'aaa', start: 5, end: 25 }, - ]} - ]); - }) - it('can get marks at a given heads', () => { - let doc1 : Automerge = create(true, "aabbcc") - doc1.enablePatches(true) - - let list = doc1.putObject("_root", "list", "") - doc1.splice(list, 0, 0, "the quick fox jumps over the lazy dog") - - let heads1 = doc1.getHeads(); - let marks1 = doc1.marks(list); - - doc1.mark(list, "[3..25]", "xxx", "aaa") - - let heads2 = doc1.getHeads(); - let marks2 = doc1.marks(list); - - doc1.mark(list, "[4..11]", "yyy", "bbb") - - let heads3 = doc1.getHeads(); - let marks3 = doc1.marks(list); - - doc1.unmark(list, "xxx", 9, 20) - - let heads4 = doc1.getHeads(); - let marks4 = doc1.marks(list); - - assert.deepEqual(marks1, doc1.marks(list,heads1)) - assert.deepEqual(marks2, doc1.marks(list,heads2)) - assert.deepEqual(marks3, doc1.marks(list,heads3)) - assert.deepEqual(marks4, doc1.marks(list,heads4)) - }) - }) -}) diff --git a/rust/automerge-wasm/test/test.ts b/rust/automerge-wasm/test/test.ts index 3e57d92c..bb4f71e3 100644 --- a/rust/automerge-wasm/test/test.ts +++ b/rust/automerge-wasm/test/test.ts @@ -1941,40 +1941,6 @@ describe('Automerge', () => { assert.deepEqual(mat.text, "ab011ij") }) - it('propogates exceptions thrown in patch callback', () => { - const doc = create(true) - doc.enablePatches(true) - let mat : any = doc.materialize("/") - doc.putObject("/", "text", "abcdefghij") - assert.throws(() => { - doc.applyPatches(mat, {}, (patches, info) => { - throw new RangeError("hello world") - }) - }, /RangeError: hello world/) - }) - - it('patch callback has correct patch info', () => { - const doc = create(true) - let mat : any = doc.materialize("/") - doc.putObject("/", "text", "abcdefghij") - - let before = doc.materialize("/") - let from = doc.getHeads() - - doc.enablePatches(true) - doc.splice("/text", 2, 2, "00") - - let after = doc.materialize("/") - let to = doc.getHeads() - - doc.applyPatches(mat, {}, (patches, info) => { - assert.deepEqual(info.before, before); - assert.deepEqual(info.after, after); - assert.deepEqual(info.from, from); - assert.deepEqual(info.to, to); - }) - }) - it('can handle utf16 text', () => { const doc = create(true) doc.enablePatches(true) diff --git a/rust/automerge/examples/watch.rs b/rust/automerge/examples/watch.rs index c2b909ee..4cd8f4ea 100644 --- a/rust/automerge/examples/watch.rs +++ b/rust/automerge/examples/watch.rs @@ -1,11 +1,11 @@ -use automerge::op_observer::HasPatches; use automerge::transaction::CommitOptions; use automerge::transaction::Transactable; use automerge::Automerge; use automerge::AutomergeError; +use automerge::Patch; +use automerge::ReadDoc; use automerge::VecOpObserver; use automerge::ROOT; -use automerge::{Patch, PatchAction}; fn main() { let mut doc = Automerge::new(); @@ -42,52 +42,64 @@ fn main() { get_changes(&doc, patches); } -fn get_changes(_doc: &Automerge, patches: Vec>) { - for Patch { obj, path, action } in patches { - match action { - PatchAction::PutMap { key, value, .. } => { +fn get_changes(doc: &Automerge, patches: Vec) { + for patch in patches { + match patch { + Patch::Put { + obj, prop, value, .. + } => { println!( "put {:?} at {:?} in obj {:?}, object path {:?}", - value, key, obj, path, + value, + prop, + obj, + doc.path_to_object(&obj) ) } - PatchAction::PutSeq { index, value, .. } => { - println!( - "put {:?} at {:?} in obj {:?}, object path {:?}", - value, index, obj, path, - ) - } - PatchAction::Insert { index, values, .. } => { + Patch::Insert { + obj, index, value, .. + } => { println!( "insert {:?} at {:?} in obj {:?}, object path {:?}", - values, index, obj, path, + value, + index, + obj, + doc.path_to_object(&obj) ) } - PatchAction::SpliceText { index, value, .. } => { + Patch::Splice { + obj, index, value, .. + } => { println!( "splice '{:?}' at {:?} in obj {:?}, object path {:?}", - value, index, obj, path, + value, + index, + obj, + doc.path_to_object(&obj) ) } - PatchAction::Increment { prop, value, .. } => { + Patch::Increment { + obj, prop, value, .. + } => { println!( "increment {:?} in obj {:?} by {:?}, object path {:?}", - prop, obj, value, path, + prop, + obj, + value, + doc.path_to_object(&obj) ) } - PatchAction::DeleteMap { key, .. } => { - println!("delete {:?} in obj {:?}, object path {:?}", key, obj, path,) - } - PatchAction::DeleteSeq { index, .. } => println!( + Patch::Delete { obj, prop, .. } => println!( "delete {:?} in obj {:?}, object path {:?}", - index, obj, path, + prop, + obj, + doc.path_to_object(&obj) ), - PatchAction::Mark { marks } => { - println!("mark {:?} in obj {:?}, object path {:?}", marks, obj, path,) - } - PatchAction::Unmark { name, start, end } => println!( - "unmark {:?} from {} to {} in obj {:?}, object path {:?}", - name, start, end, obj, path, + Patch::Expose { obj, prop, .. } => println!( + "expose {:?} in obj {:?}, object path {:?}", + prop, + obj, + doc.path_to_object(&obj) ), } } diff --git a/rust/automerge/src/autocommit.rs b/rust/automerge/src/autocommit.rs index a5685f48..ae28596e 100644 --- a/rust/automerge/src/autocommit.rs +++ b/rust/automerge/src/autocommit.rs @@ -1,7 +1,6 @@ use std::ops::RangeBounds; use crate::exid::ExId; -use crate::marks::{ExpandMark, Mark}; use crate::op_observer::{BranchableObserver, OpObserver}; use crate::sync::SyncDoc; use crate::transaction::{CommitOptions, Transactable}; @@ -281,11 +280,6 @@ impl AutoCommitWithObs { self.doc.import(s) } - #[doc(hidden)] - pub fn import_obj(&self, s: &str) -> Result { - self.doc.import_obj(s) - } - #[doc(hidden)] pub fn dump(&mut self) { self.ensure_transaction_closed(); @@ -447,18 +441,6 @@ impl ReadDoc for AutoCommitWithObs { self.doc.object_type(obj) } - fn marks>(&self, obj: O) -> Result>, AutomergeError> { - self.doc.marks(obj) - } - - fn marks_at>( - &self, - obj: O, - heads: &[ChangeHash], - ) -> Result>, AutomergeError> { - self.doc.marks_at(obj, heads) - } - fn text>(&self, obj: O) -> Result { self.doc.text(obj) } @@ -639,42 +621,6 @@ impl Transactable for AutoCommitWithObs { ) } - fn mark>( - &mut self, - obj: O, - mark: Mark<'_>, - expand: ExpandMark, - ) -> Result<(), AutomergeError> { - self.ensure_transaction_open(); - let (current, tx) = self.transaction.as_mut().unwrap(); - tx.mark( - &mut self.doc, - current.observer(), - obj.as_ref(), - mark, - expand, - ) - } - - fn unmark>( - &mut self, - obj: O, - key: &str, - start: usize, - end: usize, - ) -> Result<(), AutomergeError> { - self.ensure_transaction_open(); - let (current, tx) = self.transaction.as_mut().unwrap(); - tx.unmark( - &mut self.doc, - current.observer(), - obj.as_ref(), - key, - start, - end, - ) - } - fn base_heads(&self) -> Vec { self.doc.get_heads() } diff --git a/rust/automerge/src/automerge.rs b/rust/automerge/src/automerge.rs index c0fcfe28..0dd82253 100644 --- a/rust/automerge/src/automerge.rs +++ b/rust/automerge/src/automerge.rs @@ -4,13 +4,10 @@ use std::fmt::Debug; use std::num::NonZeroU64; use std::ops::RangeBounds; -use itertools::Itertools; - use crate::change_graph::ChangeGraph; use crate::columnar::Key as EncodedKey; use crate::exid::ExId; use crate::keys::Keys; -use crate::marks::{Mark, MarkStateMachine}; use crate::op_observer::{BranchableObserver, OpObserver}; use crate::op_set::OpSet; use crate::parents::Parents; @@ -19,13 +16,14 @@ use crate::transaction::{ self, CommitOptions, Failure, Observed, Success, Transaction, TransactionArgs, UnObserved, }; use crate::types::{ - ActorId, ChangeHash, Clock, ElemId, Export, Exportable, Key, ListEncoding, MarkData, ObjId, Op, - OpId, OpType, ScalarValue, TextEncoding, Value, + ActorId, ChangeHash, Clock, ElemId, Export, Exportable, Key, ListEncoding, ObjId, Op, OpId, + OpType, ScalarValue, TextEncoding, Value, }; use crate::{ query, AutomergeError, Change, KeysAt, ListRange, ListRangeAt, MapRange, MapRangeAt, ObjType, Prop, ReadDoc, Values, }; +use serde::Serialize; mod current_state; @@ -402,23 +400,11 @@ impl Automerge { pub(crate) fn exid_to_obj(&self, id: &ExId) -> Result<(ObjId, ObjType), AutomergeError> { match id { ExId::Root => Ok((ObjId::root(), ObjType::Map)), - ExId::Id(..) => { - let obj = ObjId(self.exid_to_opid(id)?); - if let Some(obj_type) = self.ops.object_type(&obj) { - Ok((obj, obj_type)) - } else { - Err(AutomergeError::NotAnObject) - } - } - } - } - - pub(crate) fn exid_to_opid(&self, id: &ExId) -> Result { - match id { - ExId::Root => Err(AutomergeError::Fail), ExId::Id(ctr, actor, idx) => { - if self.ops.m.actors.cache.get(*idx) == Some(actor) { - Ok(OpId::new(*ctr, *idx)) + // do a direct get here b/c this could be foriegn and not be within the array + // bounds + let obj = if self.ops.m.actors.cache.get(*idx) == Some(actor) { + ObjId(OpId::new(*ctr, *idx)) } else { // FIXME - make a real error let idx = self @@ -427,7 +413,12 @@ impl Automerge { .actors .lookup(actor) .ok_or(AutomergeError::Fail)?; - Ok(OpId::new(*ctr, idx)) + ObjId(OpId::new(*ctr, idx)) + }; + if let Some(obj_type) = self.ops.object_type(&obj) { + Ok((obj, obj_type)) + } else { + Err(AutomergeError::NotAnObject) } } } @@ -732,12 +723,7 @@ impl Automerge { obj, Op { id, - action: OpType::from_action_and_value( - c.action, - c.val, - c.mark_name, - c.expand, - ), + action: OpType::from_action_and_value(c.action, c.val), key, succ: Default::default(), pred, @@ -927,21 +913,8 @@ impl Automerge { #[doc(hidden)] pub fn import(&self, s: &str) -> Result<(ExId, ObjType), AutomergeError> { - let obj = self.import_obj(s)?; - if obj == ExId::Root { - Ok((ExId::Root, ObjType::Map)) - } else { - let obj_type = self - .object_type(&obj) - .map_err(|_| AutomergeError::InvalidObjId(s.to_owned()))?; - Ok((obj, obj_type)) - } - } - - #[doc(hidden)] - pub fn import_obj(&self, s: &str) -> Result { if s == "_root" { - Ok(ExId::Root) + Ok((ExId::Root, ObjType::Map)) } else { let n = s .find('@') @@ -957,7 +930,10 @@ impl Automerge { .lookup(&actor) .ok_or_else(|| AutomergeError::InvalidObjId(s.to_owned()))?; let obj = ExId::Id(counter, self.ops.m.actors.cache[actor].clone(), actor); - Ok(obj) + let obj_type = self + .object_type(&obj) + .map_err(|_| AutomergeError::InvalidObjId(s.to_owned()))?; + Ok((obj, obj_type)) } } @@ -991,10 +967,6 @@ impl Automerge { OpType::Make(obj) => format!("make({})", obj), OpType::Increment(obj) => format!("inc({})", obj), OpType::Delete => format!("del{}", 0), - OpType::MarkBegin(_, MarkData { name, value }) => { - format!("mark({},{})", name, value) - } - OpType::MarkEnd(_) => "/mark".to_string(), }; let pred: Vec<_> = op.pred.iter().map(|id| self.to_string(*id)).collect(); let succ: Vec<_> = op.succ.into_iter().map(|id| self.to_string(*id)).collect(); @@ -1073,18 +1045,11 @@ impl Automerge { }; if op.insert { - if op.is_mark() { - if let OpType::MarkEnd(_) = op.action { - let q = self - .ops - .search(obj, query::SeekMark::new(op.id.prev(), pos, encoding)); - observer.mark(self, ex_obj, q.marks.into_iter()); - } - } else if obj_type == Some(ObjType::Text) { + if obj_type == Some(ObjType::Text) { observer.splice_text(self, ex_obj, seen, op.to_str()); } else { let value = (op.value(), self.ops.id_to_exid(op.id)); - observer.insert(self, ex_obj, seen, value, false); + observer.insert(self, ex_obj, seen, value); } } else if op.is_delete() { if let Some(winner) = &values.last() { @@ -1116,7 +1081,7 @@ impl Automerge { .unwrap_or(false); let value = (op.value(), self.ops.id_to_exid(op.id)); if op.is_list_op() && !had_value_before { - observer.insert(self, ex_obj, seen, value, false); + observer.insert(self, ex_obj, seen, value); } else if just_conflict { observer.flag_conflict(self, ex_obj, key); } else { @@ -1344,64 +1309,6 @@ impl ReadDoc for Automerge { Ok(buffer) } - fn marks>(&self, obj: O) -> Result>, AutomergeError> { - let (obj, obj_type) = self.exid_to_obj(obj.as_ref())?; - let encoding = ListEncoding::new(obj_type, self.text_encoding); - let ops_by_key = self.ops().iter_ops(&obj).group_by(|o| o.elemid_or_key()); - let mut pos = 0; - let mut marks = MarkStateMachine::default(); - - Ok(ops_by_key - .into_iter() - .filter_map(|(_key, key_ops)| { - key_ops - .filter(|o| o.visible_or_mark()) - .last() - .and_then(|o| match &o.action { - OpType::Make(_) | OpType::Put(_) => { - pos += o.width(encoding); - None - } - OpType::MarkBegin(_, data) => marks.mark_begin(o.id, pos, data, self), - OpType::MarkEnd(_) => marks.mark_end(o.id, pos, self), - OpType::Increment(_) | OpType::Delete => None, - }) - }) - .collect()) - } - - fn marks_at>( - &self, - obj: O, - heads: &[ChangeHash], - ) -> Result>, AutomergeError> { - let (obj, obj_type) = self.exid_to_obj(obj.as_ref())?; - let clock = self.clock_at(heads); - let encoding = ListEncoding::new(obj_type, self.text_encoding); - let ops_by_key = self.ops().iter_ops(&obj).group_by(|o| o.elemid_or_key()); - let mut window = query::VisWindow::default(); - let mut pos = 0; - let mut marks = MarkStateMachine::default(); - - Ok(ops_by_key - .into_iter() - .filter_map(|(_key, key_ops)| { - key_ops - .filter(|o| window.visible_at(o, pos, &clock)) - .last() - .and_then(|o| match &o.action { - OpType::Make(_) | OpType::Put(_) => { - pos += o.width(encoding); - None - } - OpType::MarkBegin(_, data) => marks.mark_begin(o.id, pos, data, self), - OpType::MarkEnd(_) => marks.mark_end(o.id, pos, self), - OpType::Increment(_) | OpType::Delete => None, - }) - }) - .collect()) - } - fn get, P: Into>( &self, obj: O, @@ -1532,3 +1439,14 @@ impl Default for Automerge { Self::new() } } + +#[derive(Serialize, Debug, Clone, PartialEq)] +pub(crate) struct SpanInfo { + pub(crate) id: ExId, + pub(crate) time: i64, + pub(crate) start: usize, + pub(crate) end: usize, + #[serde(rename = "type")] + pub(crate) span_type: String, + pub(crate) value: ScalarValue, +} diff --git a/rust/automerge/src/automerge/current_state.rs b/rust/automerge/src/automerge/current_state.rs index bb741a76..3f7f4afc 100644 --- a/rust/automerge/src/automerge/current_state.rs +++ b/rust/automerge/src/automerge/current_state.rs @@ -1,27 +1,12 @@ -use std::borrow::Cow; +use std::{borrow::Cow, collections::HashSet, iter::Peekable}; use itertools::Itertools; use crate::{ - marks::{Mark, MarkStateMachine}, - types::{Key, ListEncoding, ObjId, Op, OpId, Prop}, - Automerge, ObjType, OpObserver, OpType, Value, + types::{ElemId, Key, ListEncoding, ObjId, Op, OpId}, + ObjType, OpObserver, OpType, ScalarValue, Value, }; -#[derive(Debug, Default)] -struct TextState<'a> { - text: String, - len: usize, - marks: MarkStateMachine<'a>, - finished: Vec>, -} - -struct Put<'a> { - value: Value<'a>, - key: Key, - id: OpId, -} - /// Traverse the "current" state of the document, notifying `observer` /// /// The "current" state of the document is the set of visible operations. This function will @@ -32,8 +17,7 @@ struct Put<'a> { /// /// Due to only notifying of visible operations the observer will only be called with `put`, /// `insert`, and `splice`, operations. - -pub(crate) fn observe_current_state(doc: &Automerge, observer: &mut O) { +pub(super) fn observe_current_state(doc: &crate::Automerge, observer: &mut O) { // The OpSet already exposes operations in the order they appear in the document. // `OpSet::iter_objs` iterates over the objects in causal order, this means that parent objects // will always appear before their children. Furthermore, the operations within each object are @@ -42,170 +26,321 @@ pub(crate) fn observe_current_state(doc: &Automerge, observer: &m // Effectively then we iterate over each object, then we group the operations in the object by // key and for each key find the visible operations for that key. Then we notify the observer // for each of those visible operations. + let mut visible_objs = HashSet::new(); + visible_objs.insert(ObjId::root()); for (obj, typ, ops) in doc.ops().iter_objs() { + if !visible_objs.contains(obj) { + continue; + } + let ops_by_key = ops.group_by(|o| o.key); + let actions = ops_by_key + .into_iter() + .flat_map(|(key, key_ops)| key_actions(key, key_ops)); if typ == ObjType::Text && !observer.text_as_seq() { - observe_text(doc, observer, obj, ops) - } else if typ.is_sequence() { - observe_list(doc, observer, obj, ops); + track_new_objs_and_notify( + &mut visible_objs, + doc, + obj, + typ, + observer, + text_actions(actions), + ) + } else if typ == ObjType::List { + track_new_objs_and_notify( + &mut visible_objs, + doc, + obj, + typ, + observer, + list_actions(actions), + ) } else { - observe_map(doc, observer, obj, ops); + track_new_objs_and_notify(&mut visible_objs, doc, obj, typ, observer, actions) } } } -fn observe_text<'a, I: Iterator, O: OpObserver>( - doc: &'a Automerge, - observer: &mut O, +fn track_new_objs_and_notify, O: OpObserver>( + visible_objs: &mut HashSet, + doc: &crate::Automerge, obj: &ObjId, - ops: I, + typ: ObjType, + observer: &mut O, + actions: I, ) { let exid = doc.id_to_exid(obj.0); - let ops_by_key = ops.group_by(|o| o.elemid_or_key()); - let encoding = ListEncoding::Text(doc.text_encoding()); - let state = TextState::default(); - let state = ops_by_key - .into_iter() - .fold(state, |mut state, (_key, key_ops)| { - if let Some(o) = key_ops.filter(|o| o.visible_or_mark()).last() { - match &o.action { - OpType::Make(_) | OpType::Put(_) => { - state.text.push_str(o.to_str()); - state.len += o.width(encoding); - } - OpType::MarkBegin(_, data) => { - if let Some(mark) = state.marks.mark_begin(o.id, state.len, data, doc) { - state.finished.push(mark); - } - } - OpType::MarkEnd(_) => { - if let Some(mark) = state.marks.mark_end(o.id, state.len, doc) { - state.finished.push(mark); - } - } - OpType::Increment(_) | OpType::Delete => {} + for action in actions { + if let Some(obj) = action.made_object() { + visible_objs.insert(obj); + } + action.notify_observer(doc, &exid, obj, typ, observer); + } +} + +trait Action { + /// Notify an observer of whatever this action does + fn notify_observer( + self, + doc: &crate::Automerge, + exid: &crate::ObjId, + obj: &ObjId, + typ: ObjType, + observer: &mut O, + ); + + /// If this action created an object, return the ID of that object + fn made_object(&self) -> Option; +} + +fn key_actions<'a, I: Iterator>( + key: Key, + key_ops: I, +) -> impl Iterator> { + #[derive(Clone)] + enum CurrentOp<'a> { + Put { + value: Value<'a>, + id: OpId, + conflicted: bool, + }, + Insert(Value<'a>, OpId), + } + let current_ops = key_ops + .filter(|o| o.visible()) + .filter_map(|o| match o.action { + OpType::Make(obj_type) => { + let value = Value::Object(obj_type); + if o.insert { + Some(CurrentOp::Insert(value, o.id)) + } else { + Some(CurrentOp::Put { + value, + id: o.id, + conflicted: false, + }) } } - state - }); - observer.splice_text(doc, exid.clone(), 0, state.text.as_str()); - observer.mark(doc, exid, state.finished.into_iter()); -} - -fn observe_list<'a, I: Iterator, O: OpObserver>( - doc: &'a Automerge, - observer: &mut O, - obj: &ObjId, - ops: I, -) { - let exid = doc.id_to_exid(obj.0); - let mut marks = MarkStateMachine::default(); - let ops_by_key = ops.group_by(|o| o.elemid_or_key()); - let mut len = 0; - let mut finished = Vec::new(); - ops_by_key - .into_iter() - .filter_map(|(_key, key_ops)| { - key_ops - .filter(|o| o.visible_or_mark()) - .filter_map(|o| match &o.action { - OpType::Make(obj_type) => Some((Value::Object(*obj_type), o.id)), - OpType::Put(value) => Some((Value::Scalar(Cow::Borrowed(value)), o.id)), - OpType::MarkBegin(_, data) => { - if let Some(mark) = marks.mark_begin(o.id, len, data, doc) { - // side effect - finished.push(mark) - } - None - } - OpType::MarkEnd(_) => { - if let Some(mark) = marks.mark_end(o.id, len, doc) { - // side effect - finished.push(mark) - } - None - } - _ => None, - }) - .enumerate() - .last() - .map(|value| { - let pos = len; - len += 1; // increment - side effect - (pos, value) - }) - }) - .for_each(|(index, (val_enum, (value, opid)))| { - let tagged_value = (value, doc.id_to_exid(opid)); - let conflict = val_enum > 0; - observer.insert(doc, exid.clone(), index, tagged_value, conflict); - }); - observer.mark(doc, exid, finished.into_iter()); -} - -fn observe_map_key<'a, I: Iterator>( - (key, key_ops): (Key, I), -) -> Option<(usize, Put<'a>)> { - key_ops - .filter(|o| o.visible()) - .filter_map(|o| match &o.action { - OpType::Make(obj_type) => { - let value = Value::Object(*obj_type); - Some(Put { - value, - key, - id: o.id, - }) - } - OpType::Put(value) => { + OpType::Put(ref value) => { let value = Value::Scalar(Cow::Borrowed(value)); - Some(Put { - value, - key, - id: o.id, - }) + if o.insert { + Some(CurrentOp::Insert(value, o.id)) + } else { + Some(CurrentOp::Put { + value, + id: o.id, + conflicted: false, + }) + } } _ => None, + }); + current_ops + .coalesce(|previous, current| match (previous, current) { + (CurrentOp::Put { .. }, CurrentOp::Put { value, id, .. }) => Ok(CurrentOp::Put { + value, + id, + conflicted: true, + }), + (previous, current) => Err((previous, current)), + }) + .map(move |op| match op { + CurrentOp::Put { + value, + id, + conflicted, + } => SimpleAction::Put { + prop: key, + tagged_value: (value, id), + conflict: conflicted, + }, + CurrentOp::Insert(val, id) => SimpleAction::Insert { + elem_id: ElemId(id), + tagged_value: (val, id), + }, }) - .enumerate() - .last() } -fn observe_map<'a, I: Iterator, O: OpObserver>( - doc: &'a Automerge, - observer: &mut O, - obj: &ObjId, - ops: I, -) { - let exid = doc.id_to_exid(obj.0); - let ops_by_key = ops.group_by(|o| o.key); - ops_by_key - .into_iter() - .filter_map(observe_map_key) - .filter_map(|(i, put)| { - let tagged_value = (put.value, doc.id_to_exid(put.id)); - let prop = doc - .ops() - .m - .props - .safe_get(put.key.prop_index()?) - .map(|s| Prop::Map(s.to_string()))?; - let conflict = i > 0; - Some((tagged_value, prop, conflict)) - }) - .for_each(|(tagged_value, prop, conflict)| { - observer.put(doc, exid.clone(), prop, tagged_value, conflict); - }); +/// Either a "put" or "insert" action. i.e. not splicing for text values +enum SimpleAction<'a> { + Put { + prop: Key, + tagged_value: (Value<'a>, OpId), + conflict: bool, + }, + Insert { + elem_id: ElemId, + tagged_value: (Value<'a>, OpId), + }, +} + +impl<'a> Action for SimpleAction<'a> { + fn notify_observer( + self, + doc: &crate::Automerge, + exid: &crate::ObjId, + obj: &ObjId, + typ: ObjType, + observer: &mut O, + ) { + let encoding = match typ { + ObjType::Text => ListEncoding::Text(doc.text_encoding()), + _ => ListEncoding::List, + }; + match self { + Self::Put { + prop, + tagged_value, + conflict, + } => { + let tagged_value = (tagged_value.0, doc.id_to_exid(tagged_value.1)); + let prop = doc.ops().export_key(*obj, prop, encoding).unwrap(); + observer.put(doc, exid.clone(), prop, tagged_value, conflict); + } + Self::Insert { + elem_id, + tagged_value: (value, opid), + } => { + let index = doc + .ops() + .search(obj, crate::query::ElemIdPos::new(elem_id, encoding)) + .index() + .unwrap(); + let tagged_value = (value, doc.id_to_exid(opid)); + observer.insert(doc, doc.id_to_exid(obj.0), index, tagged_value); + } + } + } + + fn made_object(&self) -> Option { + match self { + Self::Put { + tagged_value: (Value::Object(_), id), + .. + } => Some((*id).into()), + Self::Insert { + tagged_value: (Value::Object(_), id), + .. + } => Some((*id).into()), + _ => None, + } + } +} + +/// An `Action` which splices for text values +enum TextAction<'a> { + Action(SimpleAction<'a>), + Splice { start: ElemId, chars: String }, +} + +impl<'a> Action for TextAction<'a> { + fn notify_observer( + self, + doc: &crate::Automerge, + exid: &crate::ObjId, + obj: &ObjId, + typ: ObjType, + observer: &mut O, + ) { + match self { + Self::Action(action) => action.notify_observer(doc, exid, obj, typ, observer), + Self::Splice { start, chars } => { + let index = doc + .ops() + .search( + obj, + crate::query::ElemIdPos::new( + start, + ListEncoding::Text(doc.text_encoding()), + ), + ) + .index() + .unwrap(); + observer.splice_text(doc, doc.id_to_exid(obj.0), index, chars.as_str()); + } + } + } + + fn made_object(&self) -> Option { + match self { + Self::Action(action) => action.made_object(), + _ => None, + } + } +} + +fn list_actions<'a, I: Iterator>>( + actions: I, +) -> impl Iterator> { + actions.map(|a| match a { + SimpleAction::Put { + prop: Key::Seq(elem_id), + tagged_value, + .. + } => SimpleAction::Insert { + elem_id, + tagged_value, + }, + a => a, + }) +} + +/// Condense consecutive `SimpleAction::Insert` actions into one `TextAction::Splice` +fn text_actions<'a, I>(actions: I) -> impl Iterator> +where + I: Iterator>, +{ + TextActions { + ops: actions.peekable(), + } +} + +struct TextActions<'a, I: Iterator>> { + ops: Peekable, +} + +impl<'a, I: Iterator>> Iterator for TextActions<'a, I> { + type Item = TextAction<'a>; + + fn next(&mut self) -> Option { + if let Some(SimpleAction::Insert { .. }) = self.ops.peek() { + let (start, value) = match self.ops.next() { + Some(SimpleAction::Insert { + tagged_value: (value, opid), + .. + }) => (opid, value), + _ => unreachable!(), + }; + let mut chars = match value { + Value::Scalar(Cow::Borrowed(ScalarValue::Str(s))) => s.to_string(), + _ => "\u{fffc}".to_string(), + }; + while let Some(SimpleAction::Insert { .. }) = self.ops.peek() { + if let Some(SimpleAction::Insert { + tagged_value: (value, _), + .. + }) = self.ops.next() + { + match value { + Value::Scalar(Cow::Borrowed(ScalarValue::Str(s))) => chars.push_str(s), + _ => chars.push('\u{fffc}'), + } + } + } + Some(TextAction::Splice { + start: ElemId(start), + chars, + }) + } else { + self.ops.next().map(TextAction::Action) + } + } } #[cfg(test)] mod tests { use std::{borrow::Cow, fs}; - use crate::{ - marks::Mark, transaction::Transactable, Automerge, ObjType, OpObserver, Prop, ReadDoc, - Value, - }; - //use crate::{transaction::Transactable, Automerge, ObjType, OpObserver, Prop, ReadDoc, Value}; + use crate::{transaction::Transactable, Automerge, ObjType, OpObserver, Prop, ReadDoc, Value}; // Observer ops often carry a "tagged value", which is a value and the OpID of the op which // created that value. For a lot of values (i.e. any scalar value) we don't care about the @@ -355,7 +490,6 @@ mod tests { objid: crate::ObjId, index: usize, tagged_value: (crate::Value<'_>, crate::ObjId), - _conflict: bool, ) { self.ops.push(ObserverCall::Insert { obj: objid, @@ -432,24 +566,6 @@ mod tests { fn text_as_seq(&self) -> bool { self.text_as_seq } - - fn mark<'a, R: ReadDoc, M: Iterator>>( - &mut self, - _doc: &R, - _objid: crate::ObjId, - _mark: M, - ) { - } - - fn unmark( - &mut self, - _doc: &R, - _objid: crate::ObjId, - _name: &str, - _start: usize, - _end: usize, - ) { - } } #[test] diff --git a/rust/automerge/src/automerge/tests.rs b/rust/automerge/src/automerge/tests.rs index e2863c3f..3511c4ed 100644 --- a/rust/automerge/src/automerge/tests.rs +++ b/rust/automerge/src/automerge/tests.rs @@ -7,9 +7,6 @@ use crate::transaction::Transactable; use crate::*; use std::convert::TryInto; -use crate::op_observer::HasPatches; -use test_log::test; - #[test] fn insert_op() -> Result<(), AutomergeError> { let mut doc = Automerge::new(); @@ -1482,18 +1479,15 @@ fn observe_counter_change_application_overwrite() { assert_eq!( doc3.observer().take_patches(), - vec![Patch { + vec![Patch::Put { obj: ExId::Root, path: vec![], - action: PatchAction::PutMap { - key: "counter".into(), - value: ( - ScalarValue::Str("mystring".into()).into(), - ExId::Id(2, doc2.get_actor().clone(), 1) - ), - conflict: false, - expose: false - } + prop: Prop::Map("counter".into()), + value: ( + ScalarValue::Str("mystring".into()).into(), + ExId::Id(2, doc2.get_actor().clone(), 1) + ), + conflict: false }] ); @@ -1520,29 +1514,29 @@ fn observe_counter_change_application() { new_doc.observer().take_patches(); new_doc.apply_changes(changes).unwrap(); assert_eq!( - new_doc - .observer() - .take_patches() - .into_iter() - .map(|p| p.action) - .collect::>(), + new_doc.observer().take_patches(), vec![ - PatchAction::PutMap { - key: "counter".into(), + Patch::Put { + obj: ExId::Root, + path: vec![], + prop: Prop::Map("counter".into()), value: ( ScalarValue::counter(1).into(), ExId::Id(1, doc.get_actor().clone(), 0) ), - conflict: false, - expose: false, + conflict: false }, - PatchAction::Increment { + Patch::Increment { + obj: ExId::Root, + path: vec![], prop: Prop::Map("counter".into()), - value: 2, + value: (2, ExId::Id(2, doc.get_actor().clone(), 0)), }, - PatchAction::Increment { + Patch::Increment { + obj: ExId::Root, + path: vec![], prop: Prop::Map("counter".into()), - value: 5, + value: (5, ExId::Id(3, doc.get_actor().clone(), 0)), } ] ); diff --git a/rust/automerge/src/change.rs b/rust/automerge/src/change.rs index a6ab112a..be467a84 100644 --- a/rust/automerge/src/change.rs +++ b/rust/automerge/src/change.rs @@ -255,18 +255,6 @@ mod convert_expanded { None => Cow::Owned(ScalarValue::Null), } } - - fn expand(&self) -> bool { - self.action.expand() - } - - fn mark_name(&self) -> Option> { - if let legacy::OpType::MarkBegin(legacy::MarkData { name, .. }) = &self.action { - Some(Cow::Borrowed(name)) - } else { - None - } - } } impl<'a> convert::OpId<&'a ActorId> for &'a legacy::OpId { @@ -290,12 +278,7 @@ impl From<&Change> for crate::ExpandedChange { let operations = c .iter_ops() .map(|o| crate::legacy::Op { - action: crate::legacy::OpType::from_parts(crate::legacy::OpTypeParts { - action: o.action, - value: o.val, - expand: o.expand, - mark_name: o.mark_name, - }), + action: crate::types::OpType::from_action_and_value(o.action, o.val), insert: o.insert, key: match o.key { StoredKey::Elem(e) if e.is_head() => { diff --git a/rust/automerge/src/columnar/column_range.rs b/rust/automerge/src/columnar/column_range.rs index 709cbe9a..5762ed14 100644 --- a/rust/automerge/src/columnar/column_range.rs +++ b/rust/automerge/src/columnar/column_range.rs @@ -3,7 +3,7 @@ pub(crate) use rle::RleRange; mod delta; pub(crate) use delta::DeltaRange; mod boolean; -pub(crate) use boolean::{BooleanRange, MaybeBooleanRange}; +pub(crate) use boolean::BooleanRange; mod raw; pub(crate) use raw::RawRange; mod opid; diff --git a/rust/automerge/src/columnar/column_range/boolean.rs b/rust/automerge/src/columnar/column_range/boolean.rs index b2530462..3cefaf0d 100644 --- a/rust/automerge/src/columnar/column_range/boolean.rs +++ b/rust/automerge/src/columnar/column_range/boolean.rs @@ -1,8 +1,6 @@ use std::{borrow::Cow, ops::Range}; -use crate::columnar::encoding::{ - BooleanDecoder, BooleanEncoder, MaybeBooleanDecoder, MaybeBooleanEncoder, -}; +use crate::columnar::encoding::{BooleanDecoder, BooleanEncoder}; #[derive(Clone, Debug, PartialEq)] pub(crate) struct BooleanRange(Range); @@ -40,44 +38,3 @@ impl From for Range { r.0 } } - -#[derive(Clone, Debug, PartialEq)] -pub struct MaybeBooleanRange(Range); - -impl MaybeBooleanRange { - pub(crate) fn decoder<'a>(&self, data: &'a [u8]) -> MaybeBooleanDecoder<'a> { - MaybeBooleanDecoder::from(Cow::Borrowed(&data[self.0.clone()])) - } - - pub(crate) fn encode>(items: I, out: &mut Vec) -> Self { - let start = out.len(); - let mut encoder = MaybeBooleanEncoder::from_sink(out); - for i in items { - encoder.append(i); - } - let (_, len) = encoder.finish(); - (start..(start + len)).into() - } - - pub(crate) fn is_empty(&self) -> bool { - self.0.is_empty() - } -} - -impl AsRef> for MaybeBooleanRange { - fn as_ref(&self) -> &Range { - &self.0 - } -} - -impl From> for MaybeBooleanRange { - fn from(r: Range) -> MaybeBooleanRange { - MaybeBooleanRange(r) - } -} - -impl From for Range { - fn from(r: MaybeBooleanRange) -> Range { - r.0 - } -} diff --git a/rust/automerge/src/columnar/encoding.rs b/rust/automerge/src/columnar/encoding.rs index e15600d7..c9435448 100644 --- a/rust/automerge/src/columnar/encoding.rs +++ b/rust/automerge/src/columnar/encoding.rs @@ -4,9 +4,7 @@ pub(crate) use raw::{RawDecoder, RawEncoder}; mod rle; pub(crate) use rle::{RleDecoder, RleEncoder}; mod boolean; -pub(crate) use boolean::{ - BooleanDecoder, BooleanEncoder, MaybeBooleanDecoder, MaybeBooleanEncoder, -}; +pub(crate) use boolean::{BooleanDecoder, BooleanEncoder}; mod delta; pub(crate) use delta::{DeltaDecoder, DeltaEncoder}; pub(crate) mod leb128; diff --git a/rust/automerge/src/columnar/encoding/boolean.rs b/rust/automerge/src/columnar/encoding/boolean.rs index 00f27c16..26cb1838 100644 --- a/rust/automerge/src/columnar/encoding/boolean.rs +++ b/rust/automerge/src/columnar/encoding/boolean.rs @@ -100,72 +100,6 @@ impl<'a> Iterator for BooleanDecoder<'a> { } } -/// Like a `BooleanEncoder` but if all the values in the column are `false` then will return an -/// empty range rather than a range with `count` false values. -pub(crate) struct MaybeBooleanEncoder { - encoder: BooleanEncoder, - all_false: bool, -} - -impl MaybeBooleanEncoder> { - pub(crate) fn new() -> MaybeBooleanEncoder> { - MaybeBooleanEncoder::from_sink(Vec::new()) - } -} - -impl MaybeBooleanEncoder { - pub(crate) fn from_sink(buf: S) -> MaybeBooleanEncoder { - MaybeBooleanEncoder { - encoder: BooleanEncoder::from_sink(buf), - all_false: true, - } - } - - pub(crate) fn append(&mut self, value: bool) { - if value { - self.all_false = false; - } - self.encoder.append(value); - } - - pub(crate) fn finish(self) -> (S, usize) { - if self.all_false { - (self.encoder.buf, 0) - } else { - self.encoder.finish() - } - } -} - -/// Like a `BooleanDecoder` but if the underlying range is empty then just returns an infinite -/// sequence of `None` -#[derive(Clone, Debug)] -pub(crate) struct MaybeBooleanDecoder<'a>(BooleanDecoder<'a>); - -impl<'a> From> for MaybeBooleanDecoder<'a> { - fn from(bytes: Cow<'a, [u8]>) -> Self { - MaybeBooleanDecoder(BooleanDecoder::from(bytes)) - } -} - -impl<'a> From<&'a [u8]> for MaybeBooleanDecoder<'a> { - fn from(d: &'a [u8]) -> Self { - Cow::Borrowed(d).into() - } -} - -impl<'a> Iterator for MaybeBooleanDecoder<'a> { - type Item = Result, raw::Error>; - - fn next(&mut self) -> Option { - if self.0.decoder.is_empty() { - None - } else { - self.0.next().transpose().map(Some).transpose() - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/rust/automerge/src/columnar/encoding/raw.rs b/rust/automerge/src/columnar/encoding/raw.rs index 5c46ef1e..b86443e5 100644 --- a/rust/automerge/src/columnar/encoding/raw.rs +++ b/rust/automerge/src/columnar/encoding/raw.rs @@ -59,10 +59,6 @@ impl<'a> RawDecoder<'a> { pub(crate) fn done(&self) -> bool { self.offset >= self.data.len() } - - pub(crate) fn is_empty(&self) -> bool { - self.data.is_empty() - } } impl<'a> From<&'a [u8]> for RawDecoder<'a> { diff --git a/rust/automerge/src/legacy/mod.rs b/rust/automerge/src/legacy/mod.rs index d4fb1fd0..6e6acec5 100644 --- a/rust/automerge/src/legacy/mod.rs +++ b/rust/automerge/src/legacy/mod.rs @@ -3,7 +3,7 @@ mod utility_impls; use std::num::NonZeroU64; -pub(crate) use crate::types::{ActorId, ChangeHash, ObjType, ScalarValue}; +pub(crate) use crate::types::{ActorId, ChangeHash, ObjType, OpType, ScalarValue}; pub(crate) use crate::value::DataType; use serde::{Deserialize, Serialize}; @@ -204,96 +204,6 @@ where } } -pub(crate) struct OpTypeParts { - pub(crate) action: u64, - pub(crate) value: ScalarValue, - pub(crate) expand: bool, - pub(crate) mark_name: Option, -} - -// Like `types::OpType` except using a String for mark names -#[derive(PartialEq, Debug, Clone)] -pub enum OpType { - Make(ObjType), - Delete, - Increment(i64), - Put(ScalarValue), - MarkBegin(MarkData), - MarkEnd(bool), -} - -impl OpType { - /// Create a new legacy OpType - /// - /// This is really only meant to be used to convert from a crate::Change to a - /// crate::legacy::Change, so the arguments should all have been validated. Consequently it - /// does not return an error and instead panics on the following conditions - /// - /// # Panics - /// - /// * If The action index is unrecognized - /// * If the action index indicates that the value should be numeric but the value is not a - /// number - pub(crate) fn from_parts( - OpTypeParts { - action, - value, - expand, - mark_name, - }: OpTypeParts, - ) -> Self { - match action { - 0 => Self::Make(ObjType::Map), - 1 => Self::Put(value), - 2 => Self::Make(ObjType::List), - 3 => Self::Delete, - 4 => Self::Make(ObjType::Text), - 5 => match value { - ScalarValue::Int(i) => Self::Increment(i), - ScalarValue::Uint(i) => Self::Increment(i as i64), - _ => panic!("non numeric value for integer action"), - }, - 6 => Self::Make(ObjType::Table), - 7 => match mark_name { - Some(name) => Self::MarkBegin(MarkData { - name, - value, - expand, - }), - None => Self::MarkEnd(expand), - }, - other => panic!("unknown action type {}", other), - } - } - - pub(crate) fn action_index(&self) -> u64 { - match self { - Self::Make(ObjType::Map) => 0, - Self::Put(_) => 1, - Self::Make(ObjType::List) => 2, - Self::Delete => 3, - Self::Make(ObjType::Text) => 4, - Self::Increment(_) => 5, - Self::Make(ObjType::Table) => 6, - Self::MarkBegin(_) | Self::MarkEnd(_) => 7, - } - } - - pub(crate) fn expand(&self) -> bool { - matches!( - self, - Self::MarkBegin(MarkData { expand: true, .. }) | Self::MarkEnd(true) - ) - } -} - -#[derive(PartialEq, Debug, Clone)] -pub struct MarkData { - pub name: smol_str::SmolStr, - pub value: ScalarValue, - pub expand: bool, -} - #[derive(PartialEq, Debug, Clone)] pub struct Op { pub action: OpType, @@ -307,7 +217,6 @@ impl Op { pub fn primitive_value(&self) -> Option { match &self.action { OpType::Put(v) => Some(v.clone()), - OpType::MarkBegin(MarkData { value, .. }) => Some(value.clone()), OpType::Increment(i) => Some(ScalarValue::Int(*i)), _ => None, } diff --git a/rust/automerge/src/legacy/serde_impls/op.rs b/rust/automerge/src/legacy/serde_impls/op.rs index d42879cd..a3719fd6 100644 --- a/rust/automerge/src/legacy/serde_impls/op.rs +++ b/rust/automerge/src/legacy/serde_impls/op.rs @@ -5,9 +5,7 @@ use serde::{ }; use super::read_field; -use crate::legacy::{ - DataType, Key, MarkData, ObjType, ObjectId, Op, OpId, OpType, ScalarValue, SortedVec, -}; +use crate::legacy::{DataType, Key, ObjType, ObjectId, Op, OpId, OpType, ScalarValue, SortedVec}; impl Serialize for Op { fn serialize(&self, serializer: S) -> Result @@ -52,16 +50,6 @@ impl Serialize for Op { OpType::Increment(n) => op.serialize_field("value", &n)?, OpType::Put(ScalarValue::Counter(c)) => op.serialize_field("value", &c.start)?, OpType::Put(value) => op.serialize_field("value", &value)?, - OpType::MarkBegin(MarkData { - name, - value, - expand, - }) => { - op.serialize_field("name", &name)?; - op.serialize_field("value", &value)?; - op.serialize_field("expand", &expand)? - } - OpType::MarkEnd(expand) => op.serialize_field("expand", &expand)?, _ => {} } op.serialize_field("pred", &self.pred)?; @@ -83,8 +71,6 @@ pub(crate) enum RawOpType { Del, Inc, Set, - MarkBegin, - MarkEnd, } impl Serialize for RawOpType { @@ -100,8 +86,6 @@ impl Serialize for RawOpType { RawOpType::Del => "del", RawOpType::Inc => "inc", RawOpType::Set => "set", - RawOpType::MarkBegin => "markBegin", - RawOpType::MarkEnd => "markEnd", }; serializer.serialize_str(s) } @@ -120,8 +104,8 @@ impl<'de> Deserialize<'de> for RawOpType { "del", "inc", "set", - "markBegin", - "markEnd", + "mark", + "unmark", ]; // TODO: Probably more efficient to deserialize to a `&str` let raw_type = String::deserialize(deserializer)?; @@ -133,8 +117,6 @@ impl<'de> Deserialize<'de> for RawOpType { "del" => Ok(RawOpType::Del), "inc" => Ok(RawOpType::Inc), "set" => Ok(RawOpType::Set), - "markBegin" => Ok(RawOpType::MarkBegin), - "markEnd" => Ok(RawOpType::MarkEnd), other => Err(Error::unknown_variant(other, VARIANTS)), } } @@ -207,7 +189,24 @@ impl<'de> Deserialize<'de> for Op { RawOpType::MakeList => OpType::Make(ObjType::List), RawOpType::MakeText => OpType::Make(ObjType::Text), RawOpType::Del => OpType::Delete, - RawOpType::Set => OpType::Put(unwrap_value(value, datatype)?), + RawOpType::Set => { + 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::Put(value) + } RawOpType::Inc => match value.flatten() { Some(ScalarValue::Int(n)) => Ok(OpType::Increment(n)), Some(ScalarValue::Uint(n)) => Ok(OpType::Increment(n as i64)), @@ -231,18 +230,6 @@ impl<'de> Deserialize<'de> for Op { } None => Err(Error::missing_field("value")), }?, - RawOpType::MarkBegin => { - let name = name.ok_or_else(|| Error::missing_field("name"))?; - let name = smol_str::SmolStr::new(name); - let expand = expand.unwrap_or(false); - let value = unwrap_value(value, datatype)?; - OpType::MarkBegin(MarkData { - name, - value, - expand, - }) - } - RawOpType::MarkEnd => OpType::MarkEnd(expand.unwrap_or(false)), }; Ok(Op { action, @@ -257,27 +244,6 @@ impl<'de> Deserialize<'de> for Op { } } -fn unwrap_value( - value: Option>, - datatype: Option, -) -> Result { - 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 { - Ok(value - .ok_or_else(|| Error::missing_field("value"))? - .unwrap_or(ScalarValue::Null)) - } -} - #[cfg(test)] mod tests { use std::str::FromStr; diff --git a/rust/automerge/src/legacy/serde_impls/op_type.rs b/rust/automerge/src/legacy/serde_impls/op_type.rs index 32825119..b054bad7 100644 --- a/rust/automerge/src/legacy/serde_impls/op_type.rs +++ b/rust/automerge/src/legacy/serde_impls/op_type.rs @@ -1,7 +1,7 @@ use serde::{Serialize, Serializer}; use super::op::RawOpType; -use crate::{legacy::OpType, ObjType}; +use crate::{ObjType, OpType}; impl Serialize for OpType { fn serialize(&self, serializer: S) -> Result @@ -18,8 +18,6 @@ impl Serialize for OpType { OpType::Delete => RawOpType::Del, OpType::Increment(_) => RawOpType::Inc, OpType::Put(_) => RawOpType::Set, - OpType::MarkBegin(_) => RawOpType::MarkBegin, - OpType::MarkEnd(_) => RawOpType::MarkEnd, }; raw_type.serialize(serializer) } diff --git a/rust/automerge/src/lib.rs b/rust/automerge/src/lib.rs index ff6f6daa..cbb535af 100644 --- a/rust/automerge/src/lib.rs +++ b/rust/automerge/src/lib.rs @@ -258,14 +258,12 @@ mod list_range; mod list_range_at; mod map_range; mod map_range_at; -pub mod marks; pub mod op_observer; mod op_set; mod op_tree; mod parents; mod query; mod read; -mod sequence_tree; mod storage; pub mod sync; pub mod transaction; @@ -290,9 +288,9 @@ pub use list_range::ListRange; pub use list_range_at::ListRangeAt; pub use map_range::MapRange; pub use map_range_at::MapRangeAt; -pub use op_observer::{ - OpObserver, Patch, PatchAction, ToggleObserver, VecOpObserver, VecOpObserver16, -}; +pub use op_observer::OpObserver; +pub use op_observer::Patch; +pub use op_observer::VecOpObserver; pub use parents::{Parent, Parents}; pub use read::ReadDoc; pub use types::{ActorId, ChangeHash, ObjType, OpType, ParseChangeHashError, Prop, TextEncoding}; diff --git a/rust/automerge/src/marks.rs b/rust/automerge/src/marks.rs deleted file mode 100644 index cb9520d9..00000000 --- a/rust/automerge/src/marks.rs +++ /dev/null @@ -1,194 +0,0 @@ -use smol_str::SmolStr; -use std::fmt; -use std::fmt::Display; - -use crate::types::OpId; -use crate::value::ScalarValue; -use crate::Automerge; -use std::borrow::Cow; - -#[derive(Debug, Clone, PartialEq)] -pub struct Mark<'a> { - pub start: usize, - pub end: usize, - pub(crate) data: Cow<'a, MarkData>, -} - -impl<'a> Mark<'a> { - pub fn new>( - name: String, - value: V, - start: usize, - end: usize, - ) -> Mark<'static> { - Mark { - data: Cow::Owned(MarkData { - name: name.into(), - value: value.into(), - }), - start, - end, - } - } - - pub(crate) fn from_data(start: usize, end: usize, data: &MarkData) -> Mark<'_> { - Mark { - data: Cow::Borrowed(data), - start, - end, - } - } - - pub fn into_owned(self) -> Mark<'static> { - Mark { - data: Cow::Owned(self.data.into_owned()), - start: self.start, - end: self.end, - } - } - - pub fn name(&self) -> &str { - self.data.name.as_str() - } - - pub fn value(&self) -> &ScalarValue { - &self.data.value - } -} - -#[derive(Debug, Clone, PartialEq, Default)] -pub(crate) struct MarkStateMachine<'a> { - state: Vec<(OpId, Mark<'a>)>, -} - -impl<'a> MarkStateMachine<'a> { - pub(crate) fn mark_begin( - &mut self, - id: OpId, - pos: usize, - data: &'a MarkData, - doc: &Automerge, - ) -> Option> { - let mut result = None; - let index = self.find(id, doc).err()?; - - let mut mark = Mark::from_data(pos, pos, data); - - if let Some(above) = Self::mark_above(&self.state, index, &mark) { - if above.value() == mark.value() { - mark.start = above.start; - } - } else if let Some(below) = Self::mark_below(&mut self.state, index, &mark) { - if below.value() == mark.value() { - mark.start = below.start; - } else { - let mut m = below.clone(); - m.end = pos; - if !m.value().is_null() { - result = Some(m); - } - } - } - - self.state.insert(index, (id, mark)); - - result - } - - pub(crate) fn mark_end(&mut self, id: OpId, pos: usize, doc: &Automerge) -> Option> { - let mut result = None; - let index = self.find(id.prev(), doc).ok()?; - - let mut mark = self.state.remove(index).1; - mark.end = pos; - - if Self::mark_above(&self.state, index, &mark).is_none() { - match Self::mark_below(&mut self.state, index, &mark) { - Some(below) if below.value() == mark.value() => {} - Some(below) => { - below.start = pos; - if !mark.value().is_null() { - result = Some(mark.clone()); - } - } - None => { - if !mark.value().is_null() { - result = Some(mark.clone()); - } - } - } - } - - result - } - - fn find(&self, target: OpId, doc: &Automerge) -> Result { - let metadata = &doc.ops().m; - self.state - .binary_search_by(|probe| metadata.lamport_cmp(probe.0, target)) - } - - fn mark_above<'b>( - state: &'b [(OpId, Mark<'a>)], - index: usize, - mark: &Mark<'a>, - ) -> Option<&'b Mark<'a>> { - Some( - &state[index..] - .iter() - .find(|(_, m)| m.name() == mark.name())? - .1, - ) - } - - fn mark_below<'b>( - state: &'b mut [(OpId, Mark<'a>)], - index: usize, - mark: &Mark<'a>, - ) -> Option<&'b mut Mark<'a>> { - Some( - &mut state[0..index] - .iter_mut() - .filter(|(_, m)| m.data.name == mark.data.name) - .last()? - .1, - ) - } -} - -#[derive(PartialEq, Debug, Clone)] -pub struct MarkData { - pub name: SmolStr, - pub value: ScalarValue, -} - -impl Display for MarkData { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "name={} value={}", self.name, self.value) - } -} - -#[derive(PartialEq, Debug, Clone, Copy)] -pub enum ExpandMark { - Left, - Right, - Both, - None, -} - -impl ExpandMark { - pub fn from(left: bool, right: bool) -> Self { - match (left, right) { - (true, true) => Self::Both, - (false, true) => Self::Right, - (true, false) => Self::Left, - (false, false) => Self::None, - } - } - pub fn left(&self) -> bool { - matches!(self, Self::Left | Self::Both) - } - pub fn right(&self) -> bool { - matches!(self, Self::Right | Self::Both) - } -} diff --git a/rust/automerge/src/op_observer.rs b/rust/automerge/src/op_observer.rs index 6870c136..5b33c21f 100644 --- a/rust/automerge/src/op_observer.rs +++ b/rust/automerge/src/op_observer.rs @@ -1,17 +1,10 @@ use crate::exid::ExId; -use crate::marks::Mark; use crate::Prop; use crate::ReadDoc; use crate::Value; mod compose; -mod patch; -mod toggle_observer; -mod vec_observer; pub use compose::compose; -pub use patch::{Patch, PatchAction}; -pub use toggle_observer::ToggleObserver; -pub use vec_observer::{HasPatches, TextRepresentation, VecOpObserver, VecOpObserver16}; /// An observer of operations applied to the document. pub trait OpObserver { @@ -28,7 +21,6 @@ pub trait OpObserver { objid: ExId, index: usize, tagged_value: (Value<'_>, ExId), - conflict: bool, ); /// Some text has been spliced into a text object @@ -119,15 +111,6 @@ pub trait OpObserver { /// - `num`: the number of sequential elements deleted fn delete_seq(&mut self, doc: &R, objid: ExId, index: usize, num: usize); - fn mark<'a, R: ReadDoc, M: Iterator>>( - &mut self, - doc: &'a R, - objid: ExId, - mark: M, - ); - - fn unmark(&mut self, doc: &R, objid: ExId, name: &str, start: usize, end: usize); - /// Whether to call sequence methods or `splice_text` when encountering changes in text /// /// Returns `false` by default @@ -163,7 +146,6 @@ impl OpObserver for () { _objid: ExId, _index: usize, _tagged_value: (Value<'_>, ExId), - _conflict: bool, ) { } @@ -198,24 +180,6 @@ impl OpObserver for () { ) { } - fn mark<'a, R: ReadDoc, M: Iterator>>( - &mut self, - _doc: &'a R, - _objid: ExId, - _mark: M, - ) { - } - - fn unmark( - &mut self, - _doc: &R, - _objid: ExId, - _name: &str, - _start: usize, - _end: usize, - ) { - } - fn delete_map(&mut self, _doc: &R, _objid: ExId, _key: &str) {} fn delete_seq(&mut self, _doc: &R, _objid: ExId, _index: usize, _num: usize) {} @@ -225,3 +189,204 @@ impl BranchableObserver for () { fn merge(&mut self, _other: &Self) {} fn branch(&self) -> Self {} } + +/// Capture operations into a [`Vec`] and store them as patches. +#[derive(Default, Debug, Clone)] +pub struct VecOpObserver { + patches: Vec, +} + +impl VecOpObserver { + /// Take the current list of patches, leaving the internal list empty and ready for new + /// patches. + pub fn take_patches(&mut self) -> Vec { + std::mem::take(&mut self.patches) + } +} + +impl OpObserver for VecOpObserver { + fn insert( + &mut self, + doc: &R, + obj: ExId, + index: usize, + (value, id): (Value<'_>, ExId), + ) { + if let Ok(p) = doc.parents(&obj) { + self.patches.push(Patch::Insert { + obj, + path: p.path(), + index, + value: (value.into_owned(), id), + }); + } + } + + fn splice_text(&mut self, doc: &R, obj: ExId, index: usize, value: &str) { + if let Ok(p) = doc.parents(&obj) { + self.patches.push(Patch::Splice { + obj, + path: p.path(), + index, + value: value.to_string(), + }) + } + } + + fn put( + &mut self, + doc: &R, + obj: ExId, + prop: Prop, + (value, id): (Value<'_>, ExId), + conflict: bool, + ) { + if let Ok(p) = doc.parents(&obj) { + self.patches.push(Patch::Put { + obj, + path: p.path(), + prop, + value: (value.into_owned(), id), + conflict, + }); + } + } + + fn expose( + &mut self, + doc: &R, + obj: ExId, + prop: Prop, + (value, id): (Value<'_>, ExId), + conflict: bool, + ) { + if let Ok(p) = doc.parents(&obj) { + self.patches.push(Patch::Expose { + obj, + path: p.path(), + prop, + value: (value.into_owned(), id), + conflict, + }); + } + } + + fn increment(&mut self, doc: &R, obj: ExId, prop: Prop, tagged_value: (i64, ExId)) { + if let Ok(p) = doc.parents(&obj) { + self.patches.push(Patch::Increment { + obj, + path: p.path(), + prop, + value: tagged_value, + }); + } + } + + fn delete_map(&mut self, doc: &R, obj: ExId, key: &str) { + if let Ok(p) = doc.parents(&obj) { + self.patches.push(Patch::Delete { + obj, + path: p.path(), + prop: Prop::Map(key.to_owned()), + num: 1, + }) + } + } + + fn delete_seq(&mut self, doc: &R, obj: ExId, index: usize, num: usize) { + if let Ok(p) = doc.parents(&obj) { + self.patches.push(Patch::Delete { + obj, + path: p.path(), + prop: Prop::Seq(index), + num, + }) + } + } +} + +impl BranchableObserver for VecOpObserver { + fn merge(&mut self, other: &Self) { + self.patches.extend_from_slice(other.patches.as_slice()) + } + + fn branch(&self) -> Self { + Self::default() + } +} + +/// A notification to the application that something has changed in a document. +#[derive(Debug, Clone, PartialEq)] +pub enum Patch { + /// Associating a new value with a prop in a map, or an existing list element + Put { + /// path to the object + path: Vec<(ExId, Prop)>, + /// The object that was put into. + obj: ExId, + /// The prop that the new value was put at. + prop: Prop, + /// The value that was put, and the id of the operation that put it there. + value: (Value<'static>, ExId), + /// Whether this put conflicts with another. + conflict: bool, + }, + /// Exposing (via delete) an old but conflicted value with a prop in a map, or a list element + Expose { + /// path to the object + path: Vec<(ExId, Prop)>, + /// The object that was put into. + obj: ExId, + /// The prop that the new value was put at. + prop: Prop, + /// The value that was put, and the id of the operation that put it there. + value: (Value<'static>, ExId), + /// Whether this put conflicts with another. + conflict: bool, + }, + /// Inserting a new element into a list + Insert { + /// path to the object + path: Vec<(ExId, Prop)>, + /// The object that was inserted into. + obj: ExId, + /// The index that the new value was inserted at. + index: usize, + /// The value that was inserted, and the id of the operation that inserted it there. + value: (Value<'static>, ExId), + }, + /// Splicing a text object + Splice { + /// path to the object + path: Vec<(ExId, Prop)>, + /// The object that was inserted into. + obj: ExId, + /// The index that the new value was inserted at. + index: usize, + /// The value that was spliced + value: String, + }, + /// Incrementing a counter. + Increment { + /// path to the object + path: Vec<(ExId, Prop)>, + /// The object that was incremented in. + obj: ExId, + /// The prop that was incremented. + prop: Prop, + /// The amount that the counter was incremented by, and the id of the operation that + /// did the increment. + value: (i64, ExId), + }, + /// Deleting an element from a list/text + Delete { + /// path to the object + path: Vec<(ExId, Prop)>, + /// The object that was deleted from. + obj: ExId, + /// The prop that was deleted. + prop: Prop, + /// number of items deleted (for seq) + num: usize, + }, +} diff --git a/rust/automerge/src/op_observer/compose.rs b/rust/automerge/src/op_observer/compose.rs index 0dfc6542..92fe3b1e 100644 --- a/rust/automerge/src/op_observer/compose.rs +++ b/rust/automerge/src/op_observer/compose.rs @@ -19,11 +19,10 @@ impl<'a, O1: OpObserver, O2: OpObserver> OpObserver for ComposeObservers<'a, O1, objid: crate::ObjId, index: usize, tagged_value: (crate::Value<'_>, crate::ObjId), - conflict: bool, ) { self.obs1 - .insert(doc, objid.clone(), index, tagged_value.clone(), conflict); - self.obs2.insert(doc, objid, index, tagged_value, conflict); + .insert(doc, objid.clone(), index, tagged_value.clone()); + self.obs2.insert(doc, objid, index, tagged_value); } fn splice_text( @@ -85,30 +84,6 @@ impl<'a, O1: OpObserver, O2: OpObserver> OpObserver for ComposeObservers<'a, O1, self.obs2.increment(doc, objid, prop, tagged_value); } - fn mark<'b, R: crate::ReadDoc, M: Iterator>>( - &mut self, - doc: &'b R, - objid: crate::ObjId, - mark: M, - ) { - let marks: Vec<_> = mark.collect(); - self.obs1 - .mark(doc, objid.clone(), marks.clone().into_iter()); - self.obs2.mark(doc, objid, marks.into_iter()); - } - - fn unmark( - &mut self, - doc: &R, - objid: crate::ObjId, - name: &str, - start: usize, - end: usize, - ) { - self.obs1.unmark(doc, objid.clone(), name, start, end); - self.obs2.unmark(doc, objid, name, start, end); - } - fn delete_map(&mut self, doc: &R, objid: crate::ObjId, key: &str) { self.obs1.delete_map(doc, objid.clone(), key); self.obs2.delete_map(doc, objid, key); diff --git a/rust/automerge/src/op_observer/patch.rs b/rust/automerge/src/op_observer/patch.rs deleted file mode 100644 index 717f680b..00000000 --- a/rust/automerge/src/op_observer/patch.rs +++ /dev/null @@ -1,57 +0,0 @@ -#![allow(dead_code)] - -use crate::{marks::Mark, ObjId, Prop, Value}; -use core::fmt::Debug; - -use crate::sequence_tree::SequenceTree; - -#[derive(Debug, Clone, PartialEq)] -pub struct Patch { - pub obj: ObjId, - pub path: Vec<(ObjId, Prop)>, - pub action: PatchAction, -} - -#[derive(Debug, Clone, PartialEq)] -pub enum PatchAction { - PutMap { - key: String, - value: (Value<'static>, ObjId), - expose: bool, - conflict: bool, - }, - PutSeq { - index: usize, - value: (Value<'static>, ObjId), - expose: bool, - conflict: bool, - }, - Insert { - index: usize, - values: SequenceTree<(Value<'static>, ObjId)>, - conflict: bool, - }, - SpliceText { - index: usize, - value: SequenceTree, - }, - Increment { - prop: Prop, - value: i64, - }, - DeleteMap { - key: String, - }, - DeleteSeq { - index: usize, - length: usize, - }, - Mark { - marks: Vec>, - }, - Unmark { - name: String, - start: usize, - end: usize, - }, -} diff --git a/rust/automerge/src/op_observer/toggle_observer.rs b/rust/automerge/src/op_observer/toggle_observer.rs deleted file mode 100644 index 6afcee9f..00000000 --- a/rust/automerge/src/op_observer/toggle_observer.rs +++ /dev/null @@ -1,178 +0,0 @@ -#![allow(dead_code)] - -use crate::ChangeHash; -use core::fmt::Debug; - -use crate::{marks::Mark, ObjId, OpObserver, Prop, ReadDoc, Value}; - -use crate::op_observer::BranchableObserver; -use crate::op_observer::{HasPatches, TextRepresentation}; - -#[derive(Debug, Clone)] -pub struct ToggleObserver { - enabled: bool, - last_heads: Option>, - observer: T, -} - -impl Default for ToggleObserver { - fn default() -> Self { - Self { - enabled: false, - last_heads: None, - observer: T::default(), - } - } -} - -impl ToggleObserver { - pub fn new(observer: T) -> Self { - ToggleObserver { - enabled: false, - last_heads: None, - observer, - } - } - - pub fn take_patches(&mut self, heads: Vec) -> (T::Patches, Vec) { - let old_heads = self.last_heads.replace(heads).unwrap_or_default(); - let patches = self.observer.take_patches(); - (patches, old_heads) - } - - pub fn with_text_rep(mut self, text_rep: TextRepresentation) -> Self { - self.observer = self.observer.with_text_rep(text_rep); - self - } - - pub fn set_text_rep(&mut self, text_rep: TextRepresentation) { - self.observer.set_text_rep(text_rep) - } - - pub fn enable(&mut self, enable: bool, heads: Vec) -> bool { - if self.enabled && !enable { - self.observer.take_patches(); - self.last_heads = Some(heads); - } - let old_enabled = self.enabled; - self.enabled = enable; - old_enabled - } - - fn get_path(&mut self, doc: &R, obj: &ObjId) -> Option> { - match doc.parents(obj) { - Ok(parents) => parents.visible_path(), - Err(e) => { - log!("error generating patch : {:?}", e); - None - } - } - } -} - -impl OpObserver for ToggleObserver { - fn insert( - &mut self, - doc: &R, - obj: ObjId, - index: usize, - tagged_value: (Value<'_>, ObjId), - conflict: bool, - ) { - if self.enabled { - self.observer - .insert(doc, obj, index, tagged_value, conflict) - } - } - - fn splice_text(&mut self, doc: &R, obj: ObjId, index: usize, value: &str) { - if self.enabled { - self.observer.splice_text(doc, obj, index, value) - } - } - - fn delete_seq(&mut self, doc: &R, obj: ObjId, index: usize, length: usize) { - if self.enabled { - self.observer.delete_seq(doc, obj, index, length) - } - } - - fn delete_map(&mut self, doc: &R, obj: ObjId, key: &str) { - if self.enabled { - self.observer.delete_map(doc, obj, key) - } - } - - fn put( - &mut self, - doc: &R, - obj: ObjId, - prop: Prop, - tagged_value: (Value<'_>, ObjId), - conflict: bool, - ) { - if self.enabled { - self.observer.put(doc, obj, prop, tagged_value, conflict) - } - } - - fn expose( - &mut self, - doc: &R, - obj: ObjId, - prop: Prop, - tagged_value: (Value<'_>, ObjId), - conflict: bool, - ) { - if self.enabled { - self.observer.expose(doc, obj, prop, tagged_value, conflict) - } - } - - fn increment( - &mut self, - doc: &R, - obj: ObjId, - prop: Prop, - tagged_value: (i64, ObjId), - ) { - if self.enabled { - self.observer.increment(doc, obj, prop, tagged_value) - } - } - - fn mark<'a, R: ReadDoc, M: Iterator>>( - &mut self, - doc: &'a R, - obj: ObjId, - mark: M, - ) { - if self.enabled { - self.observer.mark(doc, obj, mark) - } - } - - fn unmark(&mut self, doc: &R, obj: ObjId, name: &str, start: usize, end: usize) { - if self.enabled { - self.observer.unmark(doc, obj, name, start, end) - } - } - - fn text_as_seq(&self) -> bool { - self.observer.get_text_rep() == TextRepresentation::Array - } -} - -impl BranchableObserver for ToggleObserver { - fn merge(&mut self, other: &Self) { - self.observer.merge(&other.observer) - } - - fn branch(&self) -> Self { - ToggleObserver { - observer: self.observer.branch(), - last_heads: None, - enabled: self.enabled, - } - } -} diff --git a/rust/automerge/src/op_observer/vec_observer.rs b/rust/automerge/src/op_observer/vec_observer.rs deleted file mode 100644 index 9887699a..00000000 --- a/rust/automerge/src/op_observer/vec_observer.rs +++ /dev/null @@ -1,561 +0,0 @@ -#![allow(dead_code)] - -use core::fmt::Debug; - -use crate::{marks::Mark, ObjId, OpObserver, Prop, ReadDoc, ScalarValue, Value}; - -use crate::sequence_tree::SequenceTree; - -use crate::op_observer::BranchableObserver; -use crate::op_observer::{Patch, PatchAction}; - -#[derive(Debug, Copy, Clone, PartialEq)] -pub enum TextRepresentation { - Array, - String, -} - -impl TextRepresentation { - pub fn is_array(&self) -> bool { - matches!(self, TextRepresentation::Array) - } - - pub fn is_string(&self) -> bool { - matches!(self, TextRepresentation::String) - } -} - -impl std::default::Default for TextRepresentation { - fn default() -> Self { - TextRepresentation::Array - } -} - -pub(crate) trait TextIndex { - type Item: Debug + PartialEq + Clone; - type Iter<'a>: Iterator; - - fn chars(text: &str) -> Self::Iter<'_>; -} - -#[derive(Debug, Clone, Default)] -struct VecOpObserverInner { - pub(crate) patches: Vec>, - pub(crate) text_rep: TextRepresentation, -} - -#[derive(Debug, Clone, Default)] -pub struct VecOpObserver(VecOpObserverInner); - -#[derive(Debug, Clone, Default)] -pub struct VecOpObserver16(VecOpObserverInner); - -#[derive(Debug, Clone, Default)] -pub(crate) struct Utf16TextIndex; - -#[derive(Debug, Clone, Default)] -pub(crate) struct Utf8TextIndex; - -impl TextIndex for Utf8TextIndex { - type Item = char; - type Iter<'a> = std::str::Chars<'a>; - - fn chars(text: &str) -> Self::Iter<'_> { - text.chars() - } -} - -impl TextIndex for Utf16TextIndex { - type Item = u16; - type Iter<'a> = std::str::EncodeUtf16<'a>; - - fn chars(text: &str) -> Self::Iter<'_> { - text.encode_utf16() - } -} - -pub trait HasPatches { - type Patches; - - fn take_patches(&mut self) -> Self::Patches; - fn with_text_rep(self, text_rep: TextRepresentation) -> Self; - fn set_text_rep(&mut self, text_rep: TextRepresentation); - fn get_text_rep(&self) -> TextRepresentation; -} - -impl HasPatches for VecOpObserver { - type Patches = Vec>; - - fn take_patches(&mut self) -> Self::Patches { - std::mem::take(&mut self.0.patches) - } - - fn with_text_rep(mut self, text_rep: TextRepresentation) -> Self { - self.0.text_rep = text_rep; - self - } - - fn set_text_rep(&mut self, text_rep: TextRepresentation) { - self.0.text_rep = text_rep; - } - - fn get_text_rep(&self) -> TextRepresentation { - self.0.text_rep - } -} - -impl HasPatches for VecOpObserver16 { - type Patches = Vec>; - - fn take_patches(&mut self) -> Self::Patches { - std::mem::take(&mut self.0.patches) - } - - fn with_text_rep(mut self, text_rep: TextRepresentation) -> Self { - self.0.text_rep = text_rep; - self - } - - fn set_text_rep(&mut self, text_rep: TextRepresentation) { - self.0.text_rep = text_rep; - } - - fn get_text_rep(&self) -> TextRepresentation { - self.0.text_rep - } -} - -impl VecOpObserverInner { - fn get_path(&mut self, doc: &R, obj: &ObjId) -> Option> { - match doc.parents(obj) { - Ok(parents) => parents.visible_path(), - Err(e) => { - log!("error generating patch : {:?}", e); - None - } - } - } - - fn maybe_append(&mut self, obj: &ObjId) -> Option<&mut PatchAction> { - match self.patches.last_mut() { - Some(Patch { - obj: tail_obj, - action, - .. - }) if obj == tail_obj => Some(action), - _ => None, - } - } -} - -impl OpObserver for VecOpObserverInner { - fn insert( - &mut self, - doc: &R, - obj: ObjId, - index: usize, - tagged_value: (Value<'_>, ObjId), - conflict: bool, - ) { - let value = (tagged_value.0.to_owned(), tagged_value.1); - if let Some(PatchAction::Insert { - index: tail_index, - values, - .. - }) = self.maybe_append(&obj) - { - let range = *tail_index..=*tail_index + values.len(); - if range.contains(&index) { - values.insert(index - *tail_index, value); - return; - } - } - if let Some(path) = self.get_path(doc, &obj) { - let mut values = SequenceTree::new(); - values.push(value); - let action = PatchAction::Insert { - index, - values, - conflict, - }; - self.patches.push(Patch { obj, path, action }); - } - } - - fn splice_text(&mut self, doc: &R, obj: ObjId, index: usize, value: &str) { - if self.text_rep == TextRepresentation::Array { - for (offset, c) in value.chars().map(ScalarValue::from).enumerate() { - let value = (c.into(), ObjId::Root); - self.insert(doc, obj.clone(), index + offset, value, false); - } - return; - } - if let Some(PatchAction::SpliceText { - index: tail_index, - value: prev_value, - .. - }) = self.maybe_append(&obj) - { - let range = *tail_index..=*tail_index + prev_value.len(); - if range.contains(&index) { - let i = index - *tail_index; - for (n, ch) in T::chars(value).enumerate() { - prev_value.insert(i + n, ch) - } - return; - } - } - if let Some(path) = self.get_path(doc, &obj) { - let mut v = SequenceTree::new(); - for ch in T::chars(value) { - v.push(ch) - } - let action = PatchAction::SpliceText { index, value: v }; - self.patches.push(Patch { obj, path, action }); - } - } - - fn delete_seq(&mut self, doc: &R, obj: ObjId, index: usize, length: usize) { - match self.maybe_append(&obj) { - Some(PatchAction::SpliceText { - index: tail_index, - value, - .. - }) => { - let range = *tail_index..*tail_index + value.len(); - if range.contains(&index) && range.contains(&(index + length - 1)) { - for _ in 0..length { - value.remove(index - *tail_index); - } - return; - } - } - Some(PatchAction::Insert { - index: tail_index, - values, - .. - }) => { - let range = *tail_index..*tail_index + values.len(); - if range.contains(&index) && range.contains(&(index + length - 1)) { - for _ in 0..length { - values.remove(index - *tail_index); - } - return; - } - } - Some(PatchAction::DeleteSeq { - index: tail_index, - length: tail_length, - .. - }) => { - if index == *tail_index { - *tail_length += length; - return; - } - } - _ => {} - } - if let Some(path) = self.get_path(doc, &obj) { - let action = PatchAction::DeleteSeq { index, length }; - self.patches.push(Patch { obj, path, action }) - } - } - - fn delete_map(&mut self, doc: &R, obj: ObjId, key: &str) { - if let Some(path) = self.get_path(doc, &obj) { - let action = PatchAction::DeleteMap { - key: key.to_owned(), - }; - self.patches.push(Patch { obj, path, action }) - } - } - - fn put( - &mut self, - doc: &R, - obj: ObjId, - prop: Prop, - tagged_value: (Value<'_>, ObjId), - conflict: bool, - ) { - let expose = false; - if let Some(path) = self.get_path(doc, &obj) { - let value = (tagged_value.0.to_owned(), tagged_value.1); - let action = match prop { - Prop::Map(key) => PatchAction::PutMap { - key, - value, - expose, - conflict, - }, - Prop::Seq(index) => PatchAction::PutSeq { - index, - value, - expose, - conflict, - }, - }; - self.patches.push(Patch { obj, path, action }) - } - } - - fn expose( - &mut self, - doc: &R, - obj: ObjId, - prop: Prop, - tagged_value: (Value<'_>, ObjId), - conflict: bool, - ) { - let expose = true; - if let Some(path) = self.get_path(doc, &obj) { - let value = (tagged_value.0.to_owned(), tagged_value.1); - let action = match prop { - Prop::Map(key) => PatchAction::PutMap { - key, - value, - expose, - conflict, - }, - Prop::Seq(index) => PatchAction::PutSeq { - index, - value, - expose, - conflict, - }, - }; - self.patches.push(Patch { obj, path, action }) - } - } - - fn increment( - &mut self, - doc: &R, - obj: ObjId, - prop: Prop, - tagged_value: (i64, ObjId), - ) { - if let Some(path) = self.get_path(doc, &obj) { - let value = tagged_value.0; - let action = PatchAction::Increment { prop, value }; - self.patches.push(Patch { obj, path, action }) - } - } - - fn mark<'a, R: ReadDoc, M: Iterator>>( - &mut self, - doc: &'a R, - obj: ObjId, - mark: M, - ) { - if let Some(PatchAction::Mark { marks, .. }) = self.maybe_append(&obj) { - for m in mark { - marks.push(m.into_owned()) - } - return; - } - if let Some(path) = self.get_path(doc, &obj) { - let marks: Vec<_> = mark.map(|m| m.into_owned()).collect(); - if !marks.is_empty() { - let action = PatchAction::Mark { marks }; - self.patches.push(Patch { obj, path, action }); - } - } - } - - fn unmark(&mut self, doc: &R, obj: ObjId, name: &str, start: usize, end: usize) { - if let Some(path) = self.get_path(doc, &obj) { - let action = PatchAction::Unmark { - name: name.to_string(), - start, - end, - }; - self.patches.push(Patch { obj, path, action }); - } - } - - fn text_as_seq(&self) -> bool { - self.text_rep == TextRepresentation::Array - } -} - -impl BranchableObserver for VecOpObserverInner { - fn merge(&mut self, other: &Self) { - self.patches.extend_from_slice(other.patches.as_slice()) - } - - fn branch(&self) -> Self { - VecOpObserverInner { - patches: vec![], - text_rep: self.text_rep, - } - } -} - -impl OpObserver for VecOpObserver { - fn insert( - &mut self, - doc: &R, - obj: ObjId, - index: usize, - tagged_value: (Value<'_>, ObjId), - conflict: bool, - ) { - self.0.insert(doc, obj, index, tagged_value, conflict) - } - - fn splice_text(&mut self, doc: &R, obj: ObjId, index: usize, value: &str) { - self.0.splice_text(doc, obj, index, value) - } - - fn delete_seq(&mut self, doc: &R, obj: ObjId, index: usize, length: usize) { - self.0.delete_seq(doc, obj, index, length) - } - - fn delete_map(&mut self, doc: &R, obj: ObjId, key: &str) { - self.0.delete_map(doc, obj, key) - } - - fn put( - &mut self, - doc: &R, - obj: ObjId, - prop: Prop, - tagged_value: (Value<'_>, ObjId), - conflict: bool, - ) { - self.0.put(doc, obj, prop, tagged_value, conflict) - } - - fn expose( - &mut self, - doc: &R, - obj: ObjId, - prop: Prop, - tagged_value: (Value<'_>, ObjId), - conflict: bool, - ) { - self.0.expose(doc, obj, prop, tagged_value, conflict) - } - - fn increment( - &mut self, - doc: &R, - obj: ObjId, - prop: Prop, - tagged_value: (i64, ObjId), - ) { - self.0.increment(doc, obj, prop, tagged_value) - } - - fn mark<'a, R: ReadDoc, M: Iterator>>( - &mut self, - doc: &'a R, - obj: ObjId, - mark: M, - ) { - self.0.mark(doc, obj, mark) - } - - fn unmark(&mut self, doc: &R, obj: ObjId, name: &str, start: usize, end: usize) { - self.0.unmark(doc, obj, name, start, end) - } - - fn text_as_seq(&self) -> bool { - self.0.text_as_seq() - } -} - -impl OpObserver for VecOpObserver16 { - fn insert( - &mut self, - doc: &R, - obj: ObjId, - index: usize, - tagged_value: (Value<'_>, ObjId), - conflict: bool, - ) { - self.0.insert(doc, obj, index, tagged_value, conflict) - } - - fn splice_text(&mut self, doc: &R, obj: ObjId, index: usize, value: &str) { - self.0.splice_text(doc, obj, index, value) - } - - fn delete_seq(&mut self, doc: &R, obj: ObjId, index: usize, length: usize) { - self.0.delete_seq(doc, obj, index, length) - } - - fn delete_map(&mut self, doc: &R, obj: ObjId, key: &str) { - self.0.delete_map(doc, obj, key) - } - - fn put( - &mut self, - doc: &R, - obj: ObjId, - prop: Prop, - tagged_value: (Value<'_>, ObjId), - conflict: bool, - ) { - self.0.put(doc, obj, prop, tagged_value, conflict) - } - - fn expose( - &mut self, - doc: &R, - obj: ObjId, - prop: Prop, - tagged_value: (Value<'_>, ObjId), - conflict: bool, - ) { - self.0.expose(doc, obj, prop, tagged_value, conflict) - } - - fn increment( - &mut self, - doc: &R, - obj: ObjId, - prop: Prop, - tagged_value: (i64, ObjId), - ) { - self.0.increment(doc, obj, prop, tagged_value) - } - - fn mark<'a, R: ReadDoc, M: Iterator>>( - &mut self, - doc: &'a R, - obj: ObjId, - mark: M, - ) { - self.0.mark(doc, obj, mark) - } - - fn unmark(&mut self, doc: &R, obj: ObjId, name: &str, start: usize, end: usize) { - self.0.unmark(doc, obj, name, start, end) - } - - fn text_as_seq(&self) -> bool { - self.0.text_as_seq() - } -} - -impl BranchableObserver for VecOpObserver { - fn merge(&mut self, other: &Self) { - self.0.merge(&other.0) - } - - fn branch(&self) -> Self { - VecOpObserver(self.0.branch()) - } -} - -impl BranchableObserver for VecOpObserver16 { - fn merge(&mut self, other: &Self) { - self.0.merge(&other.0) - } - - fn branch(&self) -> Self { - VecOpObserver16(self.0.branch()) - } -} diff --git a/rust/automerge/src/op_set.rs b/rust/automerge/src/op_set.rs index cd697abd..aab8ce74 100644 --- a/rust/automerge/src/op_set.rs +++ b/rust/automerge/src/op_set.rs @@ -78,10 +78,6 @@ impl OpSetInternal { } } - pub(crate) fn iter_ops(&self, obj: &ObjId) -> impl Iterator { - self.trees.get(obj).map(|o| o.iter()).into_iter().flatten() - } - pub(crate) fn parents(&self, obj: ObjId) -> Parents<'_> { Parents { obj, ops: self } } diff --git a/rust/automerge/src/op_tree.rs b/rust/automerge/src/op_tree.rs index 60bd2a6e..7de00dc3 100644 --- a/rust/automerge/src/op_tree.rs +++ b/rust/automerge/src/op_tree.rs @@ -319,7 +319,8 @@ struct CounterData { #[cfg(test)] mod tests { - use crate::types::{Op, OpId, OpType}; + use crate::legacy as amp; + use crate::types::{Op, OpId}; use super::*; @@ -327,7 +328,7 @@ mod tests { let zero = OpId::new(0, 0); Op { id: zero, - action: OpType::Put(0.into()), + action: amp::OpType::Put(0.into()), key: zero.into(), succ: Default::default(), pred: Default::default(), diff --git a/rust/automerge/src/query.rs b/rust/automerge/src/query.rs index c76c3a0e..640ecf8d 100644 --- a/rust/automerge/src/query.rs +++ b/rust/automerge/src/query.rs @@ -25,7 +25,6 @@ mod opid; mod opid_vis; mod prop; mod prop_at; -mod seek_mark; mod seek_op; mod seek_op_with_patch; @@ -47,7 +46,6 @@ pub(crate) use opid::OpIdSearch; pub(crate) use opid_vis::OpIdVisSearch; pub(crate) use prop::Prop; pub(crate) use prop_at::PropAt; -pub(crate) use seek_mark::SeekMark; pub(crate) use seek_op::SeekOp; pub(crate) use seek_op_with_patch::SeekOpWithPatch; @@ -289,7 +287,7 @@ pub(crate) struct VisWindow { } impl VisWindow { - pub(crate) fn visible_at(&mut self, op: &Op, pos: usize, clock: &Clock) -> bool { + fn visible_at(&mut self, op: &Op, pos: usize, clock: &Clock) -> bool { if !clock.covers(&op.id) { return false; } diff --git a/rust/automerge/src/query/insert.rs b/rust/automerge/src/query/insert.rs index 8e86d619..0dc0e98d 100644 --- a/rust/automerge/src/query/insert.rs +++ b/rust/automerge/src/query/insert.rs @@ -110,12 +110,6 @@ impl<'a> TreeQuery<'a> for InsertNth { self.last_seen = None; self.last_insert = element.elemid(); } - /*-------------------*/ - if self.valid.is_some() && element.valid_mark_anchor() { - self.last_valid_insert = Some(element.elemid_or_key()); - self.valid = None; - } - /*-------------------*/ if self.last_seen.is_none() && element.visible() { if self.seen >= self.target { return QueryResult::Finish; diff --git a/rust/automerge/src/query/seek_mark.rs b/rust/automerge/src/query/seek_mark.rs deleted file mode 100644 index c5834ab8..00000000 --- a/rust/automerge/src/query/seek_mark.rs +++ /dev/null @@ -1,124 +0,0 @@ -use crate::marks::Mark; -use crate::op_tree::OpSetMetadata; -use crate::query::{QueryResult, TreeQuery}; -use crate::types::{Key, ListEncoding, Op, OpId, OpType}; -use std::cmp::Ordering; -use std::collections::HashMap; -use std::fmt::Debug; - -#[derive(Debug, Clone, PartialEq)] -pub(crate) struct SeekMark<'a> { - /// the mark we are looking for - id: OpId, - end: usize, - encoding: ListEncoding, - found: bool, - mark_name: smol_str::SmolStr, - next_mark: Option>, - pos: usize, - seen: usize, - last_seen: Option, - super_marks: HashMap, - pub(crate) marks: Vec>, -} - -impl<'a> SeekMark<'a> { - pub(crate) fn new(id: OpId, end: usize, encoding: ListEncoding) -> Self { - SeekMark { - id, - encoding, - end, - found: false, - next_mark: None, - mark_name: "".into(), - pos: 0, - seen: 0, - last_seen: None, - super_marks: Default::default(), - marks: Default::default(), - } - } - - fn count_visible(&mut self, e: &Op) { - if e.insert { - self.last_seen = None - } - if e.visible() && self.last_seen.is_none() { - self.seen += e.width(self.encoding); - self.last_seen = Some(e.elemid_or_key()) - } - } -} - -impl<'a> TreeQuery<'a> for SeekMark<'a> { - fn query_element_with_metadata(&mut self, op: &'a Op, m: &OpSetMetadata) -> QueryResult { - match &op.action { - OpType::MarkBegin(_, data) if op.id == self.id => { - if !op.succ.is_empty() { - return QueryResult::Finish; - } - self.found = true; - self.mark_name = data.name.clone(); - // retain the name and the value - self.next_mark = Some(Mark::from_data(self.seen, self.seen, data)); - // change id to the end id - self.id = self.id.next(); - // remove all marks that dont match - self.super_marks.retain(|_, v| v == &data.name); - } - OpType::MarkBegin(_, mark) => { - if m.lamport_cmp(op.id, self.id) == Ordering::Greater { - if let Some(next_mark) = &mut self.next_mark { - // gather marks of the same type that supersede us - if mark.name == self.mark_name { - self.super_marks.insert(op.id.next(), mark.name.clone()); - if self.super_marks.len() == 1 { - // complete a mark - next_mark.end = self.seen; - self.marks.push(next_mark.clone()); - } - } - } else { - // gather all marks until we know what our mark's name is - self.super_marks.insert(op.id.next(), mark.name.clone()); - } - } - } - OpType::MarkEnd(_) if self.end == self.pos => { - if self.super_marks.is_empty() { - // complete a mark - if let Some(next_mark) = &mut self.next_mark { - next_mark.end = self.seen; - self.marks.push(next_mark.clone()); - } - } - return QueryResult::Finish; - } - OpType::MarkEnd(_) if self.super_marks.contains_key(&op.id) => { - self.super_marks.remove(&op.id); - if let Some(next_mark) = &mut self.next_mark { - if self.super_marks.is_empty() { - // begin a new mark - next_mark.start = self.seen; - } - } - } - _ => {} - } - // the end op hasn't been inserted yet so we need to work off the position - if self.end == self.pos { - if self.super_marks.is_empty() { - // complete a mark - if let Some(next_mark) = &mut self.next_mark { - next_mark.end = self.seen; - self.marks.push(next_mark.clone()); - } - } - return QueryResult::Finish; - } - - self.pos += 1; - self.count_visible(op); - QueryResult::Next - } -} diff --git a/rust/automerge/src/read.rs b/rust/automerge/src/read.rs index 9b3a6a77..6d479718 100644 --- a/rust/automerge/src/read.rs +++ b/rust/automerge/src/read.rs @@ -1,7 +1,7 @@ use crate::{ error::AutomergeError, exid::ExId, keys::Keys, keys_at::KeysAt, list_range::ListRange, - list_range_at::ListRangeAt, map_range::MapRange, map_range_at::MapRangeAt, marks::Mark, - parents::Parents, values::Values, Change, ChangeHash, ObjType, Prop, Value, + list_range_at::ListRangeAt, map_range::MapRange, map_range_at::MapRangeAt, parents::Parents, + values::Values, Change, ChangeHash, ObjType, Prop, Value, }; use std::ops::RangeBounds; @@ -129,16 +129,6 @@ pub trait ReadDoc { /// Get the type of this object, if it is an object. fn object_type>(&self, obj: O) -> Result; - /// Get all marks on a current sequence - fn marks>(&self, obj: O) -> Result>, AutomergeError>; - - /// Get all marks on a sequence at a given heads - fn marks_at>( - &self, - obj: O, - heads: &[ChangeHash], - ) -> Result>, AutomergeError>; - /// Get the string represented by the given text object. fn text>(&self, obj: O) -> Result; diff --git a/rust/automerge/src/sequence_tree.rs b/rust/automerge/src/sequence_tree.rs deleted file mode 100644 index 920eb13f..00000000 --- a/rust/automerge/src/sequence_tree.rs +++ /dev/null @@ -1,617 +0,0 @@ -use std::{ - cmp::{min, Ordering}, - fmt::Debug, - mem, -}; - -pub(crate) const B: usize = 16; -pub(crate) type SequenceTree = SequenceTreeInternal; - -#[derive(Clone, Debug)] -pub struct SequenceTreeInternal { - root_node: Option>, -} - -#[derive(Clone, Debug, PartialEq)] -struct SequenceTreeNode { - elements: Vec, - children: Vec>, - length: usize, -} - -impl SequenceTreeInternal -where - T: Clone + Debug, -{ - /// Construct a new, empty, sequence. - pub fn new() -> Self { - Self { root_node: None } - } - - /// Get the length of the sequence. - pub fn len(&self) -> usize { - self.root_node.as_ref().map_or(0, |n| n.len()) - } - - /// Create an iterator through the sequence. - pub fn iter(&self) -> Iter<'_, T> { - Iter { - inner: self, - index: 0, - } - } - - /// Insert the `element` into the sequence at `index`. - /// - /// # Panics - /// - /// Panics if `index > len`. - pub fn insert(&mut self, index: usize, element: T) { - let old_len = self.len(); - if let Some(root) = self.root_node.as_mut() { - #[cfg(debug_assertions)] - root.check(); - - if root.is_full() { - let original_len = root.len(); - let new_root = SequenceTreeNode::new(); - - // move new_root to root position - let old_root = mem::replace(root, new_root); - - root.length += old_root.len(); - root.children.push(old_root); - root.split_child(0); - - assert_eq!(original_len, root.len()); - - // after splitting the root has one element and two children, find which child the - // index is in - let first_child_len = root.children[0].len(); - let (child, insertion_index) = if first_child_len < index { - (&mut root.children[1], index - (first_child_len + 1)) - } else { - (&mut root.children[0], index) - }; - root.length += 1; - child.insert_into_non_full_node(insertion_index, element) - } else { - root.insert_into_non_full_node(index, element) - } - } else { - self.root_node = Some(SequenceTreeNode { - elements: vec![element], - children: Vec::new(), - length: 1, - }) - } - assert_eq!(self.len(), old_len + 1, "{:#?}", self); - } - - /// Push the `element` onto the back of the sequence. - pub fn push(&mut self, element: T) { - let l = self.len(); - self.insert(l, element) - } - - /// Get the `element` at `index` in the sequence. - pub fn get(&self, index: usize) -> Option<&T> { - self.root_node.as_ref().and_then(|n| n.get(index)) - } - - /// Removes the element at `index` from the sequence. - /// - /// # Panics - /// - /// Panics if `index` is out of bounds. - pub fn remove(&mut self, index: usize) -> T { - if let Some(root) = self.root_node.as_mut() { - #[cfg(debug_assertions)] - let len = root.check(); - let old = root.remove(index); - - if root.elements.is_empty() { - if root.is_leaf() { - self.root_node = None; - } else { - self.root_node = Some(root.children.remove(0)); - } - } - - #[cfg(debug_assertions)] - debug_assert_eq!(len, self.root_node.as_ref().map_or(0, |r| r.check()) + 1); - old - } else { - panic!("remove from empty tree") - } - } -} - -impl SequenceTreeNode -where - T: Clone + Debug, -{ - fn new() -> Self { - Self { - elements: Vec::new(), - children: Vec::new(), - length: 0, - } - } - - fn len(&self) -> usize { - self.length - } - - fn is_leaf(&self) -> bool { - self.children.is_empty() - } - - fn is_full(&self) -> bool { - self.elements.len() >= 2 * B - 1 - } - - /// Returns the child index and the given index adjusted for the cumulative index before that - /// child. - fn find_child_index(&self, index: usize) -> (usize, usize) { - let mut cumulative_len = 0; - for (child_index, child) in self.children.iter().enumerate() { - if cumulative_len + child.len() >= index { - return (child_index, index - cumulative_len); - } else { - cumulative_len += child.len() + 1; - } - } - panic!("index not found in node") - } - - fn insert_into_non_full_node(&mut self, index: usize, element: T) { - assert!(!self.is_full()); - if self.is_leaf() { - self.length += 1; - self.elements.insert(index, element); - } else { - let (child_index, sub_index) = self.find_child_index(index); - let child = &mut self.children[child_index]; - - if child.is_full() { - self.split_child(child_index); - - // child structure has changed so we need to find the index again - let (child_index, sub_index) = self.find_child_index(index); - let child = &mut self.children[child_index]; - child.insert_into_non_full_node(sub_index, element); - } else { - child.insert_into_non_full_node(sub_index, element); - } - self.length += 1; - } - } - - // A utility function to split the child `full_child_index` of this node - // Note that `full_child_index` must be full when this function is called. - fn split_child(&mut self, full_child_index: usize) { - let original_len_self = self.len(); - - // Create a new node which is going to store (B-1) keys - // of the full child. - let mut successor_sibling = SequenceTreeNode::new(); - - let full_child = &mut self.children[full_child_index]; - let original_len = full_child.len(); - assert!(full_child.is_full()); - - successor_sibling.elements = full_child.elements.split_off(B); - - if !full_child.is_leaf() { - successor_sibling.children = full_child.children.split_off(B); - } - - let middle = full_child.elements.pop().unwrap(); - - full_child.length = - full_child.elements.len() + full_child.children.iter().map(|c| c.len()).sum::(); - - successor_sibling.length = successor_sibling.elements.len() - + successor_sibling - .children - .iter() - .map(|c| c.len()) - .sum::(); - - let z_len = successor_sibling.len(); - - let full_child_len = full_child.len(); - - self.children - .insert(full_child_index + 1, successor_sibling); - - self.elements.insert(full_child_index, middle); - - assert_eq!(full_child_len + z_len + 1, original_len, "{:#?}", self); - - assert_eq!(original_len_self, self.len()); - } - - fn remove_from_leaf(&mut self, index: usize) -> T { - self.length -= 1; - self.elements.remove(index) - } - - fn remove_element_from_non_leaf(&mut self, index: usize, element_index: usize) -> T { - self.length -= 1; - if self.children[element_index].elements.len() >= B { - let total_index = self.cumulative_index(element_index); - // recursively delete index - 1 in predecessor_node - let predecessor = self.children[element_index].remove(index - 1 - total_index); - // replace element with that one - mem::replace(&mut self.elements[element_index], predecessor) - } else if self.children[element_index + 1].elements.len() >= B { - // recursively delete index + 1 in successor_node - let total_index = self.cumulative_index(element_index + 1); - let successor = self.children[element_index + 1].remove(index + 1 - total_index); - // replace element with that one - mem::replace(&mut self.elements[element_index], successor) - } else { - let middle_element = self.elements.remove(element_index); - let successor_child = self.children.remove(element_index + 1); - self.children[element_index].merge(middle_element, successor_child); - - let total_index = self.cumulative_index(element_index); - self.children[element_index].remove(index - total_index) - } - } - - fn cumulative_index(&self, child_index: usize) -> usize { - self.children[0..child_index] - .iter() - .map(|c| c.len() + 1) - .sum() - } - - fn remove_from_internal_child(&mut self, index: usize, mut child_index: usize) -> T { - if self.children[child_index].elements.len() < B - && if child_index > 0 { - self.children[child_index - 1].elements.len() < B - } else { - true - } - && if child_index + 1 < self.children.len() { - self.children[child_index + 1].elements.len() < B - } else { - true - } - { - // if the child and its immediate siblings have B-1 elements merge the child - // with one sibling, moving an element from this node into the new merged node - // to be the median - - if child_index > 0 { - let middle = self.elements.remove(child_index - 1); - - // use the predessor sibling - let successor = self.children.remove(child_index); - child_index -= 1; - - self.children[child_index].merge(middle, successor); - } else { - let middle = self.elements.remove(child_index); - - // use the sucessor sibling - let successor = self.children.remove(child_index + 1); - - self.children[child_index].merge(middle, successor); - } - } else if self.children[child_index].elements.len() < B { - if child_index > 0 - && self - .children - .get(child_index - 1) - .map_or(false, |c| c.elements.len() >= B) - { - let last_element = self.children[child_index - 1].elements.pop().unwrap(); - assert!(!self.children[child_index - 1].elements.is_empty()); - self.children[child_index - 1].length -= 1; - - let parent_element = - mem::replace(&mut self.elements[child_index - 1], last_element); - - self.children[child_index] - .elements - .insert(0, parent_element); - self.children[child_index].length += 1; - - if let Some(last_child) = self.children[child_index - 1].children.pop() { - self.children[child_index - 1].length -= last_child.len(); - self.children[child_index].length += last_child.len(); - self.children[child_index].children.insert(0, last_child); - } - } else if self - .children - .get(child_index + 1) - .map_or(false, |c| c.elements.len() >= B) - { - let first_element = self.children[child_index + 1].elements.remove(0); - self.children[child_index + 1].length -= 1; - - assert!(!self.children[child_index + 1].elements.is_empty()); - - let parent_element = mem::replace(&mut self.elements[child_index], first_element); - - self.children[child_index].length += 1; - self.children[child_index].elements.push(parent_element); - - if !self.children[child_index + 1].is_leaf() { - let first_child = self.children[child_index + 1].children.remove(0); - self.children[child_index + 1].length -= first_child.len(); - self.children[child_index].length += first_child.len(); - - self.children[child_index].children.push(first_child); - } - } - } - self.length -= 1; - let total_index = self.cumulative_index(child_index); - self.children[child_index].remove(index - total_index) - } - - fn check(&self) -> usize { - let l = self.elements.len() + self.children.iter().map(|c| c.check()).sum::(); - assert_eq!(self.len(), l, "{:#?}", self); - - l - } - - fn remove(&mut self, index: usize) -> T { - let original_len = self.len(); - if self.is_leaf() { - let v = self.remove_from_leaf(index); - assert_eq!(original_len, self.len() + 1); - debug_assert_eq!(self.check(), self.len()); - v - } else { - let mut total_index = 0; - for (child_index, child) in self.children.iter().enumerate() { - match (total_index + child.len()).cmp(&index) { - Ordering::Less => { - // should be later on in the loop - total_index += child.len() + 1; - continue; - } - Ordering::Equal => { - let v = self.remove_element_from_non_leaf( - index, - min(child_index, self.elements.len() - 1), - ); - assert_eq!(original_len, self.len() + 1); - debug_assert_eq!(self.check(), self.len()); - return v; - } - Ordering::Greater => { - let v = self.remove_from_internal_child(index, child_index); - assert_eq!(original_len, self.len() + 1); - debug_assert_eq!(self.check(), self.len()); - return v; - } - } - } - panic!( - "index not found to remove {} {} {} {}", - index, - total_index, - self.len(), - self.check() - ); - } - } - - fn merge(&mut self, middle: T, successor_sibling: SequenceTreeNode) { - self.elements.push(middle); - self.elements.extend(successor_sibling.elements); - self.children.extend(successor_sibling.children); - self.length += successor_sibling.length + 1; - assert!(self.is_full()); - } - - fn get(&self, index: usize) -> Option<&T> { - if self.is_leaf() { - return self.elements.get(index); - } else { - let mut cumulative_len = 0; - for (child_index, child) in self.children.iter().enumerate() { - match (cumulative_len + child.len()).cmp(&index) { - Ordering::Less => { - cumulative_len += child.len() + 1; - } - Ordering::Equal => return self.elements.get(child_index), - Ordering::Greater => { - return child.get(index - cumulative_len); - } - } - } - } - None - } -} - -impl Default for SequenceTreeInternal -where - T: Clone + Debug, -{ - fn default() -> Self { - Self::new() - } -} - -impl PartialEq for SequenceTreeInternal -where - T: Clone + Debug + PartialEq, -{ - fn eq(&self, other: &Self) -> bool { - self.len() == other.len() && self.iter().zip(other.iter()).all(|(a, b)| a == b) - } -} - -impl<'a, T> IntoIterator for &'a SequenceTreeInternal -where - T: Clone + Debug, -{ - type Item = &'a T; - - type IntoIter = Iter<'a, T>; - - fn into_iter(self) -> Self::IntoIter { - Iter { - inner: self, - index: 0, - } - } -} - -#[derive(Debug)] -pub struct Iter<'a, T> { - inner: &'a SequenceTreeInternal, - index: usize, -} - -impl<'a, T> Iterator for Iter<'a, T> -where - T: Clone + Debug, -{ - type Item = &'a T; - - fn next(&mut self) -> Option { - self.index += 1; - self.inner.get(self.index - 1) - } - - fn nth(&mut self, n: usize) -> Option { - self.index += n + 1; - self.inner.get(self.index - 1) - } -} - -#[cfg(test)] -mod tests { - use proptest::prelude::*; - - use super::*; - - #[test] - fn push_back() { - let mut t = SequenceTree::new(); - - t.push(1); - t.push(2); - t.push(3); - t.push(4); - t.push(5); - t.push(6); - t.push(8); - t.push(100); - } - - #[test] - fn insert() { - let mut t = SequenceTree::new(); - - t.insert(0, 1); - t.insert(1, 1); - t.insert(0, 1); - t.insert(0, 1); - t.insert(0, 1); - t.insert(3, 1); - t.insert(4, 1); - } - - #[test] - fn insert_book() { - let mut t = SequenceTree::new(); - - for i in 0..100 { - t.insert(i % 2, ()); - } - } - - #[test] - fn insert_book_vec() { - let mut t = SequenceTree::new(); - let mut v = Vec::new(); - - for i in 0..100 { - t.insert(i % 3, ()); - v.insert(i % 3, ()); - - assert_eq!(v, t.iter().copied().collect::>()) - } - } - - fn arb_indices() -> impl Strategy> { - proptest::collection::vec(any::(), 0..1000).prop_map(|v| { - let mut len = 0; - v.into_iter() - .map(|i| { - len += 1; - i % len - }) - .collect::>() - }) - } - - proptest! { - - #[test] - fn proptest_insert(indices in arb_indices()) { - let mut t = SequenceTreeInternal::::new(); - let mut v = Vec::new(); - - for i in indices{ - if i <= v.len() { - t.insert(i % 3, i); - v.insert(i % 3, i); - } else { - return Err(proptest::test_runner::TestCaseError::reject("index out of bounds")) - } - - assert_eq!(v, t.iter().copied().collect::>()) - } - } - - } - - proptest! { - - // This is a really slow test due to all the copying of the Vecs (i.e. not due to the - // sequencetree) so we only do a few runs - #![proptest_config(ProptestConfig::with_cases(20))] - #[test] - fn proptest_remove(inserts in arb_indices(), removes in arb_indices()) { - let mut t = SequenceTreeInternal::::new(); - let mut v = Vec::new(); - - for i in inserts { - if i <= v.len() { - t.insert(i , i); - v.insert(i , i); - } else { - return Err(proptest::test_runner::TestCaseError::reject("index out of bounds")) - } - - assert_eq!(v, t.iter().copied().collect::>()) - } - - for i in removes { - if i < v.len() { - let tr = t.remove(i); - let vr = v.remove(i); - assert_eq!(tr, vr); - } else { - return Err(proptest::test_runner::TestCaseError::reject("index out of bounds")) - } - - assert_eq!(v, t.iter().copied().collect::>()) - } - } - - } -} diff --git a/rust/automerge/src/storage/change.rs b/rust/automerge/src/storage/change.rs index a420da39..61db0b00 100644 --- a/rust/automerge/src/storage/change.rs +++ b/rust/automerge/src/storage/change.rs @@ -437,8 +437,6 @@ pub(crate) trait AsChangeOp<'a> { fn action(&self) -> u64; fn val(&self) -> Cow<'a, ScalarValue>; fn pred(&self) -> Self::PredIter; - fn expand(&self) -> bool; - fn mark_name(&self) -> Option>; } impl ChangeBuilder, Set, Set, Set> { diff --git a/rust/automerge/src/storage/change/change_actors.rs b/rust/automerge/src/storage/change/change_actors.rs index 951201dd..61f1221d 100644 --- a/rust/automerge/src/storage/change/change_actors.rs +++ b/rust/automerge/src/storage/change/change_actors.rs @@ -1,7 +1,4 @@ -use std::{ - borrow::Cow, - collections::{BTreeMap, BTreeSet}, -}; +use std::collections::{BTreeMap, BTreeSet}; use crate::convert; @@ -247,14 +244,6 @@ where fn val(&self) -> std::borrow::Cow<'aschangeop, crate::ScalarValue> { self.op.val() } - - fn expand(&self) -> bool { - self.op.expand() - } - - fn mark_name(&self) -> Option> { - self.op.mark_name() - } } pub(crate) struct WithChangeActorsPredIter<'actors, 'aschangeop, A, I, O, C, P> { diff --git a/rust/automerge/src/storage/change/change_op_columns.rs b/rust/automerge/src/storage/change/change_op_columns.rs index 145073e6..86ec59c2 100644 --- a/rust/automerge/src/storage/change/change_op_columns.rs +++ b/rust/automerge/src/storage/change/change_op_columns.rs @@ -1,16 +1,16 @@ -use std::{borrow::Cow, convert::TryFrom, ops::Range}; +use std::{convert::TryFrom, ops::Range}; use crate::{ columnar::{ column_range::{ generic::{GenericColumnRange, GroupRange, GroupedColumnRange, SimpleColRange}, - BooleanRange, DeltaRange, Key, KeyEncoder, KeyIter, KeyRange, MaybeBooleanRange, - ObjIdEncoder, ObjIdIter, ObjIdRange, OpIdListEncoder, OpIdListIter, OpIdListRange, - RleRange, ValueEncoder, ValueIter, ValueRange, + BooleanRange, DeltaRange, Key, KeyEncoder, KeyIter, KeyRange, ObjIdEncoder, ObjIdIter, + ObjIdRange, OpIdListEncoder, OpIdListIter, OpIdListRange, RleRange, ValueEncoder, + ValueIter, ValueRange, }, encoding::{ - BooleanDecoder, BooleanEncoder, ColumnDecoder, DecodeColumnError, MaybeBooleanDecoder, - MaybeBooleanEncoder, RleDecoder, RleEncoder, + BooleanDecoder, BooleanEncoder, ColumnDecoder, DecodeColumnError, RleDecoder, + RleEncoder, }, }, convert, @@ -32,8 +32,6 @@ const INSERT_COL_ID: ColumnId = ColumnId::new(3); const ACTION_COL_ID: ColumnId = ColumnId::new(4); const VAL_COL_ID: ColumnId = ColumnId::new(5); const PRED_COL_ID: ColumnId = ColumnId::new(7); -const EXPAND_COL_ID: ColumnId = ColumnId::new(8); -const MARK_NAME_COL_ID: ColumnId = ColumnId::new(9); #[derive(Clone, Debug, PartialEq)] pub(crate) struct ChangeOp { @@ -43,8 +41,6 @@ pub(crate) struct ChangeOp { pub(crate) pred: Vec, pub(crate) action: u64, pub(crate) obj: ObjId, - pub(crate) expand: bool, - pub(crate) mark_name: Option, } impl<'a, A: AsChangeOp<'a, ActorId = usize, OpId = OpId>> From for ChangeOp { @@ -63,8 +59,6 @@ impl<'a, A: AsChangeOp<'a, ActorId = usize, OpId = OpId>> From for ChangeOp { pred: a.pred().collect(), insert: a.insert(), action: a.action(), - expand: a.expand(), - mark_name: a.mark_name().map(|n| n.into_owned()), } } } @@ -105,14 +99,6 @@ impl<'a> AsChangeOp<'a> for &'a ChangeOp { fn action(&self) -> u64 { self.action } - - fn expand(&self) -> bool { - self.expand - } - - fn mark_name(&self) -> Option> { - self.mark_name.as_ref().map(Cow::Borrowed) - } } #[derive(Clone, Debug, PartialEq)] @@ -123,8 +109,6 @@ pub(crate) struct ChangeOpsColumns { action: RleRange, val: ValueRange, pred: OpIdListRange, - expand: MaybeBooleanRange, - mark_name: RleRange, } impl ChangeOpsColumns { @@ -137,8 +121,6 @@ impl ChangeOpsColumns { action: self.action.decoder(data), val: self.val.iter(data), pred: self.pred.iter(data), - expand: self.expand.decoder(data), - mark_name: self.mark_name.decoder(data), } } @@ -165,16 +147,12 @@ impl ChangeOpsColumns { Op: convert::OpId + 'a, C: AsChangeOp<'c, OpId = Op> + 'a, { - tracing::trace!(expands = ?ops.clone().map(|op| op.expand()).collect::>(), "encoding change ops"); let obj = ObjIdRange::encode(ops.clone().map(|o| o.obj()), out); let key = KeyRange::encode(ops.clone().map(|o| o.key()), out); let insert = BooleanRange::encode(ops.clone().map(|o| o.insert()), out); let action = RleRange::encode(ops.clone().map(|o| Some(o.action())), out); let val = ValueRange::encode(ops.clone().map(|o| o.val()), out); - let pred = OpIdListRange::encode(ops.clone().map(|o| o.pred()), out); - let expand = MaybeBooleanRange::encode(ops.clone().map(|o| o.expand()), out); - let mark_name = - RleRange::encode::, _>(ops.map(|o| o.mark_name()), out); + let pred = OpIdListRange::encode(ops.map(|o| o.pred()), out); Self { obj, key, @@ -182,8 +160,6 @@ impl ChangeOpsColumns { action, val, pred, - expand, - mark_name, } } @@ -199,18 +175,13 @@ impl ChangeOpsColumns { let mut action = RleEncoder::<_, u64>::from(Vec::new()); let mut val = ValueEncoder::new(); let mut pred = OpIdListEncoder::new(); - let mut expand = MaybeBooleanEncoder::new(); - let mut mark_name = RleEncoder::<_, smol_str::SmolStr>::new(Vec::new()); for op in ops { - tracing::trace!(expand=?op.expand(), "expand"); obj.append(op.obj()); key.append(op.key()); insert.append(op.insert()); action.append_value(op.action()); val.append(&op.val()); pred.append(op.pred()); - expand.append(op.expand()); - mark_name.append(op.mark_name()); } let obj = obj.finish(out); let key = key.finish(out); @@ -228,16 +199,6 @@ impl ChangeOpsColumns { let val = val.finish(out); let pred = pred.finish(out); - let expand_start = out.len(); - let (expand, _) = expand.finish(); - out.extend(expand); - let expand = MaybeBooleanRange::from(expand_start..out.len()); - - let mark_name_start = out.len(); - let (mark_name, _) = mark_name.finish(); - out.extend(mark_name); - let mark_name = RleRange::from(mark_name_start..out.len()); - Self { obj, key, @@ -245,8 +206,6 @@ impl ChangeOpsColumns { action, val, pred, - expand, - mark_name, } } @@ -313,18 +272,6 @@ impl ChangeOpsColumns { ), ]); } - if !self.expand.is_empty() { - cols.push(RawColumn::new( - ColumnSpec::new(EXPAND_COL_ID, ColumnType::Boolean, false), - self.expand.clone().into(), - )); - } - if !self.mark_name.is_empty() { - cols.push(RawColumn::new( - ColumnSpec::new(MARK_NAME_COL_ID, ColumnType::String, false), - self.mark_name.clone().into(), - )); - } cols.into_iter().collect() } } @@ -349,8 +296,6 @@ pub(crate) struct ChangeOpsIter<'a> { action: RleDecoder<'a, u64>, val: ValueIter<'a>, pred: OpIdListIter<'a>, - expand: MaybeBooleanDecoder<'a>, - mark_name: RleDecoder<'a, smol_str::SmolStr>, } impl<'a> ChangeOpsIter<'a> { @@ -372,8 +317,6 @@ impl<'a> ChangeOpsIter<'a> { let action = self.action.next_in_col("action")?; let val = self.val.next_in_col("value")?; let pred = self.pred.next_in_col("pred")?; - let expand = self.expand.maybe_next_in_col("expand")?.unwrap_or(false); - let mark_name = self.mark_name.maybe_next_in_col("mark_name")?; // This check is necessary to ensure that OpType::from_action_and_value // cannot panic later in the process. @@ -386,8 +329,6 @@ impl<'a> ChangeOpsIter<'a> { action, val, pred, - expand, - mark_name, })) } } @@ -434,8 +375,6 @@ impl TryFrom for ChangeOpsColumns { let mut pred_group: Option> = None; let mut pred_actor: Option> = None; let mut pred_ctr: Option = None; - let mut expand: Option = None; - let mut mark_name: Option> = None; let mut other = Columns::empty(); for (index, col) in columns.into_iter().enumerate() { @@ -490,8 +429,6 @@ impl TryFrom for ChangeOpsColumns { } _ => return Err(ParseChangeColumnsError::MismatchingColumn { index }), }, - (EXPAND_COL_ID, ColumnType::Boolean) => expand = Some(col.range().into()), - (MARK_NAME_COL_ID, ColumnType::String) => mark_name = Some(col.range().into()), (other_type, other_col) => { tracing::warn!(typ=?other_type, id=?other_col, "unknown column"); other.append(col); @@ -517,8 +454,6 @@ impl TryFrom for ChangeOpsColumns { action: action.unwrap_or(0..0).into(), val: val.unwrap_or_else(|| ValueRange::new((0..0).into(), (0..0).into())), pred, - expand: expand.unwrap_or_else(|| (0..0).into()), - mark_name: mark_name.unwrap_or_else(|| (0..0).into()), }) } } @@ -536,8 +471,6 @@ mod tests { pred in proptest::collection::vec(opid(), 0..20), action in 0_u64..6, obj in opid(), - mark_name in proptest::option::of(any::().prop_map(|s| s.into())), - expand in any::(), insert in any::()) -> ChangeOp { let val = if action == 5 && !(value.is_int() || value.is_uint()) { @@ -550,8 +483,6 @@ mod tests { pred, action, insert, - expand, - mark_name, } } } diff --git a/rust/automerge/src/storage/convert/op_as_changeop.rs b/rust/automerge/src/storage/convert/op_as_changeop.rs index c08fa063..00b5e940 100644 --- a/rust/automerge/src/storage/convert/op_as_changeop.rs +++ b/rust/automerge/src/storage/convert/op_as_changeop.rs @@ -5,7 +5,7 @@ use crate::{ convert, op_set::OpSetMetadata, storage::AsChangeOp, - types::{ActorId, Key, MarkData, ObjId, Op, OpId, OpType, ScalarValue}, + types::{ActorId, Key, ObjId, Op, OpId, OpType, ScalarValue}, }; /// Wrap an op in an implementation of `AsChangeOp` which represents actor IDs using a reference to @@ -93,12 +93,9 @@ impl<'a> AsChangeOp<'a> for OpWithMetadata<'a> { fn val(&self) -> Cow<'a, ScalarValue> { match &self.op.action { - OpType::Make(..) | OpType::Delete | OpType::MarkEnd(..) => { - Cow::Owned(ScalarValue::Null) - } + OpType::Make(..) | OpType::Delete => Cow::Owned(ScalarValue::Null), OpType::Increment(i) => Cow::Owned(ScalarValue::Int(*i)), OpType::Put(s) => Cow::Borrowed(s), - OpType::MarkBegin(_, MarkData { value, .. }) => Cow::Borrowed(value), } } @@ -128,19 +125,4 @@ impl<'a> AsChangeOp<'a> for OpWithMetadata<'a> { Key::Seq(e) => convert::Key::Elem(convert::ElemId::Op(self.wrap(&e.0))), } } - - fn expand(&self) -> bool { - matches!( - self.op.action, - OpType::MarkBegin(true, _) | OpType::MarkEnd(true) - ) - } - - fn mark_name(&self) -> Option> { - if let OpType::MarkBegin(_, MarkData { name, .. }) = &self.op.action { - Some(Cow::Owned(name.clone())) - } else { - None - } - } } diff --git a/rust/automerge/src/storage/convert/op_as_docop.rs b/rust/automerge/src/storage/convert/op_as_docop.rs index 09598b67..8d237354 100644 --- a/rust/automerge/src/storage/convert/op_as_docop.rs +++ b/rust/automerge/src/storage/convert/op_as_docop.rs @@ -4,7 +4,7 @@ use crate::{ convert, indexed_cache::IndexedCache, storage::AsDocOp, - types::{ElemId, Key, MarkData, ObjId, Op, OpId, OpType, ScalarValue}, + types::{ElemId, Key, ObjId, Op, OpId, OpType, ScalarValue}, }; /// Create an [`AsDocOp`] implementation for a [`crate::types::Op`] @@ -90,7 +90,6 @@ impl<'a> AsDocOp<'a> for OpAsDocOp<'a> { match &self.op.action { OpType::Put(v) => Cow::Borrowed(v), OpType::Increment(i) => Cow::Owned(ScalarValue::Int(*i)), - OpType::MarkBegin(_, MarkData { value, .. }) => Cow::Borrowed(value), _ => Cow::Owned(ScalarValue::Null), } } @@ -110,22 +109,6 @@ impl<'a> AsDocOp<'a> for OpAsDocOp<'a> { fn action(&self) -> u64 { self.op.action.action_index() } - - fn expand(&self) -> bool { - if let OpType::MarkBegin(expand, _) | OpType::MarkEnd(expand) = &self.op.action { - *expand - } else { - false - } - } - - fn mark_name(&self) -> Option> { - if let OpType::MarkBegin(_, MarkData { name, .. }) = &self.op.action { - Some(Cow::Owned(name.clone())) - } else { - None - } - } } pub(crate) struct OpAsDocOpSuccIter<'a> { diff --git a/rust/automerge/src/storage/document/doc_op_columns.rs b/rust/automerge/src/storage/document/doc_op_columns.rs index 7248b1b3..82de17eb 100644 --- a/rust/automerge/src/storage/document/doc_op_columns.rs +++ b/rust/automerge/src/storage/document/doc_op_columns.rs @@ -4,13 +4,13 @@ use crate::{ columnar::{ column_range::{ generic::{GenericColumnRange, GroupRange, GroupedColumnRange, SimpleColRange}, - BooleanRange, DeltaRange, Key, KeyEncoder, KeyIter, KeyRange, MaybeBooleanRange, - ObjIdEncoder, ObjIdIter, ObjIdRange, OpIdEncoder, OpIdIter, OpIdListEncoder, - OpIdListIter, OpIdListRange, OpIdRange, RleRange, ValueEncoder, ValueIter, ValueRange, + BooleanRange, DeltaRange, Key, KeyEncoder, KeyIter, KeyRange, ObjIdEncoder, ObjIdIter, + ObjIdRange, OpIdEncoder, OpIdIter, OpIdListEncoder, OpIdListIter, OpIdListRange, + OpIdRange, RleRange, ValueEncoder, ValueIter, ValueRange, }, encoding::{ - BooleanDecoder, BooleanEncoder, ColumnDecoder, DecodeColumnError, MaybeBooleanDecoder, - MaybeBooleanEncoder, RleDecoder, RleEncoder, + BooleanDecoder, BooleanEncoder, ColumnDecoder, DecodeColumnError, RleDecoder, + RleEncoder, }, }, convert, @@ -28,8 +28,6 @@ const INSERT_COL_ID: ColumnId = ColumnId::new(3); const ACTION_COL_ID: ColumnId = ColumnId::new(4); const VAL_COL_ID: ColumnId = ColumnId::new(5); const SUCC_COL_ID: ColumnId = ColumnId::new(8); -const EXPAND_COL_ID: ColumnId = ColumnId::new(9); -const MARK_NAME_COL_ID: ColumnId = ColumnId::new(10); /// The form operations take in the compressed document format. #[derive(Debug)] @@ -38,11 +36,9 @@ pub(crate) struct DocOp { pub(crate) object: ObjId, pub(crate) key: Key, pub(crate) insert: bool, - pub(crate) action: u64, + pub(crate) action: usize, pub(crate) value: ScalarValue, pub(crate) succ: Vec, - pub(crate) expand: bool, - pub(crate) mark_name: Option, } #[derive(Debug, Clone)] @@ -56,8 +52,6 @@ pub(crate) struct DocOpColumns { succ: OpIdListRange, #[allow(dead_code)] other: Columns, - expand: MaybeBooleanRange, - mark_name: RleRange, } struct DocId { @@ -96,8 +90,6 @@ pub(crate) trait AsDocOp<'a> { fn action(&self) -> u64; fn val(&self) -> Cow<'a, ScalarValue>; fn succ(&self) -> Self::SuccIter; - fn expand(&self) -> bool; - fn mark_name(&self) -> Option>; } impl DocOpColumns { @@ -126,9 +118,7 @@ impl DocOpColumns { let insert = BooleanRange::encode(ops.clone().map(|o| o.insert()), out); let action = RleRange::encode(ops.clone().map(|o| Some(o.action())), out); let val = ValueRange::encode(ops.clone().map(|o| o.val()), out); - let succ = OpIdListRange::encode(ops.clone().map(|o| o.succ()), out); - let expand = MaybeBooleanRange::encode(ops.clone().map(|o| o.expand()), out); - let mark_name = RleRange::encode(ops.map(|o| o.mark_name()), out); + let succ = OpIdListRange::encode(ops.map(|o| o.succ()), out); Self { obj, key, @@ -137,8 +127,6 @@ impl DocOpColumns { action, val, succ, - expand, - mark_name, other: Columns::empty(), } } @@ -156,8 +144,6 @@ impl DocOpColumns { let mut action = RleEncoder::<_, u64>::from(Vec::new()); let mut val = ValueEncoder::new(); let mut succ = OpIdListEncoder::new(); - let mut expand = MaybeBooleanEncoder::new(); - let mut mark_name = RleEncoder::<_, smol_str::SmolStr>::new(Vec::new()); for op in ops { obj.append(op.obj()); key.append(op.key()); @@ -166,8 +152,6 @@ impl DocOpColumns { action.append(Some(op.action())); val.append(&op.val()); succ.append(op.succ()); - expand.append(op.expand()); - mark_name.append(op.mark_name()); } let obj = obj.finish(out); let key = key.finish(out); @@ -185,17 +169,6 @@ impl DocOpColumns { let val = val.finish(out); let succ = succ.finish(out); - - let expand_start = out.len(); - let (expand_out, _) = expand.finish(); - out.extend(expand_out); - let expand = MaybeBooleanRange::from(expand_start..out.len()); - - let mark_name_start = out.len(); - let (mark_name_out, _) = mark_name.finish(); - out.extend(mark_name_out); - let mark_name = RleRange::from(mark_name_start..out.len()); - DocOpColumns { obj, key, @@ -204,8 +177,6 @@ impl DocOpColumns { action, val, succ, - expand, - mark_name, other: Columns::empty(), } } @@ -219,8 +190,6 @@ impl DocOpColumns { insert: self.insert.decoder(data), value: self.val.iter(data), succ: self.succ.iter(data), - expand: self.expand.decoder(data), - mark_name: self.mark_name.decoder(data), } } @@ -295,18 +264,6 @@ impl DocOpColumns { ), ]); } - if !self.expand.is_empty() { - cols.push(RawColumn::new( - ColumnSpec::new(EXPAND_COL_ID, ColumnType::Boolean, false), - self.expand.clone().into(), - )); - } - if !self.mark_name.is_empty() { - cols.push(RawColumn::new( - ColumnSpec::new(MARK_NAME_COL_ID, ColumnType::String, false), - self.mark_name.clone().into(), - )); - } cols.into_iter().collect() } } @@ -320,8 +277,6 @@ pub(crate) struct DocOpColumnIter<'a> { insert: BooleanDecoder<'a>, value: ValueIter<'a>, succ: OpIdListIter<'a>, - expand: MaybeBooleanDecoder<'a>, - mark_name: RleDecoder<'a, smol_str::SmolStr>, } impl<'a> DocOpColumnIter<'a> { @@ -366,18 +321,14 @@ impl<'a> DocOpColumnIter<'a> { let value = self.value.next_in_col("value")?; let succ = self.succ.next_in_col("succ")?; let insert = self.insert.next_in_col("insert")?; - let expand = self.expand.maybe_next_in_col("expand")?.unwrap_or(false); - let mark_name = self.mark_name.maybe_next_in_col("mark_name")?; Ok(Some(DocOp { id, value, - action, + action: action as usize, object: obj, key, succ, insert, - expand, - mark_name, })) } } @@ -412,8 +363,6 @@ impl TryFrom for DocOpColumns { let mut succ_group: Option> = None; let mut succ_actor: Option> = None; let mut succ_ctr: Option = None; - let mut expand: Option = None; - let mut mark_name: Option> = None; let mut other = Columns::empty(); for (index, col) in columns.into_iter().enumerate() { @@ -467,8 +416,6 @@ impl TryFrom for DocOpColumns { } _ => return Err(Error::MismatchingColumn { index }), }, - (EXPAND_COL_ID, ColumnType::Boolean) => expand = Some(col.range().into()), - (MARK_NAME_COL_ID, ColumnType::String) => mark_name = Some(col.range().into()), (other_col, other_type) => { tracing::warn!(id=?other_col, typ=?other_type, "unknown column type"); other.append(col) @@ -497,8 +444,6 @@ impl TryFrom for DocOpColumns { succ_actor.unwrap_or_else(|| (0..0).into()), succ_ctr.unwrap_or_else(|| (0..0).into()), ), - expand: expand.unwrap_or_else(|| (0..0).into()), - mark_name: mark_name.unwrap_or_else(|| (0..0).into()), other, }) } diff --git a/rust/automerge/src/storage/load/reconstruct_document.rs b/rust/automerge/src/storage/load/reconstruct_document.rs index 1f80a85d..44ace72a 100644 --- a/rust/automerge/src/storage/load/reconstruct_document.rs +++ b/rust/automerge/src/storage/load/reconstruct_document.rs @@ -17,6 +17,8 @@ pub(crate) enum Error { OpsOutOfOrder, #[error("error reading operation: {0:?}")] ReadOp(Box), + #[error("an operation contained an invalid action")] + InvalidAction, #[error("an operation referenced a missing actor id")] MissingActor, #[error("invalid changes: {0}")] @@ -27,8 +29,6 @@ pub(crate) enum Error { MissingOps, #[error("succ out of order")] SuccOutOfOrder, - #[error(transparent)] - InvalidOp(#[from] crate::error::InvalidOpType), } pub(crate) struct MismatchedHeads { @@ -345,10 +345,9 @@ fn import_op(m: &mut OpSetMetadata, op: DocOp) -> Result { return Err(Error::MissingActor); } } - let action = OpType::from_action_and_value(op.action, op.value, op.mark_name, op.expand); Ok(Op { id: check_opid(m, op.id)?, - action, + action: parse_optype(op.action, op.value)?, key, succ: m.try_sorted_opids(op.succ).ok_or(Error::SuccOutOfOrder)?, pred: OpIds::empty(), @@ -368,3 +367,25 @@ fn check_opid(m: &OpSetMetadata, opid: OpId) -> Result { } } } + +fn parse_optype(action_index: usize, value: ScalarValue) -> Result { + match action_index { + 0 => Ok(OpType::Make(ObjType::Map)), + 1 => Ok(OpType::Put(value)), + 2 => Ok(OpType::Make(ObjType::List)), + 3 => Ok(OpType::Delete), + 4 => Ok(OpType::Make(ObjType::Text)), + 5 => match value { + ScalarValue::Int(i) => Ok(OpType::Increment(i)), + _ => { + tracing::error!(?value, "invalid value for counter op"); + Err(Error::InvalidAction) + } + }, + 6 => Ok(OpType::Make(ObjType::Table)), + other => { + tracing::error!(action = other, "unknown action type"); + Err(Error::InvalidAction) + } + } +} diff --git a/rust/automerge/src/transaction/inner.rs b/rust/automerge/src/transaction/inner.rs index 5afd2e1d..0fe735d5 100644 --- a/rust/automerge/src/transaction/inner.rs +++ b/rust/automerge/src/transaction/inner.rs @@ -1,7 +1,6 @@ use std::num::NonZeroU64; use crate::exid::ExId; -use crate::marks::{ExpandMark, Mark}; use crate::query::{self, OpIdSearch}; use crate::storage::Change as StoredChange; use crate::types::{Key, ListEncoding, ObjId, OpId, OpIds, TextEncoding}; @@ -639,7 +638,7 @@ impl TransactionInner { for (offset, v) in values.iter().enumerate() { let op = &self.operations[start + offset].1; let value = (v.clone().into(), doc.ops().id_to_exid(op.id)); - obs.insert(doc, ex_obj.clone(), index + offset, value, false) + obs.insert(doc, ex_obj.clone(), index + offset, value) } } } @@ -649,51 +648,6 @@ impl TransactionInner { Ok(()) } - pub(crate) fn mark( - &mut self, - doc: &mut Automerge, - op_observer: Option<&mut Obs>, - ex_obj: &ExId, - mark: Mark<'_>, - expand: ExpandMark, - ) -> Result<(), AutomergeError> { - let (obj, _obj_type) = doc.exid_to_obj(ex_obj)?; - if let Some(obs) = op_observer { - let action = OpType::MarkBegin(expand.left(), mark.data.clone().into_owned()); - self.do_insert(doc, Some(obs), obj, mark.start, action)?; - self.do_insert( - doc, - Some(obs), - obj, - mark.end, - OpType::MarkEnd(expand.right()), - )?; - if mark.value().is_null() { - obs.unmark(doc, ex_obj.clone(), mark.name(), mark.start, mark.end); - } else { - obs.mark(doc, ex_obj.clone(), Some(mark).into_iter()) - } - } else { - let action = OpType::MarkBegin(expand.left(), mark.data.into_owned()); - self.do_insert::(doc, None, obj, mark.start, action)?; - self.do_insert::(doc, None, obj, mark.end, OpType::MarkEnd(expand.right()))?; - } - Ok(()) - } - - pub(crate) fn unmark( - &mut self, - doc: &mut Automerge, - op_observer: Option<&mut Obs>, - ex_obj: &ExId, - name: &str, - start: usize, - end: usize, - ) -> Result<(), AutomergeError> { - let mark = Mark::new(name.to_string(), ScalarValue::Null, start, end); - self.mark(doc, op_observer, ex_obj, mark, ExpandMark::None) - } - fn finalize_op( &mut self, doc: &mut Automerge, @@ -706,24 +660,23 @@ impl TransactionInner { if let Some(op_observer) = op_observer { let ex_obj = doc.ops().id_to_exid(obj.0); if op.insert { - if !op.is_mark() { - let obj_type = doc.ops().object_type(&obj); - assert!(obj_type.unwrap().is_sequence()); - match (obj_type, prop) { - (Some(ObjType::List), Prop::Seq(index)) => { - let value = (op.value(), doc.ops().id_to_exid(op.id)); - op_observer.insert(doc, ex_obj, index, value, false) - } - (Some(ObjType::Text), Prop::Seq(index)) => { - if op_observer.text_as_seq() { - let value = (op.value(), doc.ops().id_to_exid(op.id)); - op_observer.insert(doc, ex_obj, index, value, false) - } else { - op_observer.splice_text(doc, ex_obj, index, op.to_str()) - } - } - _ => {} + let obj_type = doc.ops().object_type(&obj); + assert!(obj_type.unwrap().is_sequence()); + match (obj_type, prop) { + (Some(ObjType::List), Prop::Seq(index)) => { + let value = (op.value(), doc.ops().id_to_exid(op.id)); + op_observer.insert(doc, ex_obj, index, value) } + (Some(ObjType::Text), Prop::Seq(index)) => { + // FIXME + if op_observer.text_as_seq() { + let value = (op.value(), doc.ops().id_to_exid(op.id)); + op_observer.insert(doc, ex_obj, index, value) + } else { + op_observer.splice_text(doc, ex_obj, index, op.to_str()) + } + } + _ => {} } } else if op.is_delete() { op_observer.delete(doc, ex_obj, prop); diff --git a/rust/automerge/src/transaction/manual_transaction.rs b/rust/automerge/src/transaction/manual_transaction.rs index 52fdd0d7..fa5f6340 100644 --- a/rust/automerge/src/transaction/manual_transaction.rs +++ b/rust/automerge/src/transaction/manual_transaction.rs @@ -1,7 +1,6 @@ use std::ops::RangeBounds; use crate::exid::ExId; -use crate::marks::{ExpandMark, Mark}; use crate::op_observer::BranchableObserver; use crate::{ Automerge, ChangeHash, KeysAt, ObjType, OpObserver, Prop, ReadDoc, ScalarValue, Value, Values, @@ -191,18 +190,6 @@ impl<'a, Obs: observation::Observation> ReadDoc for Transaction<'a, Obs> { self.doc.text_at(obj, heads) } - fn marks>(&self, obj: O) -> Result>, AutomergeError> { - self.doc.marks(obj) - } - - fn marks_at>( - &self, - obj: O, - heads: &[ChangeHash], - ) -> Result>, AutomergeError> { - self.doc.marks_at(obj, heads) - } - fn get, P: Into>( &self, obj: O, @@ -343,25 +330,6 @@ impl<'a, Obs: observation::Observation> Transactable for Transaction<'a, Obs> { self.do_tx(|tx, doc, obs| tx.splice_text(doc, obs, obj.as_ref(), pos, del, text)) } - fn mark>( - &mut self, - obj: O, - mark: Mark<'_>, - expand: ExpandMark, - ) -> Result<(), AutomergeError> { - self.do_tx(|tx, doc, obs| tx.mark(doc, obs, obj.as_ref(), mark, expand)) - } - - fn unmark>( - &mut self, - obj: O, - name: &str, - start: usize, - end: usize, - ) -> Result<(), AutomergeError> { - self.do_tx(|tx, doc, obs| tx.unmark(doc, obs, obj.as_ref(), name, start, end)) - } - fn base_heads(&self) -> Vec { self.doc.get_heads() } diff --git a/rust/automerge/src/transaction/transactable.rs b/rust/automerge/src/transaction/transactable.rs index 81b83dff..05c48c79 100644 --- a/rust/automerge/src/transaction/transactable.rs +++ b/rust/automerge/src/transaction/transactable.rs @@ -1,5 +1,4 @@ use crate::exid::ExId; -use crate::marks::{ExpandMark, Mark}; use crate::{AutomergeError, ChangeHash, ObjType, Prop, ReadDoc, ScalarValue}; /// A way of mutating a document within a single change. @@ -89,21 +88,6 @@ pub trait Transactable: ReadDoc { text: &str, ) -> Result<(), AutomergeError>; - fn mark>( - &mut self, - obj: O, - mark: Mark<'_>, - expand: ExpandMark, - ) -> Result<(), AutomergeError>; - - fn unmark>( - &mut self, - obj: O, - key: &str, - start: usize, - end: usize, - ) -> Result<(), AutomergeError>; - /// The heads this transaction will be based on fn base_heads(&self) -> Vec; } diff --git a/rust/automerge/src/types.rs b/rust/automerge/src/types.rs index 468605b6..468986ec 100644 --- a/rust/automerge/src/types.rs +++ b/rust/automerge/src/types.rs @@ -14,7 +14,6 @@ mod opids; pub(crate) use opids::OpIds; pub(crate) use crate::clock::Clock; -pub(crate) use crate::marks::MarkData; pub(crate) use crate::value::{Counter, ScalarValue, Value}; pub(crate) const HEAD: ElemId = ElemId(OpId(0, 0)); @@ -199,8 +198,6 @@ pub enum OpType { Delete, Increment(i64), Put(ScalarValue), - MarkBegin(bool, MarkData), - MarkEnd(bool), } impl OpType { @@ -216,7 +213,6 @@ impl OpType { Self::Make(ObjType::Text) => 4, Self::Increment(_) => 5, Self::Make(ObjType::Table) => 6, - Self::MarkBegin(_, _) | Self::MarkEnd(_) => 7, } } @@ -231,17 +227,11 @@ impl OpType { _ => Err(error::InvalidOpType::NonNumericInc), }, 6 => Ok(()), - 7 => Ok(()), _ => Err(error::InvalidOpType::UnknownAction(action)), } } - pub(crate) fn from_action_and_value( - action: u64, - value: ScalarValue, - mark_name: Option, - expand: bool, - ) -> OpType { + pub(crate) fn from_action_and_value(action: u64, value: ScalarValue) -> OpType { match action { 0 => Self::Make(ObjType::Map), 1 => Self::Put(value), @@ -254,27 +244,9 @@ impl OpType { _ => unreachable!("validate_action_and_value returned NonNumericInc"), }, 6 => Self::Make(ObjType::Table), - 7 => match mark_name { - Some(name) => Self::MarkBegin(expand, MarkData { name, value }), - None => Self::MarkEnd(expand), - }, _ => unreachable!("validate_action_and_value returned UnknownAction"), } } - - pub(crate) fn to_str(&self) -> &str { - if let OpType::Put(ScalarValue::Str(s)) = &self { - s - } else if self.is_mark() { - "" - } else { - "\u{fffc}" - } - } - - pub(crate) fn is_mark(&self) -> bool { - matches!(&self, OpType::MarkBegin(_, _) | OpType::MarkEnd(_)) - } } impl From for OpType { @@ -454,13 +426,6 @@ impl Display for Prop { } impl Key { - pub(crate) fn prop_index(&self) -> Option { - match self { - Key::Map(n) => Some(*n), - Key::Seq(_) => None, - } - } - pub(crate) fn elemid(&self) -> Option { match self { Key::Map(_) => None, @@ -493,16 +458,6 @@ impl OpId { .cmp(&other.0) .then_with(|| actors[self.1 as usize].cmp(&actors[other.1 as usize])) } - - #[inline] - pub(crate) fn prev(&self) -> OpId { - OpId(self.0 - 1, self.1) - } - - #[inline] - pub(crate) fn next(&self) -> OpId { - OpId(self.0 + 1, self.1) - } } #[derive(Debug, Clone, Copy, PartialOrd, Eq, PartialEq, Ord, Hash, Default)] @@ -627,20 +582,14 @@ impl Op { } pub(crate) fn to_str(&self) -> &str { - self.action.to_str() - } - - pub(crate) fn visible(&self) -> bool { - if self.is_inc() || self.is_mark() { - false - } else if self.is_counter() { - self.succ.len() <= self.incs() + if let OpType::Put(ScalarValue::Str(s)) = &self.action { + s } else { - self.succ.is_empty() + "\u{fffc}" } } - pub(crate) fn visible_or_mark(&self) -> bool { + pub(crate) fn visible(&self) -> bool { if self.is_inc() { false } else if self.is_counter() { @@ -670,18 +619,6 @@ impl Op { matches!(&self.action, OpType::Put(ScalarValue::Counter(_))) } - pub(crate) fn is_mark(&self) -> bool { - self.action.is_mark() - } - - pub(crate) fn valid_mark_anchor(&self) -> bool { - self.succ.is_empty() - && matches!( - &self.action, - OpType::MarkBegin(true, _) | OpType::MarkEnd(false) - ) - } - pub(crate) fn is_noop(&self, action: &OpType) -> bool { matches!((&self.action, action), (OpType::Put(n), OpType::Put(m)) if n == m) } @@ -718,10 +655,6 @@ impl Op { match &self.action { OpType::Make(obj_type) => Value::Object(*obj_type), OpType::Put(scalar) => Value::Scalar(Cow::Borrowed(scalar)), - OpType::MarkBegin(_, mark) => { - Value::Scalar(Cow::Owned(format!("markBegin={}", mark.value).into())) - } - OpType::MarkEnd(_) => Value::Scalar(Cow::Owned("markEnd".into())), _ => panic!("cant convert op into a value - {:?}", self), } } @@ -742,8 +675,6 @@ impl Op { OpType::Make(obj) => format!("make{}", obj), OpType::Increment(val) => format!("inc:{}", val), OpType::Delete => "del".to_string(), - OpType::MarkBegin(_, _) => "markBegin".to_string(), - OpType::MarkEnd(_) => "markEnd".to_string(), } } } diff --git a/rust/automerge/src/value.rs b/rust/automerge/src/value.rs index 22a24c2b..be128787 100644 --- a/rust/automerge/src/value.rs +++ b/rust/automerge/src/value.rs @@ -341,12 +341,6 @@ impl<'a> From for Value<'a> { } } -impl<'a> From<&'a ScalarValue> for Value<'a> { - fn from(v: &'a ScalarValue) -> Self { - Value::Scalar(Cow::Borrowed(v)) - } -} - #[derive(Deserialize, Serialize, PartialEq, Debug, Clone, Copy)] pub(crate) enum DataType { #[serde(rename = "counter")] diff --git a/rust/automerge/src/visualisation.rs b/rust/automerge/src/visualisation.rs index 4e639141..31e9bbdb 100644 --- a/rust/automerge/src/visualisation.rs +++ b/rust/automerge/src/visualisation.rs @@ -249,8 +249,6 @@ impl OpTableRow { crate::OpType::Put(v) => format!("set {}", v), crate::OpType::Make(obj) => format!("make {}", obj), crate::OpType::Increment(v) => format!("inc {}", v), - crate::OpType::MarkBegin(_, m) => format!("markEnd {}", m), - crate::OpType::MarkEnd(m) => format!("markEnd {}", m), }; let prop = match op.key { crate::types::Key::Map(k) => metadata.props[k].clone(), diff --git a/rust/automerge/tests/test.rs b/rust/automerge/tests/test.rs index 3ba1df40..3be6725e 100644 --- a/rust/automerge/tests/test.rs +++ b/rust/automerge/tests/test.rs @@ -6,7 +6,7 @@ use automerge::{ use std::fs; // set up logging for all the tests -use test_log::test; +//use test_log::test; #[allow(unused_imports)] use automerge_test::{