Skip to content

Commit

Permalink
Ensure TK2 only for BaseTextView
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmassicotte committed Oct 26, 2023
1 parent 77187a4 commit 7c6d0be
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 3 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
25 changes: 23 additions & 2 deletions Sources/BaseTextView/BaseTextView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
Expand All @@ -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, *)
Expand Down
6 changes: 6 additions & 0 deletions Tests/BaseTextViewTests/BaseTextViewTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down

0 comments on commit 7c6d0be

Please sign in to comment.