223 lines
7.9 KiB
Swift

//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2020 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
//
//===----------------------------------------------------------------------===//
// MARK: AttributedStringKey API
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
extension AttributedString {
public enum AttributeRunBoundaries : Hashable, Sendable {
case paragraph
// FIXME: This is semantically wrong. We do not ever want to constrain attributes on
// characters (i.e., grapheme clusters) -- they are way too vague, and way
// too eager to accidentally merge with neighboring string data. (And they're also way
// too slow for this use case.)
//
// The entire point of this feature is to anchor attributes that describe attachments like
// custom views that should be embedded in the text. We do not _ever_ want the anchor text
// to accidentally compose with a subsequent combining character, losing the attachment.
//
// This needs to be deprecated and replaced by `case unicodeScalar(UnicodeScalar)`.
//
// The current implementation already works like that -- it ignores all but the first scalar
// of the specified `Character`, and does not engage in normalization or grapheme breaking.
case character(Character)
}
}
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
extension AttributedString.AttributeRunBoundaries {
var _isScalarConstrained: Bool {
if case .character = self { return true }
return false
}
var _constrainedScalar: Unicode.Scalar? {
switch self {
case .character(let char): return char.unicodeScalars.first
default: return nil
}
}
}
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
extension AttributedString {
public struct AttributeInvalidationCondition : Hashable, Sendable {
private enum _Storage : Hashable {
case textChanged
case attributeChanged(String)
}
private let storage: _Storage
private init(_ storage: _Storage) {
self.storage = storage
}
var isAttribute: Bool {
guard case .attributeChanged = storage else { return false }
return true
}
var attributeKey: String? {
switch storage {
case .textChanged:
return nil
case .attributeChanged(let string):
return string
}
}
public static let textChanged = Self(.textChanged)
public static func attributeChanged<T: AttributedStringKey>(_ key: T.Type) -> Self {
Self(.attributeChanged(key.name))
}
public static func attributeChanged<T: AttributedStringKey>(_ key: KeyPath<AttributeDynamicLookup, T>) -> Self {
Self(.attributeChanged(T.name))
}
static func attributeChanged(_ name: String) -> Self {
Self(.attributeChanged(name))
}
}
}
// Developers define new attributes by implementing AttributeKey.
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
public protocol AttributedStringKey {
associatedtype Value : Hashable
static var name : String { get }
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
static var runBoundaries : AttributedString.AttributeRunBoundaries? { get }
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
static var inheritedByAddedText : Bool { get }
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
static var invalidationConditions : Set<AttributedString.AttributeInvalidationCondition>? { get }
}
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
extension AttributedStringKey {
public var description: String { Self.name }
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
public static var runBoundaries : AttributedString.AttributeRunBoundaries? { nil }
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
public static var inheritedByAddedText : Bool { true }
@available(macOS 13, iOS 16, tvOS 16, watchOS 9, *)
public static var invalidationConditions : Set<AttributedString.AttributeInvalidationCondition>? { nil }
}
// MARK: Attribute Scopes
@dynamicMemberLookup @frozen
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
public enum AttributeDynamicLookup {
public subscript<T: AttributedStringKey>(_: T.Type) -> T {
get { fatalError("Called outside of a dynamicMemberLookup subscript overload") }
}
}
@available(macOS, unavailable, introduced: 12.0)
@available(iOS, unavailable, introduced: 15.0)
@available(tvOS, unavailable, introduced: 15.0)
@available(watchOS, unavailable, introduced: 8.0)
@available(*, unavailable)
extension AttributeDynamicLookup : Sendable {}
@dynamicMemberLookup
@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
public struct ScopedAttributeContainer<S: AttributeScope> : Sendable {
internal var storage : AttributedString._AttributeStorage
// Record the most recently deleted key for use in AttributedString mutation subscripts that use _modify
// Note: if ScopedAttributeContainer ever adds a mutating function that can mutate multiple attributes, this will need to record multiple removed keys
internal var removedKey : String?
@preconcurrency
public subscript<T: AttributedStringKey>(dynamicMember keyPath: KeyPath<S, T>) -> T.Value? where T.Value : Sendable {
get { storage[T.self] }
set {
storage[T.self] = newValue
if newValue == nil {
removedKey = T.name
}
}
}
internal init(_ storage : AttributedString._AttributeStorage = .init()) {
self.storage = storage
}
#if FOUNDATION_FRAMEWORK
// TODO: Support scope-specific equality/attributes in FoundationPreview
internal func equals(_ other: Self) -> Bool {
for (name, _) in S.attributeKeyTypes() {
if self.storage[name] != other.storage[name] {
return false
}
}
return true
}
internal var attributes : AttributeContainer {
var contents = AttributedString._AttributeStorage()
for (name, _) in S.attributeKeyTypes() {
contents[name] = self.storage[name]
}
return AttributeContainer(contents)
}
#endif // FOUNDATION_FRAMEWORK
}
// MARK: Internals
#if FOUNDATION_FRAMEWORK
internal extension AttributedStringKey {
static func _convertToObjectiveCValue(_ value: Value) throws -> AnyObject {
if let convertibleType = Self.self as? any ObjectiveCConvertibleAttributedStringKey.Type {
func project<K: ObjectiveCConvertibleAttributedStringKey>(_: K.Type) throws -> AnyObject {
try K.objectiveCValue(for: value as! K.Value)
}
return try project(convertibleType)
} else {
return value as AnyObject
}
}
static func _convertFromObjectiveCValue(_ value: AnyObject) throws -> Value {
if let convertibleType = Self.self as? any ObjectiveCConvertibleAttributedStringKey.Type {
func project<K: ObjectiveCConvertibleAttributedStringKey>(_: K.Type) throws -> Value {
guard let objcValue = value as? K.ObjectiveCValue else {
throw CocoaError(.coderInvalidValue)
}
return try K.value(for: objcValue) as! Value
}
return try project(convertibleType)
} else if let trueValue = value as? Value {
return trueValue
} else {
throw CocoaError(.coderInvalidValue)
}
}
}
#endif // FOUNDATION_FRAMEWORK