Skip to content

Commit

Permalink
Implement Tree.RemoveStyle (#158)
Browse files Browse the repository at this point in the history
* Implement Protocol Changes for Tree.RemoveStyle

* merge similar functions
  • Loading branch information
humdrum authored Apr 16, 2024
1 parent 5f1aa42 commit cbde6a0
Show file tree
Hide file tree
Showing 16 changed files with 292 additions and 44 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/add-release-assets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ env:

jobs:
build:
runs-on: macos-13
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Generate API reference doc
run: |
swift package --allow-writing-to-directory $DOC_ARCH \
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/gh-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ env:

jobs:
build:
runs-on: macos-13
runs-on: macos-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Generate API reference doc
run: |
swift package --allow-writing-to-directory $OUTPUT_PATH \
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/swift-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,3 @@ jobs:
- run: docker-compose -f docker/docker-compose-ci.yml up --build -d
- name: Run tests
run: swift test --enable-code-coverage -v --filter YorkieIntegrationTests


10 changes: 6 additions & 4 deletions .github/workflows/swift.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@ on:

jobs:
build:
runs-on: macos-13
runs-on: macos-14
steps:
- uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: latest-stable
- uses: actions/checkout@v3
xcode-version: '15.3'
- uses: actions/checkout@v4
- name: SwiftLint install
run: brew install swiftlint
- name: SwiftLint
run: swiftlint lint --strict
- name: SwiftFormat
Expand All @@ -23,7 +25,7 @@ jobs:
- name: Prepare Code Coverage
run: xcrun llvm-cov export -format="lcov" .build/debug/YorkiePackageTests.xctest/Contents/MacOS/YorkiePackageTests -instr-profile .build/debug/codecov/default.profdata > lcov.info
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
file: lcov.info
env:
Expand Down
4 changes: 3 additions & 1 deletion Sources/API/Converter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,7 @@ extension Converter {
treeStyleOperation.attributes.forEach { key, value in
pbTreeStyleOperation.attributes[key] = value
}
pbTreeStyleOperation.attributesToRemove = treeStyleOperation.attributesToRemove
pbTreeStyleOperation.executedAt = toTimeTicket(treeStyleOperation.executedAt)
pbOperation.treeStyle = pbTreeStyleOperation
} else {
Expand Down Expand Up @@ -510,7 +511,8 @@ extension Converter {
return TreeStyleOperation(parentCreatedAt: fromTimeTicket(pbTreeStyleOperation.parentCreatedAt),
fromPos: fromTreePos(pbTreeStyleOperation.from),
toPos: fromTreePos(pbTreeStyleOperation.to),
attributes: pbTreeStyleOperation.attributes,
attributes: pbTreeStyleOperation.attributes,
attributesToRemove: pbTreeStyleOperation.attributesToRemove,
executedAt: fromTimeTicket(pbTreeStyleOperation.executedAt))
} else {
throw YorkieError.unimplemented(message: "unimplemented operation \(pbOperation)")
Expand Down
105 changes: 95 additions & 10 deletions Sources/API/V1/yorkie/v1/resources.pb.swift
Original file line number Diff line number Diff line change
Expand Up @@ -952,6 +952,11 @@ struct Yorkie_V1_Operation {
/// Clears the value of `executedAt`. Subsequent reads from it will return its default value.
mutating func clearExecutedAt() {_uniqueStorage()._executedAt = nil}

var attributesToRemove: [String] {
get {return _storage._attributesToRemove}
set {_uniqueStorage()._attributesToRemove = newValue}
}

var unknownFields = SwiftProtobuf.UnknownStorage()

init() {}
Expand Down Expand Up @@ -2533,7 +2538,15 @@ extension Yorkie_V1_Operation.Set: SwiftProtobuf.Message, SwiftProtobuf._Message
var _value: Yorkie_V1_JSONElementSimple? = nil
var _executedAt: Yorkie_V1_TimeTicket? = nil

static let defaultInstance = _StorageClass()
#if swift(>=5.10)
// This property is used as the initial default value for new instances of the type.
// The type itself is protecting the reference to its storage via CoW semantics.
// This will force a copy to be made of this reference when the first mutation occurs;
// hence, it is safe to mark this as `nonisolated(unsafe)`.
static nonisolated(unsafe) let defaultInstance = _StorageClass()
#else
static let defaultInstance = _StorageClass()
#endif

private init() {}

Expand Down Expand Up @@ -2625,7 +2638,15 @@ extension Yorkie_V1_Operation.Add: SwiftProtobuf.Message, SwiftProtobuf._Message
var _value: Yorkie_V1_JSONElementSimple? = nil
var _executedAt: Yorkie_V1_TimeTicket? = nil

static let defaultInstance = _StorageClass()
#if swift(>=5.10)
// This property is used as the initial default value for new instances of the type.
// The type itself is protecting the reference to its storage via CoW semantics.
// This will force a copy to be made of this reference when the first mutation occurs;
// hence, it is safe to mark this as `nonisolated(unsafe)`.
static nonisolated(unsafe) let defaultInstance = _StorageClass()
#else
static let defaultInstance = _StorageClass()
#endif

private init() {}

Expand Down Expand Up @@ -2825,7 +2846,15 @@ extension Yorkie_V1_Operation.Edit: SwiftProtobuf.Message, SwiftProtobuf._Messag
var _executedAt: Yorkie_V1_TimeTicket? = nil
var _attributes: Dictionary<String,String> = [:]

static let defaultInstance = _StorageClass()
#if swift(>=5.10)
// This property is used as the initial default value for new instances of the type.
// The type itself is protecting the reference to its storage via CoW semantics.
// This will force a copy to be made of this reference when the first mutation occurs;
// hence, it is safe to mark this as `nonisolated(unsafe)`.
static nonisolated(unsafe) let defaultInstance = _StorageClass()
#else
static let defaultInstance = _StorageClass()
#endif

private init() {}

Expand Down Expand Up @@ -2993,7 +3022,15 @@ extension Yorkie_V1_Operation.Style: SwiftProtobuf.Message, SwiftProtobuf._Messa
var _executedAt: Yorkie_V1_TimeTicket? = nil
var _createdAtMapByActor: Dictionary<String,Yorkie_V1_TimeTicket> = [:]

static let defaultInstance = _StorageClass()
#if swift(>=5.10)
// This property is used as the initial default value for new instances of the type.
// The type itself is protecting the reference to its storage via CoW semantics.
// This will force a copy to be made of this reference when the first mutation occurs;
// hence, it is safe to mark this as `nonisolated(unsafe)`.
static nonisolated(unsafe) let defaultInstance = _StorageClass()
#else
static let defaultInstance = _StorageClass()
#endif

private init() {}

Expand Down Expand Up @@ -3095,7 +3132,15 @@ extension Yorkie_V1_Operation.Increase: SwiftProtobuf.Message, SwiftProtobuf._Me
var _value: Yorkie_V1_JSONElementSimple? = nil
var _executedAt: Yorkie_V1_TimeTicket? = nil

static let defaultInstance = _StorageClass()
#if swift(>=5.10)
// This property is used as the initial default value for new instances of the type.
// The type itself is protecting the reference to its storage via CoW semantics.
// This will force a copy to be made of this reference when the first mutation occurs;
// hence, it is safe to mark this as `nonisolated(unsafe)`.
static nonisolated(unsafe) let defaultInstance = _StorageClass()
#else
static let defaultInstance = _StorageClass()
#endif

private init() {}

Expand Down Expand Up @@ -3187,7 +3232,15 @@ extension Yorkie_V1_Operation.TreeEdit: SwiftProtobuf.Message, SwiftProtobuf._Me
var _splitLevel: Int32 = 0
var _executedAt: Yorkie_V1_TimeTicket? = nil

static let defaultInstance = _StorageClass()
#if swift(>=5.10)
// This property is used as the initial default value for new instances of the type.
// The type itself is protecting the reference to its storage via CoW semantics.
// This will force a copy to be made of this reference when the first mutation occurs;
// hence, it is safe to mark this as `nonisolated(unsafe)`.
static nonisolated(unsafe) let defaultInstance = _StorageClass()
#else
static let defaultInstance = _StorageClass()
#endif

private init() {}

Expand Down Expand Up @@ -3290,6 +3343,7 @@ extension Yorkie_V1_Operation.TreeStyle: SwiftProtobuf.Message, SwiftProtobuf._M
3: .same(proto: "to"),
4: .same(proto: "attributes"),
5: .standard(proto: "executed_at"),
6: .standard(proto: "attributes_to_remove"),
]

fileprivate class _StorageClass {
Expand All @@ -3298,8 +3352,17 @@ extension Yorkie_V1_Operation.TreeStyle: SwiftProtobuf.Message, SwiftProtobuf._M
var _to: Yorkie_V1_TreePos? = nil
var _attributes: Dictionary<String,String> = [:]
var _executedAt: Yorkie_V1_TimeTicket? = nil

static let defaultInstance = _StorageClass()
var _attributesToRemove: [String] = []

#if swift(>=5.10)
// This property is used as the initial default value for new instances of the type.
// The type itself is protecting the reference to its storage via CoW semantics.
// This will force a copy to be made of this reference when the first mutation occurs;
// hence, it is safe to mark this as `nonisolated(unsafe)`.
static nonisolated(unsafe) let defaultInstance = _StorageClass()
#else
static let defaultInstance = _StorageClass()
#endif

private init() {}

Expand All @@ -3309,6 +3372,7 @@ extension Yorkie_V1_Operation.TreeStyle: SwiftProtobuf.Message, SwiftProtobuf._M
_to = source._to
_attributes = source._attributes
_executedAt = source._executedAt
_attributesToRemove = source._attributesToRemove
}
}

Expand All @@ -3332,6 +3396,7 @@ extension Yorkie_V1_Operation.TreeStyle: SwiftProtobuf.Message, SwiftProtobuf._M
case 3: try { try decoder.decodeSingularMessageField(value: &_storage._to) }()
case 4: try { try decoder.decodeMapField(fieldType: SwiftProtobuf._ProtobufMap<SwiftProtobuf.ProtobufString,SwiftProtobuf.ProtobufString>.self, value: &_storage._attributes) }()
case 5: try { try decoder.decodeSingularMessageField(value: &_storage._executedAt) }()
case 6: try { try decoder.decodeRepeatedStringField(value: &_storage._attributesToRemove) }()
default: break
}
}
Expand Down Expand Up @@ -3359,6 +3424,9 @@ extension Yorkie_V1_Operation.TreeStyle: SwiftProtobuf.Message, SwiftProtobuf._M
try { if let v = _storage._executedAt {
try visitor.visitSingularMessageField(value: v, fieldNumber: 5)
} }()
if !_storage._attributesToRemove.isEmpty {
try visitor.visitRepeatedStringField(value: _storage._attributesToRemove, fieldNumber: 6)
}
}
try unknownFields.traverse(visitor: &visitor)
}
Expand All @@ -3373,6 +3441,7 @@ extension Yorkie_V1_Operation.TreeStyle: SwiftProtobuf.Message, SwiftProtobuf._M
if _storage._to != rhs_storage._to {return false}
if _storage._attributes != rhs_storage._attributes {return false}
if _storage._executedAt != rhs_storage._executedAt {return false}
if _storage._attributesToRemove != rhs_storage._attributesToRemove {return false}
return true
}
if !storagesAreEqual {return false}
Expand Down Expand Up @@ -3973,7 +4042,15 @@ extension Yorkie_V1_RGANode: SwiftProtobuf.Message, SwiftProtobuf._MessageImplem
var _next: Yorkie_V1_RGANode? = nil
var _element: Yorkie_V1_JSONElement? = nil

static let defaultInstance = _StorageClass()
#if swift(>=5.10)
// This property is used as the initial default value for new instances of the type.
// The type itself is protecting the reference to its storage via CoW semantics.
// This will force a copy to be made of this reference when the first mutation occurs;
// hence, it is safe to mark this as `nonisolated(unsafe)`.
static nonisolated(unsafe) let defaultInstance = _StorageClass()
#else
static let defaultInstance = _StorageClass()
#endif

private init() {}

Expand Down Expand Up @@ -4205,7 +4282,15 @@ extension Yorkie_V1_TreeNode: SwiftProtobuf.Message, SwiftProtobuf._MessageImple
var _depth: Int32 = 0
var _attributes: Dictionary<String,Yorkie_V1_NodeAttr> = [:]

static let defaultInstance = _StorageClass()
#if swift(>=5.10)
// This property is used as the initial default value for new instances of the type.
// The type itself is protecting the reference to its storage via CoW semantics.
// This will force a copy to be made of this reference when the first mutation occurs;
// hence, it is safe to mark this as `nonisolated(unsafe)`.
static nonisolated(unsafe) let defaultInstance = _StorageClass()
#else
static let defaultInstance = _StorageClass()
#endif

private init() {}

Expand Down
1 change: 1 addition & 0 deletions Sources/API/V1/yorkie/v1/resources.proto
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ message Operation {
TreePos to = 3;
map<string, string> attributes = 4;
TimeTicket executed_at = 5;
repeated string attributes_to_remove = 6;
}

oneof body {
Expand Down
32 changes: 28 additions & 4 deletions Sources/Document/CRDT/CRDTTree.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,13 @@ struct TreeNodeForTest: Codable {
enum TreeChangeType {
case content
case style
case removeStyle
}

enum TreeChangeValue {
case nodes([CRDTTreeNode])
case attributes([String: String])
case attributesToRemove([String])
}

/**
Expand Down Expand Up @@ -571,28 +573,50 @@ class CRDTTree: CRDTGCElement {
*/
@discardableResult
func style(_ range: TreePosRange, _ attributes: [String: String]?, _ editedAt: TimeTicket) throws -> [TreeChange] {
try self.performChangeStyle(range, attributes, nil, editedAt)
}

/**
* `removeStyle` removes the given attributes of the given range.
*/
@discardableResult
func removeStyle(_ range: TreePosRange, _ attributesToRemove: [String], _ editedAt: TimeTicket) throws -> [TreeChange] {
try self.performChangeStyle(range, nil, attributesToRemove, editedAt)
}

private func performChangeStyle(_ range: TreePosRange, _ attributes: [String: String]?, _ attributesToRemove: [String]?, _ editedAt: TimeTicket) throws -> [TreeChange] {
let (fromParent, fromLeft) = try self.findNodesAndSplitText(range.0, editedAt)
let (toParent, toLeft) = try self.findNodesAndSplitText(range.1, editedAt)
var changes: [TreeChange] = []

var value: TreeChangeValue?
let value: TreeChangeValue?
let type: TreeChangeType

if let attributes {
value = .attributes(attributes)
type = .style
} else if let attributesToRemove {
value = .attributesToRemove(attributesToRemove)
type = .removeStyle
} else {
fatalError()
}

try self.traverseInPosRange(fromParent, fromLeft, toParent, toLeft) { token, _ in
let (node, _) = token
if node.isRemoved == false, node.isText == false, let attributes {
if node.isRemoved == false, node.isText == false {
if node.attrs == nil {
node.attrs = RHT()
}
for (key, value) in attributes {
for (key, value) in attributes ?? [:] {
node.attrs?.set(key: key, value: value, executedAt: editedAt)
}
for key in attributesToRemove ?? [] {
node.attrs?.remove(key: key, executedAt: editedAt)
}

try changes.append(TreeChange(actor: editedAt.actorID,
type: .style,
type: type,
from: self.toIndex(fromParent, fromLeft),
to: self.toIndex(toParent, toLeft),
fromPath: self.toPath(fromParent, fromLeft),
Expand Down
Loading

0 comments on commit cbde6a0

Please sign in to comment.