mirror of
https://github.com/apple/swift-foundation.git
synced 2025-05-22 13:39:25 +08:00
* rdar://108152217 Start using Rope for attribute run storage - Replace `Array<_InternalRun>` with `Rope<_InternalRun>`. - Fix semantics of AttributeRunBoundaries.character. It no longer pretends that attributes can be tied to grapheme clusters — that never actually worked properly, and it cannot be supported without breaking the intended use case. - Remove cache of latest run position; log(n) might be fast enough that we don’t need to worry about that. - Switch AttributedString.Guts members to take/return BigString.Index values, not AttributedString indices. This cuts down on constant back-and-forth conversions. - Discard unused Guts members; update remaining mutation methods to follow Swift naming conventions. - Stop using the `public extension` language misfeature. * rdar://108152217 Apply notes from code review AttributedString.CharacterView and AttributedString.UnicodeScalarView did not use the right semantics in the case when the underlying text was not touched. It’s okay in that case to not mutate text storage, but we still want to override the attributes within the subrange as if the client actually did replace text — as in, we want to use precisely the same attribute storage for the range on both paths. Also, make sure we correctly enforce attribute constraints in the `!hasStringChanges` case, in both views. * rdar://108152217 Fix logic error in AttributedString.Runs.subscript (Discovered during code review.) * rdar://108152217 Clean up CharacterView/UnicodeScalarView mutations & update failing test * rdar://108152217 Apply review notes
823 lines
26 KiB
Swift
823 lines
26 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
|
||
@_implementationOnly @_spi(Unstable) import CollectionsInternal
|
||
#else
|
||
package import _RopeModule
|
||
#endif
|
||
|
||
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
|
||
extension AttributedString.Runs {
|
||
@preconcurrency
|
||
public struct AttributesSlice1<T : AttributedStringKey> : BidirectionalCollection, Sendable
|
||
where T.Value : Sendable {
|
||
public typealias Index = AttributedString.Index
|
||
|
||
// FIXME: Why no labels?
|
||
public typealias Element = (T.Value?, Range<AttributedString.Index>)
|
||
|
||
internal typealias Runs = AttributedString.Runs
|
||
|
||
let runs: Runs
|
||
let _names: [String]
|
||
let _constraints: [AttributeRunBoundaries]
|
||
|
||
init(runs: Runs) {
|
||
self.runs = runs
|
||
// FIXME: ☠️ Get these from a proper cache in runs._guts.
|
||
_names = [T.name]
|
||
_constraints = T._constraintsInvolved
|
||
}
|
||
|
||
public struct Iterator: IteratorProtocol, Sendable {
|
||
// Note: This is basically equivalent to `IndexingIterator`.
|
||
|
||
public typealias Element = AttributesSlice1.Element
|
||
|
||
let _slice: AttributesSlice1
|
||
var _index: AttributesSlice1.Index
|
||
|
||
internal init(_ slice: AttributesSlice1) {
|
||
self._slice = slice
|
||
self._index = slice.startIndex
|
||
}
|
||
|
||
public mutating func next() -> Element? {
|
||
if _index == _slice.endIndex {
|
||
return nil
|
||
}
|
||
let run = _slice.runs[_index]
|
||
let next = _slice.index(after: _index)
|
||
let range = _index ..< next
|
||
_index = next
|
||
return (run._attributes[T.self], range)
|
||
}
|
||
}
|
||
|
||
public func makeIterator() -> Iterator {
|
||
Iterator(self)
|
||
}
|
||
|
||
public var startIndex: Index {
|
||
Index(runs._strBounds.lowerBound)
|
||
}
|
||
|
||
public var endIndex: Index {
|
||
Index(runs._strBounds.upperBound)
|
||
}
|
||
|
||
public func index(before i: Index) -> Index {
|
||
runs._slicedRunBoundary(
|
||
before: i,
|
||
attributeNames: _names,
|
||
constraints: _constraints)
|
||
}
|
||
|
||
public func index(after i: Index) -> Index {
|
||
runs._slicedRunBoundary(
|
||
after: i,
|
||
attributeNames: _names,
|
||
constraints: _constraints)
|
||
}
|
||
|
||
public subscript(position: AttributedString.Index) -> Element {
|
||
let (start, runIndex) = runs._slicedRunBoundary(
|
||
roundingDown: position,
|
||
attributeNames: _names,
|
||
constraints: _constraints)
|
||
let end = self.index(after: position)
|
||
let attributes = runs._guts.runs[runIndex].attributes
|
||
return (attributes[T.self], start ..< end)
|
||
}
|
||
|
||
// FIXME: This is a collection with potentially unaligned indices that uses Slice as its
|
||
// SubSequence. Slicing the collection on such an index will produce spurious crashes.
|
||
// Add a custom implementation for the range subscript that forcibly rounds the given bounds
|
||
// down to the nearest valid indices.
|
||
}
|
||
|
||
public subscript<T : AttributedStringKey>(_ keyPath: KeyPath<AttributeDynamicLookup, T>) -> AttributesSlice1<T> {
|
||
return AttributesSlice1<T>(runs: self)
|
||
}
|
||
|
||
public subscript<T : AttributedStringKey>(_ t: T.Type) -> AttributesSlice1<T> {
|
||
return AttributesSlice1<T>(runs: self)
|
||
}
|
||
}
|
||
|
||
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
|
||
extension AttributedString.Runs {
|
||
@preconcurrency
|
||
public struct AttributesSlice2<
|
||
T : AttributedStringKey,
|
||
U : AttributedStringKey
|
||
> : BidirectionalCollection, Sendable
|
||
where
|
||
T.Value : Sendable,
|
||
U.Value : Sendable
|
||
{
|
||
public typealias Index = AttributedString.Index
|
||
|
||
// FIXME: Why no labels?
|
||
public typealias Element = (T.Value?, U.Value?, Range<AttributedString.Index>)
|
||
|
||
internal typealias Runs = AttributedString.Runs
|
||
|
||
let runs : Runs
|
||
let _names: [String]
|
||
let _constraints: [AttributeRunBoundaries]
|
||
|
||
init(runs: Runs) {
|
||
self.runs = runs
|
||
// FIXME: ☠️ Get these from a proper cache in runs._guts.
|
||
_names = [T.name, U.name]
|
||
_constraints = Array(_contents: T.runBoundaries, U.runBoundaries)
|
||
}
|
||
|
||
public struct Iterator: IteratorProtocol, Sendable {
|
||
// Note: This is basically equivalent to `IndexingIterator`.
|
||
|
||
public typealias Element = AttributesSlice2.Element
|
||
|
||
let _slice: AttributesSlice2
|
||
var _index: AttributedString.Index
|
||
|
||
internal init(_ slice: AttributesSlice2) {
|
||
self._slice = slice
|
||
self._index = slice.startIndex
|
||
}
|
||
|
||
public mutating func next() -> Element? {
|
||
if _index == _slice.endIndex {
|
||
return nil
|
||
}
|
||
let run = _slice.runs[_index]
|
||
let next = _slice.index(after: _index)
|
||
let range = _index ..< next
|
||
_index = next
|
||
return (run._attributes[T.self], run._attributes[U.self], range)
|
||
}
|
||
}
|
||
|
||
public func makeIterator() -> Iterator {
|
||
Iterator(self)
|
||
}
|
||
|
||
public var startIndex: Index {
|
||
Index(runs._strBounds.lowerBound)
|
||
}
|
||
|
||
public var endIndex: Index {
|
||
Index(runs._strBounds.upperBound)
|
||
}
|
||
|
||
public func index(before i: Index) -> Index {
|
||
runs._slicedRunBoundary(
|
||
before: i,
|
||
attributeNames: _names,
|
||
constraints: _constraints)
|
||
}
|
||
|
||
public func index(after i: Index) -> Index {
|
||
runs._slicedRunBoundary(
|
||
after: i,
|
||
attributeNames: _names,
|
||
constraints: _constraints)
|
||
}
|
||
|
||
public subscript(position: AttributedString.Index) -> Element {
|
||
let (start, runIndex) = runs._slicedRunBoundary(
|
||
roundingDown: position,
|
||
attributeNames: _names,
|
||
constraints: _constraints)
|
||
let end = self.index(after: position)
|
||
let attributes = runs._guts.runs[runIndex].attributes
|
||
return (attributes[T.self], attributes[U.self], start ..< end)
|
||
}
|
||
|
||
// FIXME: This is a collection with potentially unaligned indices that uses Slice as its
|
||
// SubSequence. Slicing the collection on such an index will produce spurious crashes.
|
||
// Add a custom implementation for the range subscript that forcibly rounds the given bounds
|
||
// down to the nearest valid indices.
|
||
}
|
||
|
||
public subscript <
|
||
T : AttributedStringKey,
|
||
U : AttributedStringKey
|
||
> (
|
||
_ t: KeyPath<AttributeDynamicLookup, T>,
|
||
_ u: KeyPath<AttributeDynamicLookup, U>
|
||
) -> AttributesSlice2<T, U> {
|
||
return AttributesSlice2<T, U>(runs: self)
|
||
}
|
||
|
||
public subscript <
|
||
T : AttributedStringKey,
|
||
U : AttributedStringKey
|
||
> (
|
||
_ t: T.Type,
|
||
_ u: U.Type
|
||
) -> AttributesSlice2<T, U> {
|
||
return AttributesSlice2<T, U>(runs: self)
|
||
}
|
||
}
|
||
|
||
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
|
||
extension AttributedString.Runs {
|
||
@preconcurrency
|
||
public struct AttributesSlice3<
|
||
T : AttributedStringKey,
|
||
U : AttributedStringKey,
|
||
V : AttributedStringKey
|
||
> : BidirectionalCollection, Sendable
|
||
where
|
||
T.Value : Sendable,
|
||
U.Value : Sendable,
|
||
V.Value : Sendable
|
||
{
|
||
public typealias Index = AttributedString.Index
|
||
|
||
// FIXME: Why no labels?
|
||
public typealias Element = (T.Value?, U.Value?, V.Value?, Range<AttributedString.Index>)
|
||
|
||
internal typealias Runs = AttributedString.Runs
|
||
|
||
let runs : Runs
|
||
let _names: [String]
|
||
let _constraints: [AttributeRunBoundaries]
|
||
|
||
init(runs: Runs) {
|
||
self.runs = runs
|
||
// FIXME: ☠️ Get these from a proper cache in runs._guts.
|
||
_names = [T.name, U.name, V.name]
|
||
_constraints = Array(_contents: T.runBoundaries, U.runBoundaries, V.runBoundaries)
|
||
}
|
||
|
||
public struct Iterator: IteratorProtocol, Sendable {
|
||
// Note: This is basically equivalent to `IndexingIterator`.
|
||
|
||
public typealias Element = AttributesSlice3.Element
|
||
|
||
let _slice: AttributesSlice3
|
||
var _index: AttributedString.Index
|
||
|
||
internal init(_ slice: AttributesSlice3) {
|
||
self._slice = slice
|
||
self._index = slice.startIndex
|
||
}
|
||
|
||
public mutating func next() -> Element? {
|
||
if _index == _slice.endIndex {
|
||
return nil
|
||
}
|
||
let run = _slice.runs[_index]
|
||
let next = _slice.index(after: _index)
|
||
let range = _index ..< next
|
||
_index = next
|
||
return (
|
||
run._attributes[T.self],
|
||
run._attributes[U.self],
|
||
run._attributes[V.self],
|
||
range)
|
||
}
|
||
}
|
||
|
||
public func makeIterator() -> Iterator {
|
||
Iterator(self)
|
||
}
|
||
|
||
public var startIndex: Index {
|
||
Index(runs._strBounds.lowerBound)
|
||
}
|
||
|
||
public var endIndex: Index {
|
||
Index(runs._strBounds.upperBound)
|
||
}
|
||
|
||
public func index(before i: Index) -> Index {
|
||
runs._slicedRunBoundary(
|
||
before: i,
|
||
attributeNames: _names,
|
||
constraints: _constraints)
|
||
}
|
||
|
||
public func index(after i: Index) -> Index {
|
||
runs._slicedRunBoundary(
|
||
after: i,
|
||
attributeNames: _names,
|
||
constraints: _constraints)
|
||
}
|
||
|
||
public subscript(position: AttributedString.Index) -> Element {
|
||
let (start, runIndex) = runs._slicedRunBoundary(
|
||
roundingDown: position,
|
||
attributeNames: _names,
|
||
constraints: _constraints)
|
||
let end = self.index(after: position)
|
||
let attributes = runs._guts.runs[runIndex].attributes
|
||
return (attributes[T.self], attributes[U.self], attributes[V.self], start ..< end)
|
||
}
|
||
|
||
// FIXME: This is a collection with potentially unaligned indices that uses Slice as its
|
||
// SubSequence. Slicing the collection on such an index will produce spurious crashes.
|
||
// Add a custom implementation for the range subscript that forcibly rounds the given bounds
|
||
// down to the nearest valid indices.
|
||
}
|
||
|
||
public subscript <
|
||
T : AttributedStringKey,
|
||
U : AttributedStringKey,
|
||
V : AttributedStringKey
|
||
> (
|
||
_ t: KeyPath<AttributeDynamicLookup, T>,
|
||
_ u: KeyPath<AttributeDynamicLookup, U>,
|
||
_ v: KeyPath<AttributeDynamicLookup, V>
|
||
) -> AttributesSlice3<T, U, V> {
|
||
return AttributesSlice3<T, U, V>(runs: self)
|
||
}
|
||
|
||
public subscript <
|
||
T : AttributedStringKey,
|
||
U : AttributedStringKey,
|
||
V : AttributedStringKey
|
||
> (
|
||
_ t: T.Type,
|
||
_ u: U.Type,
|
||
_ v: V.Type
|
||
) -> AttributesSlice3<T, U, V> {
|
||
return AttributesSlice3<T, U, V>(runs: self)
|
||
}
|
||
}
|
||
|
||
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
|
||
extension AttributedString.Runs {
|
||
@preconcurrency
|
||
public struct AttributesSlice4<
|
||
T : AttributedStringKey,
|
||
U : AttributedStringKey,
|
||
V : AttributedStringKey,
|
||
W : AttributedStringKey
|
||
> : BidirectionalCollection, Sendable
|
||
where
|
||
T.Value : Sendable,
|
||
U.Value : Sendable,
|
||
V.Value : Sendable,
|
||
W.Value : Sendable
|
||
{
|
||
public typealias Index = AttributedString.Index
|
||
|
||
// FIXME: Why no labels?
|
||
public typealias Element = (T.Value?, U.Value?, V.Value?, W.Value?, Range<AttributedString.Index>)
|
||
|
||
internal typealias Runs = AttributedString.Runs
|
||
|
||
let runs : Runs
|
||
let _names: [String]
|
||
let _constraints: [AttributeRunBoundaries]
|
||
|
||
init(runs: Runs) {
|
||
self.runs = runs
|
||
// FIXME: ☠️ Get these from a proper cache in runs._guts.
|
||
_names = [T.name, U.name, V.name, W.name]
|
||
_constraints = Array(
|
||
_contents: T.runBoundaries, U.runBoundaries, V.runBoundaries, W.runBoundaries)
|
||
}
|
||
|
||
public struct Iterator: IteratorProtocol, Sendable {
|
||
// Note: This is basically equivalent to `IndexingIterator`.
|
||
|
||
public typealias Element = AttributesSlice4.Element
|
||
|
||
let _slice: AttributesSlice4
|
||
var _index: AttributedString.Index
|
||
|
||
internal init(_ slice: AttributesSlice4) {
|
||
self._slice = slice
|
||
self._index = slice.startIndex
|
||
}
|
||
|
||
public mutating func next() -> Element? {
|
||
if _index == _slice.endIndex {
|
||
return nil
|
||
}
|
||
let run = _slice.runs[_index]
|
||
let next = _slice.index(after: _index)
|
||
let range = _index ..< next
|
||
_index = next
|
||
return (
|
||
run._attributes[T.self],
|
||
run._attributes[U.self],
|
||
run._attributes[V.self],
|
||
run._attributes[W.self],
|
||
range)
|
||
}
|
||
}
|
||
|
||
public func makeIterator() -> Iterator {
|
||
Iterator(self)
|
||
}
|
||
|
||
public var startIndex: Index {
|
||
Index(runs._strBounds.lowerBound)
|
||
}
|
||
|
||
public var endIndex: Index {
|
||
Index(runs._strBounds.upperBound)
|
||
}
|
||
|
||
public func index(before i: Index) -> Index {
|
||
runs._slicedRunBoundary(
|
||
before: i,
|
||
attributeNames: _names,
|
||
constraints: _constraints)
|
||
}
|
||
|
||
public func index(after i: Index) -> Index {
|
||
runs._slicedRunBoundary(
|
||
after: i,
|
||
attributeNames: _names,
|
||
constraints: _constraints)
|
||
}
|
||
|
||
public subscript(position: AttributedString.Index) -> Element {
|
||
let (start, runIndex) = runs._slicedRunBoundary(
|
||
roundingDown: position,
|
||
attributeNames: _names,
|
||
constraints: _constraints)
|
||
let end = self.index(after: position)
|
||
let attributes = runs._guts.runs[runIndex].attributes
|
||
return (
|
||
attributes[T.self],
|
||
attributes[U.self],
|
||
attributes[V.self],
|
||
attributes[W.self],
|
||
start ..< end)
|
||
}
|
||
|
||
// FIXME: This is a collection with potentially unaligned indices that uses Slice as its
|
||
// SubSequence. Slicing the collection on such an index will produce spurious crashes.
|
||
// Add a custom implementation for the range subscript that forcibly rounds the given bounds
|
||
// down to the nearest valid indices.
|
||
}
|
||
|
||
public subscript <
|
||
T : AttributedStringKey,
|
||
U : AttributedStringKey,
|
||
V : AttributedStringKey,
|
||
W : AttributedStringKey
|
||
> (
|
||
_ t: KeyPath<AttributeDynamicLookup, T>,
|
||
_ u: KeyPath<AttributeDynamicLookup, U>,
|
||
_ v: KeyPath<AttributeDynamicLookup, V>,
|
||
_ w: KeyPath<AttributeDynamicLookup, W>
|
||
) -> AttributesSlice4<T, U, V, W> {
|
||
return AttributesSlice4<T, U, V, W>(runs: self)
|
||
}
|
||
|
||
public subscript <
|
||
T : AttributedStringKey,
|
||
U : AttributedStringKey,
|
||
V : AttributedStringKey,
|
||
W : AttributedStringKey
|
||
> (
|
||
_ t: T.Type,
|
||
_ u: U.Type,
|
||
_ v: V.Type,
|
||
_ w: W.Type
|
||
) -> AttributesSlice4<T, U, V, W> {
|
||
return AttributesSlice4<T, U, V, W>(runs: self)
|
||
}
|
||
}
|
||
|
||
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
|
||
extension AttributedString.Runs {
|
||
@preconcurrency
|
||
public struct AttributesSlice5<
|
||
T : AttributedStringKey,
|
||
U : AttributedStringKey,
|
||
V : AttributedStringKey,
|
||
W : AttributedStringKey,
|
||
X : AttributedStringKey
|
||
> : BidirectionalCollection, Sendable
|
||
where
|
||
T.Value : Sendable,
|
||
U.Value : Sendable,
|
||
V.Value : Sendable,
|
||
W.Value : Sendable,
|
||
X.Value : Sendable
|
||
{
|
||
public typealias Index = AttributedString.Index
|
||
|
||
// FIXME: Why no labels?
|
||
public typealias Element = (T.Value?, U.Value?, V.Value?, W.Value?, X.Value?, Range<AttributedString.Index>)
|
||
|
||
internal typealias Runs = AttributedString.Runs
|
||
|
||
let runs : Runs
|
||
let _names: [String]
|
||
let _constraints: [AttributeRunBoundaries]
|
||
|
||
init(runs: Runs) {
|
||
self.runs = runs
|
||
// FIXME: ☠️ Get these from a proper cache in runs._guts.
|
||
_names = [T.name, U.name, V.name, W.name]
|
||
_constraints = Array(
|
||
_contents: T.runBoundaries,
|
||
U.runBoundaries,
|
||
V.runBoundaries,
|
||
W.runBoundaries,
|
||
X.runBoundaries)
|
||
}
|
||
|
||
public struct Iterator: IteratorProtocol, Sendable {
|
||
public typealias Element = AttributesSlice5.Element
|
||
|
||
let _slice: AttributesSlice5
|
||
var _index: AttributedString.Index
|
||
|
||
internal init(_ slice: AttributesSlice5) {
|
||
self._slice = slice
|
||
self._index = slice.startIndex
|
||
}
|
||
|
||
public mutating func next() -> Element? {
|
||
if _index == _slice.endIndex {
|
||
return nil
|
||
}
|
||
let run = _slice.runs[_index]
|
||
let next = _slice.index(after: _index)
|
||
let range = _index ..< next
|
||
_index = next
|
||
return (
|
||
run._attributes[T.self],
|
||
run._attributes[U.self],
|
||
run._attributes[V.self],
|
||
run._attributes[W.self],
|
||
run._attributes[X.self],
|
||
range)
|
||
}
|
||
}
|
||
|
||
public func makeIterator() -> Iterator {
|
||
Iterator(self)
|
||
}
|
||
|
||
public var startIndex: Index {
|
||
Index(runs._strBounds.lowerBound)
|
||
}
|
||
|
||
public var endIndex: Index {
|
||
Index(runs._strBounds.upperBound)
|
||
}
|
||
|
||
public func index(before i: Index) -> Index {
|
||
runs._slicedRunBoundary(
|
||
before: i,
|
||
attributeNames: _names,
|
||
constraints: _constraints)
|
||
}
|
||
|
||
public func index(after i: Index) -> Index {
|
||
runs._slicedRunBoundary(
|
||
after: i,
|
||
attributeNames: _names,
|
||
constraints: _constraints)
|
||
}
|
||
|
||
public subscript(position: AttributedString.Index) -> Element {
|
||
let (start, runIndex) = runs._slicedRunBoundary(
|
||
roundingDown: position,
|
||
attributeNames: _names,
|
||
constraints: _constraints)
|
||
let end = self.index(after: position)
|
||
let attributes = runs._guts.runs[runIndex].attributes
|
||
return (
|
||
attributes[T.self],
|
||
attributes[U.self],
|
||
attributes[V.self],
|
||
attributes[W.self],
|
||
attributes[X.self],
|
||
start ..< end)
|
||
}
|
||
|
||
// FIXME: This is a collection with potentially unaligned indices that uses Slice as its
|
||
// SubSequence. Slicing the collection on such an index will produce spurious crashes.
|
||
// Add a custom implementation for the range subscript that forcibly rounds the given bounds
|
||
// down to the nearest valid indices.
|
||
}
|
||
|
||
public subscript <
|
||
T : AttributedStringKey,
|
||
U : AttributedStringKey,
|
||
V : AttributedStringKey,
|
||
W : AttributedStringKey,
|
||
X : AttributedStringKey
|
||
> (
|
||
_ t: KeyPath<AttributeDynamicLookup, T>,
|
||
_ u: KeyPath<AttributeDynamicLookup, U>,
|
||
_ v: KeyPath<AttributeDynamicLookup, V>,
|
||
_ w: KeyPath<AttributeDynamicLookup, W>,
|
||
_ x: KeyPath<AttributeDynamicLookup, X>
|
||
) -> AttributesSlice5<T, U, V, W, X> {
|
||
return AttributesSlice5<T, U, V, W, X>(runs: self)
|
||
}
|
||
|
||
public subscript <
|
||
T : AttributedStringKey,
|
||
U : AttributedStringKey,
|
||
V : AttributedStringKey,
|
||
W : AttributedStringKey,
|
||
X : AttributedStringKey
|
||
> (
|
||
_ t: T.Type,
|
||
_ u: U.Type,
|
||
_ v: V.Type,
|
||
_ w: W.Type,
|
||
_ x: X.Type
|
||
) -> AttributesSlice5<T, U, V, W, X> {
|
||
return AttributesSlice5<T, U, V, W, X>(runs: self)
|
||
}
|
||
}
|
||
|
||
#if FOUNDATION_FRAMEWORK
|
||
|
||
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
|
||
extension AttributedString.Runs {
|
||
@_spi(AttributedString)
|
||
public struct NSAttributesSlice : BidirectionalCollection, Sendable {
|
||
public typealias Index = AttributedString.Index
|
||
|
||
// FIXME: Why no labels?
|
||
public typealias Element = (AttributeContainer, Range<AttributedString.Index>)
|
||
|
||
internal typealias Runs = AttributedString.Runs
|
||
|
||
internal let _runs: Runs
|
||
private let _names: [String]
|
||
|
||
internal init(runs: Runs, names: [String]) {
|
||
self._runs = runs
|
||
self._names = names
|
||
}
|
||
|
||
public struct Iterator: IteratorProtocol, Sendable {
|
||
// Note: This is basically equivalent to `IndexingIterator`.
|
||
|
||
public typealias Element = NSAttributesSlice.Element
|
||
|
||
let _slice: NSAttributesSlice
|
||
var _index: AttributedString.Index
|
||
|
||
internal init(_ slice: NSAttributesSlice) {
|
||
self._slice = slice
|
||
self._index = slice.startIndex
|
||
}
|
||
|
||
public mutating func next() -> Element? {
|
||
if _index == _slice.endIndex {
|
||
return nil
|
||
}
|
||
let run = _slice._runs[_index]
|
||
let next = _slice.index(after: _index)
|
||
let range = _index ..< next
|
||
_index = next
|
||
return (_slice.buildContainer(from: run._attributes), range)
|
||
}
|
||
}
|
||
|
||
public func makeIterator() -> Iterator {
|
||
Iterator(self)
|
||
}
|
||
|
||
public var startIndex: Index {
|
||
Index(_runs._strBounds.lowerBound)
|
||
}
|
||
|
||
public var endIndex: Index {
|
||
Index(_runs._strBounds.upperBound)
|
||
}
|
||
|
||
public func index(before i: Index) -> Index {
|
||
_runs._slicedRunBoundary(
|
||
before: i,
|
||
attributeNames: _names,
|
||
constraints: [])
|
||
}
|
||
|
||
public func index(after i: Index) -> Index {
|
||
_runs._slicedRunBoundary(
|
||
after: i,
|
||
attributeNames: _names,
|
||
constraints: [])
|
||
}
|
||
|
||
public subscript(position: AttributedString.Index) -> Element {
|
||
let (start, runIndex) = _runs._slicedRunBoundary(
|
||
roundingDown: position,
|
||
attributeNames: _names,
|
||
constraints: [])
|
||
let end = self.index(after: position)
|
||
let attributes = _runs._guts.runs[runIndex].attributes
|
||
return (buildContainer(from: attributes), start ..< end)
|
||
}
|
||
|
||
// FIXME: This is a collection with potentially unaligned indices that uses Slice as its
|
||
// SubSequence. Slicing the collection on such an index will produce spurious crashes.
|
||
// Add a custom implementation for the range subscript that forcibly rounds the given bounds
|
||
// down to the nearest valid indices.
|
||
|
||
private func buildContainer(from storage: AttributedString._AttributeStorage) -> AttributeContainer {
|
||
AttributeContainer(storage.filterWithoutInvalidatingDependents { _names.contains($0.key) })
|
||
}
|
||
}
|
||
|
||
@_spi(AttributedString)
|
||
public subscript(nsAttributedStringKeys keys: NSAttributedString.Key...) -> NSAttributesSlice {
|
||
return NSAttributesSlice(runs: self, names: keys.map { $0.rawValue })
|
||
}
|
||
}
|
||
|
||
#endif // FOUNDATION_FRAMEWORK
|
||
|
||
extension RangeReplaceableCollection {
|
||
internal init(_contents item1: Element?) {
|
||
self.init()
|
||
if let item1 { self.append(item1) }
|
||
}
|
||
|
||
internal init(_contents item1: Element?, _ item2: Element?) {
|
||
self.init()
|
||
var c = 0
|
||
if item1 != nil { c &+= 1 }
|
||
if item2 != nil { c &+= 1 }
|
||
guard c > 0 else { return }
|
||
self.reserveCapacity(c)
|
||
if let item1 { self.append(item1) }
|
||
if let item2 { self.append(item2) }
|
||
}
|
||
|
||
internal init(_contents item1: Element?, _ item2: Element?, _ item3: Element?) {
|
||
self.init()
|
||
var c = 0
|
||
if item1 != nil { c &+= 1 }
|
||
if item2 != nil { c &+= 1 }
|
||
if item3 != nil { c &+= 1 }
|
||
guard c > 0 else { return }
|
||
self.reserveCapacity(c)
|
||
if let item1 { self.append(item1) }
|
||
if let item2 { self.append(item2) }
|
||
if let item3 { self.append(item3) }
|
||
}
|
||
|
||
internal init(
|
||
_contents item1: Element?, _ item2: Element?, _ item3: Element?, _ item4: Element?
|
||
) {
|
||
self.init()
|
||
var c = 0
|
||
if item1 != nil { c &+= 1 }
|
||
if item2 != nil { c &+= 1 }
|
||
if item3 != nil { c &+= 1 }
|
||
if item4 != nil { c &+= 1 }
|
||
guard c > 0 else { return }
|
||
self.reserveCapacity(c)
|
||
if let item1 { self.append(item1) }
|
||
if let item2 { self.append(item2) }
|
||
if let item3 { self.append(item3) }
|
||
if let item4 { self.append(item4) }
|
||
}
|
||
|
||
internal init(
|
||
_contents item1: Element?,
|
||
_ item2: Element?,
|
||
_ item3: Element?,
|
||
_ item4: Element?,
|
||
_ item5: Element?
|
||
) {
|
||
self.init()
|
||
var c = 0
|
||
if item1 != nil { c &+= 1 }
|
||
if item2 != nil { c &+= 1 }
|
||
if item3 != nil { c &+= 1 }
|
||
if item4 != nil { c &+= 1 }
|
||
if item5 != nil { c &+= 1 }
|
||
guard c > 0 else { return }
|
||
self.reserveCapacity(c)
|
||
if let item1 { self.append(item1) }
|
||
if let item2 { self.append(item2) }
|
||
if let item3 { self.append(item3) }
|
||
if let item4 { self.append(item4) }
|
||
if let item5 { self.append(item5) }
|
||
}
|
||
}
|
||
|