mirror of
https://github.com/apple/swift-foundation.git
synced 2025-05-17 18:59:40 +08:00
147 lines
5.4 KiB
Swift
147 lines
5.4 KiB
Swift
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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 {
|
|
/// A mutable rope of `_InternalRun` values, sliced on an arbitrary UTF-8 offset range.
|
|
/// Boundary runs may be sliced into two parts; this collection transparently constructs
|
|
/// virtual (sub-)runs representing the parts that are logically inside it.
|
|
///
|
|
/// Mutating one of these virtual runs effectively splits the underlying run, creating new runs
|
|
/// as needed. The usual run coalescing rules continue to apply, as if the underlying rope was
|
|
/// not sliced at all -- e.g., run coalescing will merge runs across the slice boundary.
|
|
struct _InternalRunsSlice {
|
|
typealias _AttributeStorage = AttributedString._AttributeStorage
|
|
typealias _InternalRun = AttributedString._InternalRun
|
|
typealias _InternalRuns = AttributedString._InternalRuns
|
|
typealias Storage = Rope<_InternalRun>
|
|
|
|
/// The guts of the underlying attributed string.
|
|
var _guts: Guts
|
|
|
|
/// The UTF-8 offset range of this slice.
|
|
///
|
|
/// We cannot (easily) store actual indices for the bounds -- mutations via `update`
|
|
/// would invalidate them, and keeping them updated would be too much trouble.
|
|
var _utf8Bounds: Range<Int>
|
|
|
|
init(_ guts: Guts, utf8Bounds: Range<Int>) {
|
|
assert(utf8Bounds.lowerBound >= 0 && utf8Bounds.upperBound <= guts.string.utf8.count)
|
|
self._guts = guts
|
|
self._utf8Bounds = utf8Bounds
|
|
}
|
|
}
|
|
}
|
|
|
|
extension AttributedString._InternalRunsSlice: BidirectionalCollection {
|
|
// Note: This is a true bidirectional collection, only supporting step-by-step access.
|
|
// (It would be possible to implement `index(_:offsetBy:)` in logarithmic time, but we have
|
|
// no use case for it -- this type is really just used to enumerate/mutate runs one by one
|
|
// within a certain range.
|
|
|
|
typealias Element = _InternalRun
|
|
|
|
/// Runs slices share index values with their base collection -- even for indices that address
|
|
/// partial runs on the edges of the slice.
|
|
typealias Index = _InternalRuns.Index
|
|
|
|
var isEmpty: Bool {
|
|
_utf8Bounds.isEmpty
|
|
}
|
|
|
|
var startIndex: Index {
|
|
_guts.runs.index(atUTF8Offset: _utf8Bounds.lowerBound).index
|
|
}
|
|
|
|
/// For non-empty slices, we reuse the base collection's endIndex as the slice's end.
|
|
/// This is O(1) and avoids having to find an index representing the actual end position,
|
|
/// which would be annoying to do when the bounds slice the last run in two halves.
|
|
///
|
|
/// For empty slices, we need to return the start index to avoid breaking Collection
|
|
/// expectations.
|
|
var endIndex: Index {
|
|
isEmpty ? startIndex : _guts.runs.endIndex
|
|
}
|
|
|
|
subscript(index: Index) -> Element {
|
|
let (attributes, fullRange) = _guts.run(at: index)
|
|
let clampedRange = fullRange.clamped(to: _utf8Bounds)
|
|
precondition(!clampedRange.isEmpty, "Index out of bounds")
|
|
return _InternalRun(length: clampedRange.count, attributes: attributes)
|
|
}
|
|
|
|
func index(after i: Index) -> Index {
|
|
// Note: this does not check that `i` is within the bounds of the slice.
|
|
let j = _guts.runs.index(after: i)
|
|
if j.utf8Offset >= _utf8Bounds.upperBound { return endIndex }
|
|
return j
|
|
}
|
|
|
|
func index(before i: Index) -> Index {
|
|
// Note: this does not check that `i` is within the bounds of the slice.
|
|
if i == _guts.runs.endIndex {
|
|
return _guts.runs.index(atUTF8Offset: _utf8Bounds.upperBound, preferEnd: true).index
|
|
}
|
|
return _guts.runs.index(before: i)
|
|
}
|
|
}
|
|
|
|
extension AttributedString._InternalRunsSlice {
|
|
func update(
|
|
at index: inout _InternalRuns.Index,
|
|
with body: (
|
|
_ attributes: inout _AttributeStorage,
|
|
_ utf8Range: Range<Int>,
|
|
_ mutated: inout Bool
|
|
) -> Void
|
|
) {
|
|
_guts.updateRun(at: &index, within: _utf8Bounds, with: body)
|
|
}
|
|
|
|
func updateEach(
|
|
with body: (
|
|
_ attributes: inout _AttributeStorage,
|
|
_ utf8Range: Range<Int>,
|
|
_ mutated: inout Bool
|
|
) -> Void
|
|
) {
|
|
var i = self.startIndex
|
|
while i < self.endIndex {
|
|
self.update(at: &i, with: body)
|
|
self.formIndex(after: &i)
|
|
}
|
|
}
|
|
|
|
func updateEach(
|
|
when predicate: (_AttributeStorage) -> Bool,
|
|
with body: (
|
|
_ attributes: inout _AttributeStorage,
|
|
_ utf8Range: Range<Int>
|
|
) -> Void
|
|
) {
|
|
var i = self.startIndex
|
|
while i < self.endIndex {
|
|
if predicate(self._guts.runs[i].attributes) {
|
|
self.update(at: &i) { attributes, range, _ in body(&attributes, range) }
|
|
}
|
|
self.formIndex(after: &i)
|
|
}
|
|
}
|
|
}
|