Skip to content

Commit 618ba0e

Browse files
committed
Highlight ignored Indexes
Tracks indexes that were not given an explicit color and highlights them as normal text.
1 parent e3c93a2 commit 618ba0e

File tree

2 files changed

+68
-21
lines changed

2 files changed

+68
-21
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
//
2+
// IndexSet+NSRange.swift
3+
//
4+
//
5+
// Created by Khan Winter on 1/12/23.
6+
//
7+
8+
import Foundation
9+
10+
extension NSRange {
11+
/// Convenience getter for safely creating a `Range<Int>` from an `NSRange`
12+
var intRange: Range<Int> {
13+
self.location..<NSMaxRange(self)
14+
}
15+
}
16+
17+
/// Helpers for working with `NSRange`s and `IndexSet`s.
18+
extension IndexSet {
19+
/// Initializes the index set with a range of integers
20+
init(integersIn range: NSRange) {
21+
self.init(integersIn: range.intRange)
22+
}
23+
24+
/// Remove all the integers in the `NSRange`
25+
mutating func remove(integersIn range: NSRange) {
26+
self.remove(integersIn: range.intRange)
27+
}
28+
29+
/// Insert all the integers in the `NSRange`
30+
mutating func insert(integersIn range: NSRange) {
31+
self.insert(integersIn: range.intRange)
32+
}
33+
34+
/// Returns true if self contains all of the integers in range.
35+
func contains(integersIn range: NSRange) -> Bool {
36+
return self.contains(integersIn: range.intRange)
37+
}
38+
}

Sources/CodeEditTextView/Highlighting/Highlighter.swift

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

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

5148
// MARK: - UI
@@ -107,7 +104,7 @@ class Highlighter: NSObject {
107104
if !(treeSitterClient?.hasSetText ?? true) {
108105
treeSitterClient?.setText(text: textView.string)
109106
}
110-
invalidate(range: entireTextRange)
107+
invalidate(range: NSRange(entireTextRange))
111108
}
112109

113110
/// Sets the language and causes a re-highlight of the entire text.
@@ -129,12 +126,6 @@ private extension Highlighter {
129126
/// Invalidates a given range and adds it to the queue to be highlighted.
130127
/// - Parameter range: The range to invalidate.
131128
func invalidate(range: NSRange) {
132-
invalidate(range: Range(range)!)
133-
}
134-
135-
/// Invalidates a given range and adds it to the queue to be highlighted.
136-
/// - Parameter range: The range to invalidate.
137-
func invalidate(range: Range<Int>) {
138129
let set = IndexSet(integersIn: range)
139130

140131
if set.isEmpty {
@@ -161,31 +152,35 @@ private extension Highlighter {
161152

162153
/// Highlights the given range
163154
/// - Parameter range: The range to request highlights for.
164-
func highlight(range nsRange: NSRange) {
165-
let range = Range(nsRange)!
166-
pendingSet.insert(integersIn: range)
155+
func highlight(range rangeToHighlight: NSRange) {
156+
pendingSet.insert(integersIn: rangeToHighlight)
167157

168-
treeSitterClient?.queryColorsFor(range: nsRange) { [weak self] highlightRanges in
158+
treeSitterClient?.queryColorsFor(range: rangeToHighlight) { [weak self] highlightRanges in
169159
guard let attributeProvider = self?.attributeProvider,
170160
let textView = self?.textView else { return }
171161

172162
// Mark these indices as not pending and valid
173-
self?.pendingSet.remove(integersIn: range)
174-
self?.validSet.formUnion(IndexSet(integersIn: range))
163+
self?.pendingSet.remove(integersIn: rangeToHighlight)
164+
self?.validSet.formUnion(IndexSet(integersIn: rangeToHighlight))
175165

176166
// If this range does not exist in the visible set, we can exit.
177-
if !(self?.visibleSet ?? .init()).contains(integersIn: range) {
167+
if !(self?.visibleSet ?? .init()).contains(integersIn: rangeToHighlight) {
178168
return
179169
}
180170

181171
// Try to create a text range for invalidating. If this fails we fail silently
182172
guard let textContentManager = textView.textLayoutManager.textContentManager,
183-
let textRange = NSTextRange(nsRange, provider: textContentManager) else {
173+
let textRange = NSTextRange(rangeToHighlight, provider: textContentManager) else {
184174
return
185175
}
186176

187177
// Loop through each highlight and modify the textStorage accordingly.
188178
textView.textContentStorage.textStorage?.beginEditing()
179+
180+
// Create a set of indexes that were not highlighted.
181+
var ignoredIndexes = IndexSet(integersIn: rangeToHighlight)
182+
183+
// Apply all highlights that need color
189184
for highlight in highlightRanges {
190185
// Does not work:
191186
// textView.textLayoutManager.setRenderingAttributes(attributeProvider.attributesFor(highlight.capture),
@@ -196,7 +191,21 @@ private extension Highlighter {
196191
attributeProvider.attributesFor(highlight.capture),
197192
range: highlight.range
198193
)
194+
195+
// Remove highlighted indexes from the "ignored" indexes.
196+
ignoredIndexes.remove(integersIn: highlight.range)
197+
}
198+
199+
// For any indices left over, we need to apply normal attributes to them
200+
// This fixes the case where characters are changed to have a non-text color, and then are skipped when
201+
// they need to be changed back.
202+
for ignoredRange in ignoredIndexes.rangeView {
203+
textView.textContentStorage.textStorage?.setAttributes(
204+
attributeProvider.attributesFor(nil),
205+
range: NSRange(ignoredRange)
206+
)
199207
}
208+
200209
textView.textContentStorage.textStorage?.endEditing()
201210

202211
// After applying edits to the text storage we need to invalidate the layout
@@ -227,7 +236,7 @@ private extension Highlighter {
227236
private extension Highlighter {
228237
/// Updates the view to highlight newly visible text when the textview is scrolled or bounds change.
229238
@objc func visibleTextChanged(_ notification: Notification) {
230-
visibleSet = IndexSet(integersIn: Range(textView.visibleTextRange ?? NSRange())!)
239+
visibleSet = IndexSet(integersIn: textView.visibleTextRange ?? NSRange())
231240

232241
// Any indices that are both *not* valid and in the visible text range should be invalidated
233242
let newlyInvalidSet = visibleSet.subtracting(validSet)
@@ -263,7 +272,7 @@ extension Highlighter: NSTextStorageDelegate {
263272
treeSitterClient?.applyEdit(edit,
264273
text: textStorage.string) { [weak self] invalidatedIndexSet in
265274
let indexSet = invalidatedIndexSet
266-
.union(IndexSet(integersIn: Range(editedRange)!))
275+
.union(IndexSet(integersIn: editedRange))
267276
// Only invalidate indices that aren't visible.
268277
.intersection(self?.visibleSet ?? .init())
269278

0 commit comments

Comments
 (0)