Skip to content

Commit

Permalink
Store attributes in TextKit 1 text storage (#28)
Browse files Browse the repository at this point in the history
* expand example text and highlighting

* store attributes in TextKit 1 text storage

* set default textView font

* make executionMode injectable in TextViewHighlighter convenience init

* fix boldness of boldFont

* fix tests
  • Loading branch information
DivineDominion authored Nov 18, 2023
1 parent 0959da6 commit a369b1c
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 17 deletions.
22 changes: 18 additions & 4 deletions Projects/NeonExample/ViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,20 @@ final class ViewController: NSViewController {

scrollView.documentView = textView

let provider: TextViewSystemInterface.AttributeProvider = { token in
guard token.name.hasPrefix("keyword") else { return [:] }
let regularFont = NSFont.monospacedSystemFont(ofSize: 16, weight: .regular)
let boldFont = NSFont.monospacedSystemFont(ofSize: 16, weight: .bold)
let italicFont = NSFont(descriptor: regularFont.fontDescriptor.withSymbolicTraits(.italic), size: 16) ?? regularFont

// Alternatively, set `textView.typingAttributes = [.font: regularFont, ...]`
// if you want to customize other default (fallback) attributes.
textView.font = regularFont

return [NSAttributedString.Key.foregroundColor: NSColor.red]
let provider: TextViewSystemInterface.AttributeProvider = { token in
return switch token.name {
case let keyword where keyword.hasPrefix("keyword"): [.foregroundColor: NSColor.red, .font: boldFont]
case "comment": [.foregroundColor: NSColor.green, .font: italicFont]
default: [.foregroundColor: NSColor.textColor, .font: regularFont]
}
}

let language = Language(language: tree_sitter_swift())
Expand Down Expand Up @@ -52,6 +62,10 @@ final class ViewController: NSViewController {
}

override func viewWillAppear() {
textView.string = "let value = \"hello world\"\nprint(value)"
textView.string = """
// Example Code!
let value = "hello world"
print(value)
"""
}
}
15 changes: 13 additions & 2 deletions Sources/Neon/TextViewHighlighter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,10 +80,21 @@ public final class TextViewHighlighter: NSObject {
treeSitterClient.invalidationHandler = { [weak self] in self?.handleInvalidation($0) }
}

public convenience init(textView: TextView, language: Language, highlightQuery: Query, attributeProvider: @escaping TextViewSystemInterface.AttributeProvider) throws {
public convenience init(
textView: TextView,
language: Language,
highlightQuery: Query,
executionMode: TreeSitterClient.ExecutionMode = .asynchronous(prefetch: true),
attributeProvider: @escaping TextViewSystemInterface.AttributeProvider
) throws {
let client = try TreeSitterClient(language: language, transformer: { _ in return .zero })

try self.init(textView: textView, client: client, highlightQuery: highlightQuery, attributeProvider: attributeProvider)
try self.init(
textView: textView,
client: client,
highlightQuery: highlightQuery,
executionMode: executionMode,
attributeProvider: attributeProvider)
}

@objc private func visibleContentChanged(_ notification: NSNotification) {
Expand Down
13 changes: 4 additions & 9 deletions Sources/Neon/TextViewSystemInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -61,15 +61,10 @@ extension TextViewSystemInterface: TextSystemInterface {
return
}

// next, textkit 1
#if os(macOS)
if let layoutManager = layoutManager {
layoutManager.setTemporaryAttributes(attrs, forCharacterRange: clampedRange)
return
}
#endif

// finally, fall back to applying color directly to the storage
// For TextKit 1: Fall back to applying styles directly to the storage.
// `NSLayoutManager.setTemporaryAttributes` is limited to attributes
// that don't affect layout, like color. So it ignores fonts,
// making font weight changes or italicizing text impossible.
assert(textStorage != nil, "TextView's NSTextStorage cannot be nil")
textStorage?.setAttributes(attrs, range: clampedRange)
}
Expand Down
4 changes: 2 additions & 2 deletions Tests/NeonTests/TextViewSystemInterfaceTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ final class TextViewSystemInterfaceTests: XCTestCase {
var effectiveRange: NSRange = .zero

#if os(macOS)
let attrs = textView.layoutManager?.temporaryAttributes(atCharacterIndex: 0, effectiveRange: &effectiveRange) ?? [:]
let allAttrs = textView.textStorage?.attributes(at: 0, effectiveRange: &effectiveRange) ?? [:]
#else
let allAttrs = textView.textStorage.attributes(at: 0, effectiveRange: &effectiveRange)
#endif

// we have to remove some attributes, like font, that are normal for the textStorage.
let attrs = allAttrs.filter({ $0.key == .foregroundColor })
#endif

XCTAssertEqual(attrs.count, 1)
XCTAssertEqual(attrs[.foregroundColor] as? PlatformColor, PlatformColor.red)
Expand Down

0 comments on commit a369b1c

Please sign in to comment.