//===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2020-2023 Apple Inc. and the Swift project authors // Licensed under Apache License v2.0 with Runtime Library Exception // // See https://swift.org/LICENSE.txt for license information // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors // //===----------------------------------------------------------------------===// #if FOUNDATION_FRAMEWORK @_spi(Unstable) internal import CollectionsInternal #elseif canImport(_RopeModule) internal import _RopeModule #elseif canImport(_FoundationCollections) internal import _FoundationCollections #endif extension AttributedString { /// An internal convenience wrapper around `Rope<_InternalRun>`, giving it functionality /// that's specific to attributed strings. struct _InternalRuns: Sendable { typealias _InternalRun = AttributedString._InternalRun typealias Storage = Rope<_InternalRun> var _rope: Rope<_InternalRun> init() { self._rope = Rope() } init(_ rope: Storage) { self._rope = rope } init(_ runs: some Sequence<_InternalRun>) { self._rope = Rope(runs) } } } extension AttributedString._InternalRuns { /// A metric that assigns each run a size of 1; i.e., the metric corresponding to run offsets. /// /// Runs are not divisable under this metric. struct RunMetric: RopeMetric { typealias Element = AttributedString._InternalRun typealias Summary = Element.Summary @inline(__always) init() {} func size(of summary: Summary) -> Int { summary.count } func index(at offset: Int, in element: Element) -> Int { precondition(offset >= 0 && offset <= 1) return offset } } /// A metric where the size of each run is the number of UTF-8 code units it is covering. /// I.e., the metric corresponding to UTF-8 offsets. /// /// This enables to quickly find the run that corresponds to a particular UTF-8 position, or /// to find the UTF-8 offset range of a specific run. /// /// Runs are subdivided into UTF-8 positions under this metric, so that e.g. we can split a run /// between an arbitrary pair of UTF-8 code units. /// /// Note that runs do not have direct access to the underlying text data -- they only remember /// positional information. struct UTF8Metric: RopeMetric { typealias Element = AttributedString._InternalRun typealias Summary = Element.Summary @inline(__always) init() {} func size(of summary: Summary) -> Int { summary.utf8Length } func index(at offset: Int, in element: Element) -> Int { precondition(offset >= 0 && offset <= element.length) return offset } } } extension AttributedString._InternalRuns { struct Index { /// The underlying index in the rope. var base: Storage.Index /// The offset of this run from the start of the attributed string. var offset: Int /// The UTF-8 offset in the string of the start of this run. var utf8Offset: Int init(_ base: Storage.Index, offset: Int, utf8Offset: Int) { self.base = base self.offset = offset self.utf8Offset = utf8Offset } } } extension AttributedString._InternalRuns.Index: Equatable { static func ==(left: Self, right: Self) -> Bool { left.utf8Offset == right.utf8Offset } } extension AttributedString._InternalRuns.Index: Comparable { static func <(left: Self, right: Self) -> Bool { left.utf8Offset < right.utf8Offset } } extension AttributedString._InternalRuns: BidirectionalCollection { typealias Element = _InternalRun typealias SubSequence = Slice typealias _AttributeStorage = AttributedString._AttributeStorage var startIndex: Index { Index(_rope.startIndex, offset: 0, utf8Offset: 0) } var endIndex: Index { Index(_rope.endIndex, offset: self.count, utf8Offset: utf8Count) } var count: Int { _rope.count(in: RunMetric()) } func distance(from start: Index, to end: Index) -> Int { _rope.distance(from: start.base, to: end.base, in: RunMetric()) } func index(after index: Index) -> Index { var i = index formIndex(after: &i) return i } func formIndex(after i: inout Index) { i.offset += 1 i.utf8Offset += _rope[i.base].length _rope.formIndex(after: &i.base) } func index(before index: Index) -> Index { var i = index formIndex(before: &i) return i } func formIndex(before i: inout Index) { i.offset -= 1 _rope.formIndex(before: &i.base) i.utf8Offset -= _rope[i.base].length } func index(_ i: Index, offsetBy distance: Int) -> Index { let r = _rope.index(i.base, offsetBy: distance, in: RunMetric(), preferEnd: false) assert(r.remaining == 0) let utf8Distance = _rope.distance(from: i.base, to: r.index, in: UTF8Metric()) return Index(r.index, offset: i.offset + distance, utf8Offset: i.utf8Offset + utf8Distance) } func index(_ i: Index, offsetBy distance: Int, limitedBy limit: Index) -> Index? { // FIXME: Do we need a direct implementation? if distance >= 0 { if limit >= i { let d = self.distance(from: i, to: limit) if d < distance { return nil } } } else { if limit <= i { let d = self.distance(from: i, to: limit) if d > distance { return nil } } } return self.index(i, offsetBy: distance) } subscript(position: Index) -> _InternalRun { _rope[position.base] } } extension Slice { var utf8Count: Int { self.base.distance(from: self.startIndex, to: self.endIndex) } } extension AttributedString._InternalRuns { func index(atRunOffset runOffset: Int) -> Index { let r = _rope.find(at: runOffset, in: RunMetric(), preferEnd: false) assert(r.remaining == 0) let utf8Offset = _rope.offset(of: r.index, in: UTF8Metric()) return Index(r.index, offset: runOffset, utf8Offset: utf8Offset) } func index( atUTF8Offset utf8Offset: Int, preferEnd: Bool = false ) -> (index: Index, remainingUTF8: Int) { let r = _rope.find(at: utf8Offset, in: UTF8Metric(), preferEnd: preferEnd) let offset = _rope.offset(of: r.index, in: RunMetric()) return (Index(r.index, offset: offset, utf8Offset: utf8Offset - r.remaining), r.remaining) } func _exactIndex(atUTF8Offset utf8Offset: Int) -> Index { let r = index(atUTF8Offset: utf8Offset) precondition(r.remainingUTF8 == 0) return r.index } func index(containing i: AttributedString.Index) -> (index: Index, remainingUTF8: Int) { let r = index(atUTF8Offset: i._value.utf8Offset) return (r.index, r.remainingUTF8) } } extension AttributedString._InternalRuns { var utf8Count: Int { _rope.count(in: UTF8Metric()) } @_transparent mutating func _update( at index: inout Index, by body: (inout Element) -> R ) -> R { _rope.update(at: &index.base, by: body) } @discardableResult mutating func _remove(at index: Index) -> _InternalRun { _rope.remove(at: index.base) } mutating func _removeRuns(_ runOffsets: Range) { _rope.removeSubrange(runOffsets, in: RunMetric()) } // FIXME: Don't return a standalone rope. func extract(utf8Offsets: Range) -> Self { Self(_rope.extract(utf8Offsets, in: UTF8Metric())) } } extension AttributedString._InternalRuns { /// Update the run at `index` with the provided `run` and coalesce it with its /// neighbors if necessary. `index` is updated to a valid index addressing the /// run in the new collection that includes the UTF-8 range of the original run. mutating func updateAndCoalesce( at index: inout Index, with body: (inout _AttributeStorage) -> Void ) { var offset = index.offset var utf8Offset = index.utf8Offset var i = index.base let attributes = _rope.update(at: &i, by: { body(&$0.attributes) return $0.attributes }) let next = _rope.index(after: i) if next < _rope.endIndex, _rope[next].attributes == attributes { // Coalesce with next run, preserving position. let utf8Length = _rope.remove(at: &i).length _rope.update(at: &i) { $0.length += utf8Length } } if i > _rope.startIndex { let prev = _rope.index(before: i) if _rope[prev].attributes == attributes { // Coalesce with previous run, preserving position. let utf8Length = _rope.remove(at: &i).length _rope.formIndex(before: &i) _rope.update(at: &i) { utf8Offset -= $0.length $0.length += utf8Length } offset -= 1 } } index = Index(i, offset: offset, utf8Offset: utf8Offset) } /// Replaces the runs within specified UTF-8 offset range with the supplied collection, which /// must be properly coalesced. This method takes care of coalescing the edges if necessary. /// /// Note: This has no way to access string data, so it does not try to enforce run /// constraints on paragraph/character boundaries. (You can call /// `Guts.enforceAttributeConstraintsAfterMutation` to do that, after this call.) mutating func replaceUTF8Subrange( _ range: Range, with newElements: some Sequence<_InternalRun> ) { let origUTF8Length = _rope.summary.utf8Length _rope.replaceSubrange(range, in: UTF8Metric(), with: newElements) let newUTF8Length = _rope.summary.utf8Length let upperBound = range.upperBound + (newUTF8Length - origUTF8Length) if upperBound > 0, upperBound < newUTF8Length { var index = _exactIndex(atUTF8Offset: upperBound).base let attrs = _rope[index].attributes _rope.formIndex(before: &index) if _rope[index].attributes == attrs { // Coalesce with first item after replacement. let utf8Length = _rope.remove(at: &index).length _rope.update(at: &index) { $0.length += utf8Length } } } if range.lowerBound > 0, upperBound > range.lowerBound { var index = _exactIndex(atUTF8Offset: range.lowerBound).base let run = _rope[index] _rope.formIndex(before: &index) if _rope[index].attributes == run.attributes { // Coalesce with last item before replacement. _rope.update(at: &index) { $0.length += run.length } _rope.formIndex(after: &index) _rope.remove(at: index) } } } }