Skip to content

Commit e3c93a2

Browse files
Remove NSLayoutManager (#112)
# Description This PR removes all references to `NSLayoutManager` and fixes syntax highlighting after the regression in #105. # Related Issues N/A # Checklist - [x] I read and understood the [contributing guide](https://github.com/CodeEditApp/CodeEdit/blob/main/CONTRIBUTING.md) as well as the [code of conduct](https://github.com/CodeEditApp/CodeEdit/blob/main/CODE_OF_CONDUCT.md) - [x] My changes generate no new warnings - [x] My code builds and runs on my machine - [x] I documented my code - [x] Review requested
2 parents cc6d9d5 + 974aa05 commit e3c93a2

File tree

5 files changed

+53
-15
lines changed

5 files changed

+53
-15
lines changed

Sources/CodeEditTextView/CodeEditTextView.swift

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,19 @@ public struct CodeEditTextView: NSViewControllerRepresentable {
7373

7474
public func updateNSViewController(_ controller: NSViewControllerType, context: Context) {
7575
controller.font = font
76-
controller.language = language
77-
controller.theme = theme
7876
controller.tabWidth = tabWidth
7977
controller.wrapLines = wrapLines
8078
controller.lineHeightMultiple = lineHeight
8179
controller.editorOverscroll = editorOverscroll
80+
81+
// Updating the language and theme needlessly can cause highlights to be re-calculated.
82+
if controller.language.id != language.id {
83+
controller.language = language
84+
}
85+
if controller.theme != theme {
86+
controller.theme = theme
87+
}
88+
8289
controller.reloadUI()
8390
return
8491
}

Sources/CodeEditTextView/Extensions/NSRange+/NSRange+NSTextRange.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,17 @@ public extension NSTextRange {
2121

2222
self.init(location: start, end: end)
2323
}
24+
25+
/// Creates an `NSRange` using document information from the given provider.
26+
/// - Parameter provider: The `NSTextElementProvider` to use to convert this range into an `NSRange`
27+
/// - Returns: An `NSRange` if possible
28+
func nsRange(using provider: NSTextElementProvider) -> NSRange? {
29+
guard let location = provider.offset?(from: provider.documentRange.location, to: location) else {
30+
return nil
31+
}
32+
guard let length = provider.offset?(from: self.location, to: endLocation) else {
33+
return nil
34+
}
35+
return NSRange(location: location, length: length)
36+
}
2437
}

Sources/CodeEditTextView/Extensions/STTextView+/STTextView+VisibleRange.swift

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,32 @@
77

88
import Foundation
99
import STTextView
10+
import AppKit
1011

1112
extension STTextView {
12-
func textRange(for rect: CGRect) -> NSRange {
13-
let length = self.textContentStorage.textStorage?.length ?? 0
13+
/// A helper for calculating the visible range on the text view with some small vertical padding.
14+
var visibleTextRange: NSRange? {
15+
// This helper finds the visible rect of the text using the enclosing scroll view, then finds the nearest
16+
// `NSTextElement`s to those points and uses those elements to create the returned range.
1417

15-
guard let layoutManager = self.textContainer.layoutManager else {
16-
return NSRange(0..<length)
18+
// Get visible rect
19+
guard let bounds = enclosingScrollView?.documentVisibleRect else {
20+
return textLayoutManager.documentRange.nsRange(using: textContentStorage)
1721
}
18-
let container = self.textContainer
1922

20-
let glyphRange = layoutManager.glyphRange(forBoundingRect: rect, in: container)
23+
// Calculate min & max points w/ a small amount of padding vertically.
24+
let minPoint = CGPoint(x: bounds.minX,
25+
y: bounds.minY - 100)
26+
let maxPoint = CGPoint(x: bounds.maxX,
27+
y: bounds.maxY + 100)
2128

22-
return layoutManager.characterRange(forGlyphRange: glyphRange, actualGlyphRange: nil)
23-
}
29+
// Get text fragments for both the min and max points
30+
guard let start = textLayoutManager.textLayoutFragment(for: minPoint)?.rangeInElement.location,
31+
let end = textLayoutManager.textLayoutFragment(for: maxPoint)?.rangeInElement.endLocation else {
32+
return textLayoutManager.documentRange.nsRange(using: textContentStorage)
33+
}
2434

25-
var visibleTextRange: NSRange {
26-
return textRange(for: visibleRect)
35+
// Calculate a range and return it as an `NSRange`
36+
return NSTextRange(location: start, end: end)?.nsRange(using: textContentStorage)
2737
}
2838
}

Sources/CodeEditTextView/Highlighting/HighlightRange.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//
22
// HighlightRange.swift
3-
//
3+
//
44
//
55
// Created by Khan Winter on 9/14/22.
66
//

Sources/CodeEditTextView/Highlighting/Highlighter.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,10 @@ class Highlighter: NSObject {
4242

4343
/// The set of visible indexes in tht text view
4444
lazy private var visibleSet: IndexSet = {
45-
return IndexSet(integersIn: Range(textView.visibleTextRange)!)
45+
guard let range = textView.visibleTextRange else {
46+
return IndexSet()
47+
}
48+
return IndexSet(integersIn: Range(range)!)
4649
}()
4750

4851
// MARK: - UI
@@ -184,6 +187,11 @@ private extension Highlighter {
184187
// Loop through each highlight and modify the textStorage accordingly.
185188
textView.textContentStorage.textStorage?.beginEditing()
186189
for highlight in highlightRanges {
190+
// Does not work:
191+
// textView.textLayoutManager.setRenderingAttributes(attributeProvider.attributesFor(highlight.capture),
192+
// for: NSTextRange(highlight.range,
193+
// provider: textView.textContentStorage)!)
194+
// Temp solution (until Apple fixes above)
187195
textView.textContentStorage.textStorage?.setAttributes(
188196
attributeProvider.attributesFor(highlight.capture),
189197
range: highlight.range
@@ -219,7 +227,7 @@ private extension Highlighter {
219227
private extension Highlighter {
220228
/// Updates the view to highlight newly visible text when the textview is scrolled or bounds change.
221229
@objc func visibleTextChanged(_ notification: Notification) {
222-
visibleSet = IndexSet(integersIn: Range(textView.visibleTextRange)!)
230+
visibleSet = IndexSet(integersIn: Range(textView.visibleTextRange ?? NSRange())!)
223231

224232
// Any indices that are both *not* valid and in the visible text range should be invalidated
225233
let newlyInvalidSet = visibleSet.subtracting(validSet)

0 commit comments

Comments
 (0)