Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Blocks2 - dealing with the abstract block structure #189

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
464 changes: 443 additions & 21 deletions AutomergeUniffi/automerge.swift

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions Sources/Automerge/Document.swift
Original file line number Diff line number Diff line change
Expand Up @@ -857,6 +857,25 @@ public final class Document: @unchecked Sendable {
try marksAt(obj: obj, position: position, heads: heads())
}

public func splitBlock(obj: ObjId, index: UInt64) throws -> ObjId {
try sync {
try self.doc.wrapErrors { doc in
sendObjectWillChange()
let objIdBytes = try doc.splitBlock(obj: obj.bytes, index: index)
return ObjId(bytes: objIdBytes)
}
}
}

public func joinBlock(obj: ObjId, index: UInt64) throws {
try sync {
try self.doc.wrapErrors { doc in
sendObjectWillChange()
try doc.joinBlock(obj: obj.bytes, index: index)
}
}
}

/// Commit the auto-generated transaction with options.
///
/// - Parameters:
Expand Down
36 changes: 34 additions & 2 deletions Sources/_CAutomergeUniffi/include/automergeFFI.h
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,26 @@ typedef struct UniffiForeignFutureStructVoid {
typedef void (*UniffiForeignFutureCompleteVoid)(uint64_t, UniffiForeignFutureStructVoid
);

#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_FN_CLONE_AMVALUE
#define UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_FN_CLONE_AMVALUE
void*_Nonnull uniffi_uniffi_automerge_fn_clone_amvalue(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_FN_FREE_AMVALUE
#define UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_FN_FREE_AMVALUE
void uniffi_uniffi_automerge_fn_free_amvalue(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_FN_CONSTRUCTOR_AMVALUE_NEW
#define UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_FN_CONSTRUCTOR_AMVALUE_NEW
void*_Nonnull uniffi_uniffi_automerge_fn_constructor_amvalue_new(RustBuffer input, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_FN_CONSTRUCTOR_AMVALUE_NEW_FROM_MAP
#define UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_FN_CONSTRUCTOR_AMVALUE_NEW_FROM_MAP
void*_Nonnull uniffi_uniffi_automerge_fn_constructor_amvalue_new_from_map(RustBuffer input, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_FN_CLONE_DOC
#define UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_FN_CLONE_DOC
Expand Down Expand Up @@ -434,7 +454,7 @@ RustBuffer uniffi_uniffi_automerge_fn_method_doc_insert_object_in_list(void*_Non
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_FN_METHOD_DOC_JOIN_BLOCK
#define UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_FN_METHOD_DOC_JOIN_BLOCK
void uniffi_uniffi_automerge_fn_method_doc_join_block(void*_Nonnull ptr, RustBuffer obj, uint32_t index, RustCallStatus *_Nonnull out_status
void uniffi_uniffi_automerge_fn_method_doc_join_block(void*_Nonnull ptr, RustBuffer obj, uint64_t index, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_FN_METHOD_DOC_LENGTH
Expand Down Expand Up @@ -559,7 +579,7 @@ void uniffi_uniffi_automerge_fn_method_doc_splice_text(void*_Nonnull ptr, RustBu
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_FN_METHOD_DOC_SPLIT_BLOCK
#define UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_FN_METHOD_DOC_SPLIT_BLOCK
RustBuffer uniffi_uniffi_automerge_fn_method_doc_split_block(void*_Nonnull ptr, RustBuffer obj, uint32_t index, RustCallStatus *_Nonnull out_status
RustBuffer uniffi_uniffi_automerge_fn_method_doc_split_block(void*_Nonnull ptr, RustBuffer obj, uint64_t index, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_FN_METHOD_DOC_TEXT
Expand Down Expand Up @@ -1303,6 +1323,18 @@ uint16_t uniffi_uniffi_automerge_checksum_method_syncstate_reset(void
#define UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_CHECKSUM_METHOD_SYNCSTATE_THEIR_HEADS
uint16_t uniffi_uniffi_automerge_checksum_method_syncstate_their_heads(void

);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_CHECKSUM_CONSTRUCTOR_AMVALUE_NEW
#define UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_CHECKSUM_CONSTRUCTOR_AMVALUE_NEW
uint16_t uniffi_uniffi_automerge_checksum_constructor_amvalue_new(void

);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_CHECKSUM_CONSTRUCTOR_AMVALUE_NEW_FROM_MAP
#define UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_CHECKSUM_CONSTRUCTOR_AMVALUE_NEW_FROM_MAP
uint16_t uniffi_uniffi_automerge_checksum_constructor_amvalue_new_from_map(void

);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_UNIFFI_AUTOMERGE_CHECKSUM_CONSTRUCTOR_DOC_LOAD
Expand Down
46 changes: 46 additions & 0 deletions Tests/AutomergeTests/TestBlocks.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
@testable import Automerge
import XCTest

class BlocksTestCase: XCTestCase {
func testSplitBlock() async throws {
// replicating test from https://github.com/automerge/automerge/blob/main/rust/automerge-wasm/test/blocks.mts#L8
// to verify interactions

// although looking through it, the test at
// https://github.com/automerge/automerge/blob/main/rust/automerge/tests/block_tests.rs#L11
// would make a lot more sense...
let doc = Document()
let text = try! doc.putObject(obj: ObjId.ROOT, key: "example", ty: ObjType.Text)
try doc.updateText(obj: text, value: "🐻🐻🐻bbbccc")
let result = try doc.splitBlock(obj: text, index: 6)
// try doc.walk()
}

/*
it("can split a block", () => {
const doc = create({ actor: "aabbcc" })
const text = doc.putObject("_root", "list", "🐻🐻🐻bbbccc")
doc.splitBlock(text, 6, { type: "li", parents: ["ul"], attrs: {kind: "todo" }});

NOTE(heckj):
^^ JS API wraps two calls in Rust api- first splitting the block, second updating the block that was just split

const spans = doc.spans("/list");
console.log(JSON.stringify(spans))
assert.deepStrictEqual(spans, [
{ type: "text", value: "🐻🐻🐻" },
{ type: 'block', value: { type: 'li', parents: ['ul'], attrs: {kind: "todo"} } },
{ type: 'text', value: 'bbbccc' }
])
})

*/

func testJoinBlock() async throws {
let doc = Document()
let text = try! doc.putObject(obj: ObjId.ROOT, key: "example", ty: ObjType.Text)
try doc.updateText(obj: text, value: "🐻🐻🐻bbbccc")
let result = try doc.splitBlock(obj: text, index: 6)
try doc.joinBlock(obj: text, index: 6)
}
}
33 changes: 31 additions & 2 deletions rust/src/automerge.udl
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,35 @@ dictionary Mark {
ScalarValue value;
};

dictionary MarkSet {
record<string,ScalarValue> marks;
};

[Enum]
interface AMValue {
Map ( record<string,AMValue> value );
Scalar ( ScalarValue value );
List ( sequence<HydratedListItem> value );
Text ( HydratedText value );
};

dictionary HydratedText {
string value;
record<string,ScalarValue> marks;
};

dictionary HydratedListItem {
AMValue value;
record<string,ScalarValue> marks;
boolean conflict;
};

[Enum]
interface Span {
Text ( string text, MarkSet? marks );
Block ( record<string,AMValue> value );
};

dictionary PathElement {
Prop prop;
ObjId obj;
Expand Down Expand Up @@ -180,9 +209,9 @@ interface Doc {
sequence<Mark> marks_at_position(ObjId obj, Position position, sequence<ChangeHash> heads);

[Throws=DocError]
ObjId split_block(ObjId obj, u32 index);
ObjId split_block(ObjId obj, u64 index);
[Throws=DocError]
void join_block(ObjId obj, u32 index);
void join_block(ObjId obj, u64 index);

[Throws=DocError]
void delete_in_map(ObjId obj, string key);
Expand Down
14 changes: 12 additions & 2 deletions rust/src/doc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use automerge::{transaction::Transactable, ReadDoc};
use crate::actor_id::ActorId;
use crate::cursor::Position;
use crate::mark::{ExpandMark, KeyValue, Mark};
use crate::span::{Span};
use crate::patches::Patch;
use crate::{
Change, ChangeHash, Cursor, ObjId, ObjType, PathElement, ScalarValue, SyncState, Value,
Expand Down Expand Up @@ -500,20 +501,29 @@ impl Doc {
Ok(Mark::from_markset(markset, index as u64))
}

pub fn split_block(&self, obj: ObjId, index: u32) -> Result<ObjId, DocError> {
pub fn split_block(&self, obj: ObjId, index: u64) -> Result<ObjId, DocError> {
let mut doc = self.0.write().unwrap();
let obj = am::ObjId::from(obj);
let id = doc.split_block(obj, index.try_into().unwrap())?;
Ok(id.into())
}

pub fn join_block(&self, obj: ObjId, index: u32) -> Result<(), DocError> {
pub fn join_block(&self, obj: ObjId, index: u64) -> Result<(), DocError> {
let mut doc = self.0.write().unwrap();
let obj = am::ObjId::from(obj);
doc.join_block(obj, index.try_into().unwrap())?;
Ok(())
}

pub fn spans(&self, obj: ObjId) -> Result<Vec<Span>, DocError> {
let mut doc = self.0.write().unwrap();
let obj = am::ObjId::from(obj);
let x = doc.spans(obj).unwrap();
let y = x
.into_iter()
.map(am::iter::spans::from);
}

pub fn merge(&self, other: Arc<Self>) -> Result<(), DocError> {
let mut doc = self.0.write().unwrap();
let mut other = other.0.write().unwrap();
Expand Down
2 changes: 2 additions & 0 deletions rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,5 @@ mod sync_state;
use sync_state::{DecodeSyncStateError, SyncState};
mod value;
use value::Value;
mod span;
use span::{AMValue, HydratedListItem, HydratedText, MarkSet, Span};
78 changes: 78 additions & 0 deletions rust/src/span.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
use crate::ScalarValue;
use std::collections::HashMap;
use automerge as am;

// maps to am::iter::Span
// need to create From<> in to convert over
pub enum Span {
/// A span of text and the marks that were active for that span
Text {
text: String,
marks: Option<MarkSet>,
},
/// A block marker
Block { value: HashMap<String, AMValue> },
}

// impl From<Span> for am::iter::Spans<'_> {
// fn from(value: Span) -> Self {
// let inner: [u8; 32] = value.0.try_into().unwrap();
// am::ChangeHash(inner)
// }
// }

impl<'a> From<&'a am::iter::Span> for Span {
fn from(value: &'a am::iter::Span) -> Self {
match value {
am::iter::Span::Text( t, m) => Self::Text { text: t.to_string(), marks: Option<Arc<am::marks::MarkSet>>::from() },
am::iter::Span::Block( value ) => Self::Block { value: HashMap<String, AMValue>::from(value) }
}
}
}

impl<'a> From<&'a am::hydrate::Map> for HashMap<String, AMValue> {
fn from(value: &'a am::hydrate::Map) -> Self {
let mut new_hash_map:HashMap<String, AMValue> = HashMap::new();
// fill in the middle bits...
return new_hash_map;
}
}

// loosely maps to am::marks:MarkSet
// need to create From<> in to convert over
pub struct MarkSet {
pub marks: HashMap<String, ScalarValue>,
}

impl<'a> From<&'a am::marks::MarkSet> for MarkSet {
fn from(value: &'a am::marks::MarkSet) -> Self {
let mut new_hash:HashMap<String, ScalarValue> = HashMap::new();
// iterate through MarkSet, building a hashmap for this MarkSet
for (k, v) in value.iter() {
new_hash.insert(k.to_string(), v.into());
}
Self { marks: new_hash }
}
}

pub enum AMValue {
Map { value: HashMap<String, AMValue> },
Scalar { value: ScalarValue },
List { value: Vec<HydratedListItem> },
Text { value: HydratedText },
}

// loosely maps to am::hydrate::ListValue
// need to create From<> in to convert over
pub struct HydratedListItem {
pub value: AMValue,
pub marks: HashMap<String, ScalarValue>,
pub conflict: bool,
}

// loosely maps to am::hydrate::Text
// need to create From<> in to convert over
pub struct HydratedText {
pub value: String,
pub marks: HashMap<String, ScalarValue>,
}