diff --git a/README.md b/README.md index 6c8cf2d..8a6a8f2 100644 --- a/README.md +++ b/README.md @@ -25,8 +25,9 @@ targets: [ ## BaseTextView -This is an `NSTextView` subclass that aims for an absolute minimal amount of changes. Things are allowed only if they are required for correct functionality. It is intended to be a drop-in replacement for `NSTextView`, and should maintain compatibilty with existing subclasses. Behaviors are appropriate for all types of text. +This is an TextKit 2-only `NSTextView` subclass that aims for an absolute minimal amount of changes. Things are allowed only if they are required for correct functionality. It is intended to be a drop-in replacement for `NSTextView`, and should maintain compatibilty with existing subclasses. Behaviors are appropriate for all types of text. +- Disables all support for TextKit 1 - Workaround for `scrollRangeToVisible` bug (FB13100459) - Minimum `textContainerInset` enforcement to address more `scrollRangeToVisible` bugs - Additional routing to `NSTextViewDelegate.textView(_:, doCommandBy:) -> Bool`: `paste`, `pasteAsRichText`, `pasteAsPlainText` diff --git a/Sources/BaseTextView/BaseTextView.swift b/Sources/BaseTextView/BaseTextView.swift index de04de2..143fa85 100644 --- a/Sources/BaseTextView/BaseTextView.swift +++ b/Sources/BaseTextView/BaseTextView.swift @@ -5,7 +5,9 @@ import OSLog // I guess this should be defined by AppKit, but isn't fileprivate let NSOldSelectedCharacterRanges = "NSOldSelectedCharacterRanges" -/// A minimal `NSTextView` subclass to support correct functionality. +/// A minimal `NSTextView` subclass to support correct functionality using TextKit 2. +/// +/// - Warning: TextKit 1 is **unsupported**. An attempt to access the `layoutManager` property will assert in debug. @available(macOS 12.0, *) open class BaseTextView: NSTextView { public typealias OnEvent = (_ event: NSEvent, _ action: () -> Void) -> Void @@ -23,7 +25,20 @@ open class BaseTextView: NSTextView { public var continuousSelectionNotifications: Bool = false public override init(frame frameRect: NSRect, textContainer container: NSTextContainer?) { - super.init(frame: frameRect, textContainer: container) + let effectiveContainer = container ?? NSTextContainer() + + if effectiveContainer.textLayoutManager == nil { + let textLayoutManager = NSTextLayoutManager() + + textLayoutManager.textContainer = effectiveContainer + + let storage = NSTextContentStorage() + + storage.addTextLayoutManager(textLayoutManager) + storage.primaryTextLayoutManager = textLayoutManager + } + + super.init(frame: frameRect, textContainer: effectiveContainer) self.textContainerInset = CGSize(width: 5.0, height: 5.0) } @@ -36,6 +51,12 @@ open class BaseTextView: NSTextView { public required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + + public override var layoutManager: NSLayoutManager? { + assertionFailure("TextKit 1 is not supported by this type") + + return nil + } } @available(macOS 12.0, *) diff --git a/Tests/BaseTextViewTests/BaseTextViewTests.swift b/Tests/BaseTextViewTests/BaseTextViewTests.swift index 513895a..9f67db6 100644 --- a/Tests/BaseTextViewTests/BaseTextViewTests.swift +++ b/Tests/BaseTextViewTests/BaseTextViewTests.swift @@ -11,6 +11,12 @@ final class MockTextViewDelegate: NSObject, NSTextViewDelegate { @MainActor final class BaseTextViewTests: XCTestCase { + func testUsesTextKit2ByDefault() { + let view = BaseTextView() + + XCTAssertNotNil(view.textLayoutManager) + } + func testStockMissingDoCommandBy() { let view = NSTextView() let delegate = MockTextViewDelegate()