Skip to content

Commit 4a1db47

Browse files
TreeSitterClient Highlight Responsiveness (#267)
1 parent 1b54e15 commit 4a1db47

File tree

2 files changed

+28
-33
lines changed

2 files changed

+28
-33
lines changed

Sources/CodeEditSourceEditor/Highlighting/Highlighter.swift

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -178,21 +178,9 @@ private extension Highlighter {
178178
func queryHighlights(for rangesToHighlight: [NSRange]) {
179179
guard let textView else { return }
180180

181-
if !Thread.isMainThread {
182-
DispatchQueue.main.async { [weak self] in
183-
for range in rangesToHighlight {
184-
self?.highlightProvider?.queryHighlightsFor(
185-
textView: textView,
186-
range: range
187-
) { [weak self] highlights in
188-
assert(Thread.isMainThread, "Highlighted ranges called on non-main thread.")
189-
self?.applyHighlightResult(highlights, rangeToHighlight: range)
190-
}
191-
}
192-
}
193-
} else {
181+
DispatchQueue.dispatchMainIfNot {
194182
for range in rangesToHighlight {
195-
highlightProvider?.queryHighlightsFor(textView: textView, range: range) { [weak self] highlights in
183+
self.highlightProvider?.queryHighlightsFor(textView: textView, range: range) { [weak self] highlights in
196184
assert(Thread.isMainThread, "Highlighted ranges called on non-main thread.")
197185
self?.applyHighlightResult(highlights, rangeToHighlight: range)
198186
}
@@ -222,7 +210,6 @@ private extension Highlighter {
222210
validSet.formUnion(IndexSet(integersIn: rangeToHighlight))
223211

224212
// Loop through each highlight and modify the textStorage accordingly.
225-
textView?.layoutManager.beginTransaction()
226213
textView?.textStorage.beginEditing()
227214

228215
// Create a set of indexes that were not highlighted.
@@ -249,7 +236,7 @@ private extension Highlighter {
249236
}
250237

251238
textView?.textStorage.endEditing()
252-
textView?.layoutManager.endTransaction()
239+
textView?.layoutManager.invalidateLayoutForRange(rangeToHighlight)
253240
}
254241
}
255242

Sources/CodeEditSourceEditor/TreeSitter/TreeSitterClient.swift

Lines changed: 25 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import OSLog
2222
/// can throw an ``TreeSitterClientExecutor/Error/syncUnavailable`` error if an asynchronous or synchronous call is
2323
/// already being made on the object. In those cases it is up to the caller to decide whether or not to retry
2424
/// asynchronously.
25-
///
25+
///
2626
/// The only exception to the above rule is the ``HighlightProviding`` conformance methods. The methods for that
2727
/// implementation may return synchronously or asynchronously depending on a variety of factors such as document
2828
/// length, edit length, highlight length and if the object is available for a synchronous call.
@@ -156,23 +156,26 @@ public final class TreeSitterClient: HighlightProviding {
156156
}
157157

158158
let operation = { [weak self] in
159-
let invalidatedRanges = self?.applyEdit(edit: edit) ?? IndexSet()
160-
DispatchQueue.dispatchMainIfNot { completion(.success(invalidatedRanges)) }
159+
return self?.applyEdit(edit: edit) ?? IndexSet()
161160
}
162161

163162
let longEdit = range.length > Constants.maxSyncEditLength
164163
let longDocument = textView.documentRange.length > Constants.maxSyncContentLength
165-
166-
if forceSyncOperation {
167-
executor.execSync(operation)
168-
return
164+
let execAsync = longEdit || longDocument
165+
166+
if !execAsync || forceSyncOperation {
167+
let result = executor.execSync(operation)
168+
if case .success(let invalidatedRanges) = result {
169+
DispatchQueue.dispatchMainIfNot { completion(.success(invalidatedRanges)) }
170+
return
171+
}
169172
}
170173

171-
if longEdit || longDocument || !executor.execSync(operation).isSuccess {
174+
if !forceSyncOperation {
172175
executor.cancelAll(below: .reset) // Cancel all edits, add it to the pending edit queue
173176
executor.execAsync(
174177
priority: .edit,
175-
operation: operation,
178+
operation: { completion(.success(operation())) },
176179
onCancel: { [weak self] in
177180
self?.pendingEdits.mutate { edits in
178181
edits.append(edit)
@@ -205,22 +208,27 @@ public final class TreeSitterClient: HighlightProviding {
205208
completion: @escaping @MainActor (Result<[HighlightRange], Error>) -> Void
206209
) {
207210
let operation = { [weak self] in
208-
let highlights = self?.queryHighlightsForRange(range: range)
209-
DispatchQueue.dispatchMainIfNot { completion(.success(highlights ?? [])) }
211+
return self?.queryHighlightsForRange(range: range) ?? []
210212
}
211213

212214
let longQuery = range.length > Constants.maxSyncQueryLength
213215
let longDocument = textView.documentRange.length > Constants.maxSyncContentLength
214-
215-
if forceSyncOperation {
216-
executor.execSync(operation)
217-
return
216+
let execAsync = longQuery || longDocument
217+
218+
if !execAsync || forceSyncOperation {
219+
let result = executor.execSync(operation)
220+
if case .success(let highlights) = result {
221+
DispatchQueue.dispatchMainIfNot { completion(.success(highlights)) }
222+
return
223+
}
218224
}
219225

220-
if longQuery || longDocument || !executor.execSync(operation).isSuccess {
226+
if execAsync && !forceSyncOperation {
221227
executor.execAsync(
222228
priority: .access,
223-
operation: operation,
229+
operation: {
230+
DispatchQueue.dispatchMainIfNot { completion(.success(operation())) }
231+
},
224232
onCancel: {
225233
DispatchQueue.dispatchMainIfNot {
226234
completion(.failure(HighlightProvidingError.operationCancelled))

0 commit comments

Comments
 (0)