//===----------------------------------------------------------------------===// // // 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 @dynamicMemberLookup @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) public struct AttributedString : Sendable { internal var _guts: Guts internal init(_ guts: Guts) { _guts = guts } } @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension AttributedString { internal static let currentIdentity = LockedState(initialState: 0) internal static var _nextModifyIdentity : Int { currentIdentity.withLock { identity in identity += 1 return identity } } } // MARK: Initialization @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension AttributedString { public init() { self._guts = Guts() } internal init(_ s: some AttributedStringProtocol) { if let s = _specializingCast(s, to: AttributedString.self) { self = s } else if let s = _specializingCast(s, to: AttributedSubstring.self) { self = AttributedString(s) } else { // !!!: We don't expect or want this to happen. let substring = AttributedSubstring(s.__guts, in: s._stringBounds) self = AttributedString(substring) } } internal init(_ string: BigString, attributes: _AttributeStorage) { guard !string.isEmpty else { self.init() return } var runs = _InternalRuns.Storage() runs.append(_InternalRun(length: string.utf8.count, attributes: attributes)) self.init(Guts(string: string, runs: _InternalRuns(runs))) // Only scalar-bound attributes can be incorrect if only one run exists if attributes.containsScalarConstraint { _guts.fixScalarConstrainedAttributes(in: string.startIndex ..< string.endIndex) } } /// Creates a new attributed string with the given `String` value associated with the given /// attributes. public init(_ string: String, attributes: AttributeContainer = .init()) { self.init(BigString(string), attributes: attributes.storage) } /// Creates a new attributed string with the given `Substring` value associated with the given /// attributes. public init(_ substring: Substring, attributes: AttributeContainer = .init()) { self.init(BigString(substring), attributes: attributes.storage) } public init( _ elements: S, attributes: AttributeContainer = .init() ) where S.Element == Character { let str = Self._bstring(from: elements) self.init(str, attributes: attributes.storage) } public init(_ substring: AttributedSubstring) { let str = BigString(substring._unicodeScalars) let runs = substring._guts.runs.extract(utf8Offsets: substring._range._utf8OffsetRange) assert(str.utf8.count == runs.utf8Count) _guts = Guts(string: str, runs: runs) // FIXME: Extracting a slice should invalidate .textChanged attribute runs on the edges // (Compare with the `copy(in:)` call in the scope filtering initializer below -- that // one does too much, this one does too little.) } #if FOUNDATION_FRAMEWORK // TODO: Support scope-specific initialization in FoundationPreview public init(_ other: T, including scope: KeyPath) { self.init(other, including: S.self) } public init(_ other: T, including scope: S.Type) { // FIXME: This `copy(in:)` call does too much work, potentially unexpectedly removing attributes. self.init(other.__guts.copy(in: other._stringBounds)) let attributeTypes = scope.attributeKeyTypes() _guts.runs(in: _guts.utf8OffsetRange).updateEach { attributes, utf8Range, modified in modified = false for key in attributes.keys { if !attributeTypes.keys.contains(key) { attributes[key] = nil modified = true } } } } #endif // FOUNDATION_FRAMEWORK } @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension AttributedString { internal static func _bstring>(from elements: S) -> BigString { if let elements = _specializingCast(elements, to: String.self) { return BigString(elements) } if let elements = _specializingCast(elements, to: Substring.self) { return BigString(elements) } if let elements = _specializingCast(elements, to: AttributedString.CharacterView.self) { return BigString(elements._characters) } if let elements = _specializingCast( elements, to: Slice.self ) { return BigString(elements._characters) } return BigString(elements) } } @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension AttributedString { // Equatable public static func == (lhs: Self, rhs: Self) -> Bool { AttributedString.Guts.characterwiseIsEqual(lhs._guts, to: rhs._guts) } } // Note: The Hashable implementation is inherited from AttributedStringProtocol. @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension AttributedString: ExpressibleByStringLiteral { public init(stringLiteral value: String) { self.init(value) } } @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension AttributedString { // AttributedStringAttributeMutation public mutating func setAttributes(_ attributes: AttributeContainer) { ensureUniqueReference() _guts.setAttributes(attributes.storage, in: _stringBounds) } public mutating func mergeAttributes(_ attributes: AttributeContainer, mergePolicy: AttributeMergePolicy = .keepNew) { ensureUniqueReference() _guts.mergeAttributes(attributes, in: _stringBounds, mergePolicy: mergePolicy) } public mutating func replaceAttributes(_ attributes: AttributeContainer, with others: AttributeContainer) { guard attributes != others else { return } ensureUniqueReference() let hasConstrainedAttributes = attributes._hasConstrainedAttributes || others._hasConstrainedAttributes var fixupRanges: [Range] = [] _guts.runs(in: _guts.utf8OffsetRange).updateEach( when: { $0.matches(attributes.storage) }, with: { runAttributes, utf8Range in for key in attributes.storage.keys { runAttributes[key] = nil } runAttributes.mergeIn(others) if hasConstrainedAttributes { fixupRanges._extend(with: utf8Range) } }) for range in fixupRanges { // FIXME: Collect boundary constraints. _guts.enforceAttributeConstraintsAfterMutation(in: range, type: .attributes) } } } @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension AttributedString: AttributedStringProtocol { public struct Index : Comparable, Sendable { internal var _value: BigString.Index internal init(_ value: BigString.Index) { self._value = value } public static func == (left: Self, right: Self) -> Bool { left._value == right._value } public static func < (left: Self, right: Self) -> Bool { left._value < right._value } } public var startIndex : Index { Index(_guts.string.startIndex) } public var endIndex : Index { Index(_guts.string.endIndex) } @preconcurrency public subscript(_: K.Type) -> K.Value? where K.Value : Sendable { get { _guts.getUniformValue(in: _stringBounds, key: K.self)?.rawValue(as: K.self) } set { ensureUniqueReference() if let v = newValue { _guts.setAttributeValue(v, forKey: K.self, in: _stringBounds) } else { _guts.removeAttributeValue(forKey: K.self, in: _stringBounds) } } } @preconcurrency public subscript( dynamicMember keyPath: KeyPath ) -> K.Value? where K.Value: Sendable { get { self[K.self] } set { self[K.self] = newValue } } public subscript( dynamicMember keyPath: KeyPath ) -> ScopedAttributeContainer { get { return ScopedAttributeContainer(_guts.getUniformValues(in: _stringBounds)) } _modify { ensureUniqueReference() var container = ScopedAttributeContainer() defer { if let removedKey = container.removedKey { _guts.removeAttributeValue(forKey: removedKey, in: _stringBounds) } else { _guts.mergeAttributes(AttributeContainer(container.storage), in: _stringBounds) } } yield &container } } } // MARK: Mutating operations @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension AttributedString { internal mutating func ensureUniqueReference() { if !isKnownUniquelyReferenced(&_guts) { _guts = _guts.copy() } } public mutating func append(_ s: some AttributedStringProtocol) { replaceSubrange(endIndex ..< endIndex, with: s) } public mutating func insert(_ s: some AttributedStringProtocol, at index: AttributedString.Index) { replaceSubrange(index ..< index, with: s) } public mutating func removeSubrange(_ range: some RangeExpression) { replaceSubrange(range, with: AttributedString()) } public mutating func replaceSubrange(_ range: some RangeExpression, with s: some AttributedStringProtocol) { ensureUniqueReference() // Note: slicing generally allows sub-Character ranges, but we need to resolve range // expressions using the characters view, to remain consistent with the stdlib. let subrange = range.relative(to: characters)._bstringRange _guts.replaceSubrange(subrange, with: s) } } // MARK: Concatenation operators @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension AttributedString { public static func +(lhs: AttributedString, rhs: some AttributedStringProtocol) -> AttributedString { var result = lhs result.append(rhs) return result } public static func +=(lhs: inout AttributedString, rhs: some AttributedStringProtocol) { lhs.append(rhs) } public static func + (lhs: AttributedString, rhs: AttributedString) -> AttributedString { var result = lhs result.append(rhs) return result } public static func += (lhs: inout Self, rhs: AttributedString) { lhs.append(rhs) } } // MARK: Substring access @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension AttributedString { public subscript(bounds: some RangeExpression) -> AttributedSubstring { get { // Note: slicing generally allows sub-Character ranges, but we need to resolve range // expressions using the characters view, to remain consistent with the stdlib. let bounds = bounds.relative(to: characters) return AttributedSubstring(_guts, in: bounds._bstringRange) } _modify { ensureUniqueReference() // Note: slicing generally allows sub-Character ranges, but we need to resolve range // expressions using the characters view, to remain consistent with the stdlib. let bounds = bounds.relative(to: characters) var substr = AttributedSubstring(_guts, in: bounds._bstringRange) let ident = Self._nextModifyIdentity substr._identity = ident _guts = Guts() // Dummy guts to allow in-place mutations defer { if substr._identity != ident { fatalError("Mutating an AttributedSubstring by replacing it with another from a different source is unsupported") } _guts = substr._guts } yield &substr } set { // Note: slicing generally allows sub-Character ranges, but we need to resolve range // expressions using the characters view, to remain consistent with the stdlib. let bounds = bounds.relative(to: characters) // FIXME: Why is this allowed if _modify traps on replacement? self.replaceSubrange(bounds, with: newValue) } } } @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension Range where Bound == AttributedString.Index { internal var _bstringRange: Range { Range(uncheckedBounds: (lowerBound._value, upperBound._value)) } internal var _utf8OffsetRange: Range { Range(uncheckedBounds: (lowerBound._value.utf8Offset, upperBound._value.utf8Offset)) } } @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) extension Range where Bound == BigString.Index { internal var _utf8OffsetRange: Range { Range(uncheckedBounds: (lowerBound.utf8Offset, upperBound.utf8Offset)) } }