diff --git a/AutomergeUniffi/automerge.swift b/AutomergeUniffi/automerge.swift index 2abb0df..b1090f1 100644 --- a/AutomergeUniffi/automerge.swift +++ b/AutomergeUniffi/automerge.swift @@ -393,19 +393,6 @@ private struct FfiConverterUInt8: FfiConverterPrimitive { } } -private struct FfiConverterUInt32: FfiConverterPrimitive { - typealias FfiType = UInt32 - typealias SwiftType = UInt32 - - public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UInt32 { - try lift(readInt(&buf)) - } - - public static func write(_ value: SwiftType, into buf: inout [UInt8]) { - writeInt(&buf, lower(value)) - } -} - private struct FfiConverterUInt64: FfiConverterPrimitive { typealias FfiType = UInt64 typealias SwiftType = UInt64 @@ -504,6 +491,105 @@ private struct FfiConverterString: FfiConverter { } } +public protocol AmValueProtocol: AnyObject {} + +open class AmValue: + AmValueProtocol +{ + fileprivate let pointer: UnsafeMutableRawPointer! + + /// Used to instantiate a [FFIObject] without an actual pointer, for fakes in tests, mostly. + public struct NoPointer { + public init() {} + } + + // TODO: We'd like this to be `private` but for Swifty reasons, + // we can't implement `FfiConverter` without making this `required` and we can't + // make it `required` without making it `public`. + public required init(unsafeFromRawPointer pointer: UnsafeMutableRawPointer) { + self.pointer = pointer + } + + /// This constructor can be used to instantiate a fake object. + /// - Parameter noPointer: Placeholder value so we can have a constructor separate from the default empty one that + /// may be implemented for classes extending [FFIObject]. + /// + /// - Warning: + /// Any object instantiated with this constructor cannot be passed to an actual Rust-backed object. Since there + /// isn't a backing [Pointer] the FFI lower functions will crash. + public init(noPointer _: NoPointer) { + pointer = nil + } + + public func uniffiClonePointer() -> UnsafeMutableRawPointer { + try! rustCall { uniffi_uniffi_automerge_fn_clone_amvalue(self.pointer, $0) } + } + + public convenience init(input: ScalarValue) { + let pointer = + try! rustCall { + uniffi_uniffi_automerge_fn_constructor_amvalue_new( + FfiConverterTypeScalarValue.lower(input), $0 + ) + } + self.init(unsafeFromRawPointer: pointer) + } + + deinit { + guard let pointer = pointer else { + return + } + + try! rustCall { uniffi_uniffi_automerge_fn_free_amvalue(pointer, $0) } + } + + public static func newFromMap(input: MapValue) -> AmValue { + try! FfiConverterTypeAMValue.lift(try! rustCall { + uniffi_uniffi_automerge_fn_constructor_amvalue_new_from_map( + FfiConverterTypeMapValue.lower(input), $0 + ) + }) + } +} + +public struct FfiConverterTypeAMValue: FfiConverter { + typealias FfiType = UnsafeMutableRawPointer + typealias SwiftType = AmValue + + public static func lift(_ pointer: UnsafeMutableRawPointer) throws -> AmValue { + AmValue(unsafeFromRawPointer: pointer) + } + + public static func lower(_ value: AmValue) -> UnsafeMutableRawPointer { + value.uniffiClonePointer() + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AmValue { + let v: UInt64 = try readInt(&buf) + // The Rust code won't compile if a pointer won't fit in a UInt64. + // We have to go via `UInt` because that's the thing that's the size of a pointer. + let ptr = UnsafeMutableRawPointer(bitPattern: UInt(truncatingIfNeeded: v)) + if ptr == nil { + throw UniffiInternalError.unexpectedNullPointer + } + return try lift(ptr!) + } + + public static func write(_ value: AmValue, into buf: inout [UInt8]) { + // This fiddling is because `Int` is the thing that's the same size as a pointer. + // The Rust code won't compile if a pointer won't fit in a `UInt64`. + writeInt(&buf, UInt64(bitPattern: Int64(Int(bitPattern: lower(value))))) + } +} + +public func FfiConverterTypeAMValue_lift(_ pointer: UnsafeMutableRawPointer) throws -> AmValue { + try FfiConverterTypeAMValue.lift(pointer) +} + +public func FfiConverterTypeAMValue_lower(_ value: AmValue) -> UnsafeMutableRawPointer { + FfiConverterTypeAMValue.lower(value) +} + public protocol DocProtocol: AnyObject { func actorId() -> ActorId @@ -567,7 +653,7 @@ public protocol DocProtocol: AnyObject { func insertObjectInList(obj: ObjId, index: UInt64, objType: ObjType) throws -> ObjId - func joinBlock(obj: ObjId, index: UInt32) throws + func joinBlock(obj: ObjId, index: UInt64) throws func length(obj: ObjId) -> UInt64 @@ -617,7 +703,7 @@ public protocol DocProtocol: AnyObject { func spliceText(obj: ObjId, start: UInt64, delete: Int64, chars: String) throws - func splitBlock(obj: ObjId, index: UInt32) throws -> ObjId + func splitBlock(obj: ObjId, index: UInt64) throws -> ObjId func text(obj: ObjId) throws -> String @@ -1027,11 +1113,11 @@ open class Doc: }) } - open func joinBlock(obj: ObjId, index: UInt32) throws { try rustCallWithError(FfiConverterTypeDocError.lift) { + open func joinBlock(obj: ObjId, index: UInt64) throws { try rustCallWithError(FfiConverterTypeDocError.lift) { uniffi_uniffi_automerge_fn_method_doc_join_block( self.uniffiClonePointer(), FfiConverterTypeObjId.lower(obj), - FfiConverterUInt32.lower(index), + FfiConverterUInt64.lower(index), $0 ) } @@ -1308,12 +1394,12 @@ open class Doc: } } - open func splitBlock(obj: ObjId, index: UInt32) throws -> ObjId { + open func splitBlock(obj: ObjId, index: UInt64) throws -> ObjId { try FfiConverterTypeObjId.lift(rustCallWithError(FfiConverterTypeDocError.lift) { uniffi_uniffi_automerge_fn_method_doc_split_block( self.uniffiClonePointer(), FfiConverterTypeObjId.lower(obj), - FfiConverterUInt32.lower(index), + FfiConverterUInt64.lower(index), $0 ) }) @@ -1682,6 +1768,87 @@ public func FfiConverterTypeKeyValue_lower(_ value: KeyValue) -> RustBuffer { FfiConverterTypeKeyValue.lower(value) } +public struct ListValue { + public var value: AmValue + public var marks: [String: ScalarValue] + public var conflict: Bool + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(value: AmValue, marks: [String: ScalarValue], conflict: Bool) { + self.value = value + self.marks = marks + self.conflict = conflict + } +} + +public struct FfiConverterTypeListValue: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> ListValue { + try ListValue( + value: FfiConverterTypeAMValue.read(from: &buf), + marks: FfiConverterDictionaryStringTypeScalarValue.read(from: &buf), + conflict: FfiConverterBool.read(from: &buf) + ) + } + + public static func write(_ value: ListValue, into buf: inout [UInt8]) { + FfiConverterTypeAMValue.write(value.value, into: &buf) + FfiConverterDictionaryStringTypeScalarValue.write(value.marks, into: &buf) + FfiConverterBool.write(value.conflict, into: &buf) + } +} + +public func FfiConverterTypeListValue_lift(_ buf: RustBuffer) throws -> ListValue { + try FfiConverterTypeListValue.lift(buf) +} + +public func FfiConverterTypeListValue_lower(_ value: ListValue) -> RustBuffer { + FfiConverterTypeListValue.lower(value) +} + +public struct MapValue { + public var value: [String: ScalarValue] + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(value: [String: ScalarValue]) { + self.value = value + } +} + +extension MapValue: Equatable, Hashable { + public static func == (lhs: MapValue, rhs: MapValue) -> Bool { + if lhs.value != rhs.value { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(value) + } +} + +public struct FfiConverterTypeMapValue: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MapValue { + try MapValue( + value: FfiConverterDictionaryStringTypeScalarValue.read(from: &buf) + ) + } + + public static func write(_ value: MapValue, into buf: inout [UInt8]) { + FfiConverterDictionaryStringTypeScalarValue.write(value.value, into: &buf) + } +} + +public func FfiConverterTypeMapValue_lift(_ buf: RustBuffer) throws -> MapValue { + try FfiConverterTypeMapValue.lift(buf) +} + +public func FfiConverterTypeMapValue_lower(_ value: MapValue) -> RustBuffer { + FfiConverterTypeMapValue.lower(value) +} + public struct Mark { public var start: UInt64 public var end: UInt64 @@ -1749,6 +1916,49 @@ public func FfiConverterTypeMark_lower(_ value: Mark) -> RustBuffer { FfiConverterTypeMark.lower(value) } +public struct MarkSet { + public var marks: [String: ScalarValue] + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(marks: [String: ScalarValue]) { + self.marks = marks + } +} + +extension MarkSet: Equatable, Hashable { + public static func == (lhs: MarkSet, rhs: MarkSet) -> Bool { + if lhs.marks != rhs.marks { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(marks) + } +} + +public struct FfiConverterTypeMarkSet: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> MarkSet { + try MarkSet( + marks: FfiConverterDictionaryStringTypeScalarValue.read(from: &buf) + ) + } + + public static func write(_ value: MarkSet, into buf: inout [UInt8]) { + FfiConverterDictionaryStringTypeScalarValue.write(value.marks, into: &buf) + } +} + +public func FfiConverterTypeMarkSet_lift(_ buf: RustBuffer) throws -> MarkSet { + try FfiConverterTypeMarkSet.lift(buf) +} + +public func FfiConverterTypeMarkSet_lower(_ value: MarkSet) -> RustBuffer { + FfiConverterTypeMarkSet.lower(value) +} + public struct Patch { public var path: [PathElement] public var action: PatchAction @@ -1851,6 +2061,112 @@ public func FfiConverterTypePathElement_lower(_ value: PathElement) -> RustBuffe FfiConverterTypePathElement.lower(value) } +public struct TextValue { + public var value: String + public var marks: [String: ScalarValue] + + // Default memberwise initializers are never public by default, so we + // declare one manually. + public init(value: String, marks: [String: ScalarValue]) { + self.value = value + self.marks = marks + } +} + +extension TextValue: Equatable, Hashable { + public static func == (lhs: TextValue, rhs: TextValue) -> Bool { + if lhs.value != rhs.value { + return false + } + if lhs.marks != rhs.marks { + return false + } + return true + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(value) + hasher.combine(marks) + } +} + +public struct FfiConverterTypeTextValue: FfiConverterRustBuffer { + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> TextValue { + try TextValue( + value: FfiConverterString.read(from: &buf), + marks: FfiConverterDictionaryStringTypeScalarValue.read(from: &buf) + ) + } + + public static func write(_ value: TextValue, into buf: inout [UInt8]) { + FfiConverterString.write(value.value, into: &buf) + FfiConverterDictionaryStringTypeScalarValue.write(value.marks, into: &buf) + } +} + +public func FfiConverterTypeTextValue_lift(_ buf: RustBuffer) throws -> TextValue { + try FfiConverterTypeTextValue.lift(buf) +} + +public func FfiConverterTypeTextValue_lower(_ value: TextValue) -> RustBuffer { + FfiConverterTypeTextValue.lower(value) +} + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + +public enum AmValueType { + case scalar + case list + case map + case text +} + +public struct FfiConverterTypeAMValueType: FfiConverterRustBuffer { + typealias SwiftType = AmValueType + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> AmValueType { + let variant: Int32 = try readInt(&buf) + switch variant { + case 1: return .scalar + + case 2: return .list + + case 3: return .map + + case 4: return .text + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: AmValueType, into buf: inout [UInt8]) { + switch value { + case .scalar: + writeInt(&buf, Int32(1)) + + case .list: + writeInt(&buf, Int32(2)) + + case .map: + writeInt(&buf, Int32(3)) + + case .text: + writeInt(&buf, Int32(4)) + } + } +} + +public func FfiConverterTypeAMValueType_lift(_ buf: RustBuffer) throws -> AmValueType { + try FfiConverterTypeAMValueType.lift(buf) +} + +public func FfiConverterTypeAMValueType_lower(_ value: AmValueType) -> RustBuffer { + FfiConverterTypeAMValueType.lower(value) +} + +extension AmValueType: Equatable, Hashable {} + public enum DecodeSyncStateError { case Internal(message: String) } @@ -2500,6 +2816,62 @@ extension ScalarValue: Equatable, Hashable {} // Note that we don't yet support `indirect` for enums. // See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. +public enum Span { + case text( + text: String, + marks: MarkSet? + ) + case block( + value: MapValue + ) +} + +public struct FfiConverterTypeSpan: FfiConverterRustBuffer { + typealias SwiftType = Span + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Span { + let variant: Int32 = try readInt(&buf) + switch variant { + case 1: return try .text( + text: FfiConverterString.read(from: &buf), + marks: FfiConverterOptionTypeMarkSet.read(from: &buf) + ) + + case 2: return try .block( + value: FfiConverterTypeMapValue.read(from: &buf) + ) + + default: throw UniffiInternalError.unexpectedEnumCase + } + } + + public static func write(_ value: Span, into buf: inout [UInt8]) { + switch value { + case let .text(text, marks): + writeInt(&buf, Int32(1)) + FfiConverterString.write(text, into: &buf) + FfiConverterOptionTypeMarkSet.write(marks, into: &buf) + + case let .block(value): + writeInt(&buf, Int32(2)) + FfiConverterTypeMapValue.write(value, into: &buf) + } + } +} + +public func FfiConverterTypeSpan_lift(_ buf: RustBuffer) throws -> Span { + try FfiConverterTypeSpan.lift(buf) +} + +public func FfiConverterTypeSpan_lower(_ value: Span) -> RustBuffer { + FfiConverterTypeSpan.lower(value) +} + +extension Span: Equatable, Hashable {} + +// Note that we don't yet support `indirect` for enums. +// See https://github.com/mozilla/uniffi-rs/issues/396 for further discussion. + public enum Value { case object( typ: ObjType, @@ -2595,6 +2967,27 @@ private struct FfiConverterOptionTypeChange: FfiConverterRustBuffer { } } +private struct FfiConverterOptionTypeMarkSet: FfiConverterRustBuffer { + typealias SwiftType = MarkSet? + + public static func write(_ value: SwiftType, into buf: inout [UInt8]) { + guard let value = value else { + writeInt(&buf, Int8(0)) + return + } + writeInt(&buf, Int8(1)) + FfiConverterTypeMarkSet.write(value, into: &buf) + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType { + switch try readInt(&buf) as Int8 { + case 0: return nil + case 1: return try FfiConverterTypeMarkSet.read(from: &buf) + default: throw UniffiInternalError.unexpectedOptionalTag + } + } +} + private struct FfiConverterOptionTypeValue: FfiConverterRustBuffer { typealias SwiftType = Value? @@ -2856,6 +3249,29 @@ private struct FfiConverterSequenceTypeChangeHash: FfiConverterRustBuffer { } } +private struct FfiConverterDictionaryStringTypeScalarValue: FfiConverterRustBuffer { + public static func write(_ value: [String: ScalarValue], into buf: inout [UInt8]) { + let len = Int32(value.count) + writeInt(&buf, len) + for (key, value) in value { + FfiConverterString.write(key, into: &buf) + FfiConverterTypeScalarValue.write(value, into: &buf) + } + } + + public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [String: ScalarValue] { + let len: Int32 = try readInt(&buf) + var dict = [String: ScalarValue]() + dict.reserveCapacity(Int(len)) + for _ in 0 ..< len { + let key = try FfiConverterString.read(from: &buf) + let value = try FfiConverterTypeScalarValue.read(from: &buf) + dict[key] = value + } + return dict + } +} + private struct FfiConverterDictionaryStringTypeValue: FfiConverterRustBuffer { public static func write(_ value: [String: Value], into buf: inout [UInt8]) { let len = Int32(value.count) @@ -3123,7 +3539,7 @@ private var initializationResult: InitializationResult { if uniffi_uniffi_automerge_checksum_method_doc_insert_object_in_list() != 30538 { return InitializationResult.apiChecksumMismatch } - if uniffi_uniffi_automerge_checksum_method_doc_join_block() != 37348 { + if uniffi_uniffi_automerge_checksum_method_doc_join_block() != 8448 { return InitializationResult.apiChecksumMismatch } if uniffi_uniffi_automerge_checksum_method_doc_length() != 30352 { @@ -3198,7 +3614,7 @@ private var initializationResult: InitializationResult { if uniffi_uniffi_automerge_checksum_method_doc_splice_text() != 20602 { return InitializationResult.apiChecksumMismatch } - if uniffi_uniffi_automerge_checksum_method_doc_split_block() != 10956 { + if uniffi_uniffi_automerge_checksum_method_doc_split_block() != 15883 { return InitializationResult.apiChecksumMismatch } if uniffi_uniffi_automerge_checksum_method_doc_text() != 64716 { @@ -3225,6 +3641,12 @@ private var initializationResult: InitializationResult { if uniffi_uniffi_automerge_checksum_method_syncstate_their_heads() != 39870 { return InitializationResult.apiChecksumMismatch } + if uniffi_uniffi_automerge_checksum_constructor_amvalue_new() != 11832 { + return InitializationResult.apiChecksumMismatch + } + if uniffi_uniffi_automerge_checksum_constructor_amvalue_new_from_map() != 44769 { + return InitializationResult.apiChecksumMismatch + } if uniffi_uniffi_automerge_checksum_constructor_doc_load() != 20048 { return InitializationResult.apiChecksumMismatch } diff --git a/Sources/Automerge/Document.swift b/Sources/Automerge/Document.swift index 58fa1b8..c705f00 100644 --- a/Sources/Automerge/Document.swift +++ b/Sources/Automerge/Document.swift @@ -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: diff --git a/Sources/_CAutomergeUniffi/include/automergeFFI.h b/Sources/_CAutomergeUniffi/include/automergeFFI.h index d521cb4..eda6135 100644 --- a/Sources/_CAutomergeUniffi/include/automergeFFI.h +++ b/Sources/_CAutomergeUniffi/include/automergeFFI.h @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/Tests/AutomergeTests/TestBlocks.swift b/Tests/AutomergeTests/TestBlocks.swift new file mode 100644 index 0000000..2d41936 --- /dev/null +++ b/Tests/AutomergeTests/TestBlocks.swift @@ -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) + } +} diff --git a/rust/src/automerge.udl b/rust/src/automerge.udl index 76b24df..63d12a4 100644 --- a/rust/src/automerge.udl +++ b/rust/src/automerge.udl @@ -104,6 +104,35 @@ dictionary Mark { ScalarValue value; }; +dictionary MarkSet { + record marks; +}; + +[Enum] +interface AMValue { + Map ( record value ); + Scalar ( ScalarValue value ); + List ( sequence value ); + Text ( HydratedText value ); +}; + +dictionary HydratedText { + string value; + record marks; +}; + +dictionary HydratedListItem { + AMValue value; + record marks; + boolean conflict; +}; + +[Enum] +interface Span { + Text ( string text, MarkSet? marks ); + Block ( record value ); +}; + dictionary PathElement { Prop prop; ObjId obj; @@ -180,9 +209,9 @@ interface Doc { sequence marks_at_position(ObjId obj, Position position, sequence 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); diff --git a/rust/src/doc.rs b/rust/src/doc.rs index e115e5b..dad8e7b 100644 --- a/rust/src/doc.rs +++ b/rust/src/doc.rs @@ -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, @@ -500,20 +501,29 @@ impl Doc { Ok(Mark::from_markset(markset, index as u64)) } - pub fn split_block(&self, obj: ObjId, index: u32) -> Result { + pub fn split_block(&self, obj: ObjId, index: u64) -> Result { 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, 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) -> Result<(), DocError> { let mut doc = self.0.write().unwrap(); let mut other = other.0.write().unwrap(); diff --git a/rust/src/lib.rs b/rust/src/lib.rs index a0f14a8..ca0ebb4 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -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}; diff --git a/rust/src/span.rs b/rust/src/span.rs new file mode 100644 index 0000000..27357cf --- /dev/null +++ b/rust/src/span.rs @@ -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, + }, + /// A block marker + Block { value: HashMap }, +} + +// impl From 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>::from() }, + am::iter::Span::Block( value ) => Self::Block { value: HashMap::from(value) } + } + } +} + +impl<'a> From<&'a am::hydrate::Map> for HashMap { + fn from(value: &'a am::hydrate::Map) -> Self { + let mut new_hash_map:HashMap = 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, +} + +impl<'a> From<&'a am::marks::MarkSet> for MarkSet { + fn from(value: &'a am::marks::MarkSet) -> Self { + let mut new_hash:HashMap = 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 }, + Scalar { value: ScalarValue }, + List { value: Vec }, + 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, + 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, +}