Skip to content

Commit

Permalink
Fix Ambiguous Highlights (#275)
Browse files Browse the repository at this point in the history
### Description

Fixes some bad syntax highlighting caused by overlapping captures
returned from tree-sitter. Previously the last value returned took
precedence, but with the new highlighting system that's not the case.
This filters highlights on duplicate ranges, and prioritizes the best
capture for any range so this is no longer dependent on the highlighting
system's semantics.

### Related Issues

- closes #276 

### 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] The issues this PR addresses are related to each other
- [x] My changes generate no new warnings
- [x] My code builds and runs on my machine
- [x] My changes are all related to the related issue above
- [x] I documented my code

### Screenshots

Disambiguated highlights:

![Screenshot 2024-11-17 at 2 50
28 PM](https://github.com/user-attachments/assets/e1bde8b3-81b5-481f-80d2-428798374c2b)

Ambiguous highlights:

![Screenshot 2024-11-17 at 2 52
04 PM](https://github.com/user-attachments/assets/90a89ed4-afb6-4d60-a7d7-39ba0a560ee6)

---------

Co-authored-by: Tom Ludwig <[email protected]>
  • Loading branch information
thecoolwinter and tom-ludwig authored Nov 19, 2024
1 parent f8ecd2e commit 696a7f1
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 10 deletions.
12 changes: 11 additions & 1 deletion Sources/CodeEditSourceEditor/Highlighting/HighlightRange.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
import Foundation

/// This struct represents a range to highlight, as well as the capture name for syntax coloring.
public struct HighlightRange: Sendable {
public struct HighlightRange: Hashable, Sendable {
public let range: NSRange
public let capture: CaptureName?
public let modifiers: CaptureModifierSet
Expand All @@ -19,3 +19,13 @@ public struct HighlightRange: Sendable {
self.modifiers = modifiers
}
}

extension HighlightRange: CustomDebugStringConvertible {
public var debugDescription: String {
if capture == nil && modifiers.isEmpty {
"\(range) (empty)"
} else {
"\(range) (\(capture?.stringValue ?? "No Capture")) \(modifiers.values.map({ $0.stringValue }))"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,35 @@ extension TreeSitterClient {
cursor: QueryCursor,
includedRange: NSRange
) -> [HighlightRange] {
guard let readCallback else { return [] }
var ranges: [NSRange: Int] = [:]
return cursor
.resolve(with: .init(textProvider: readCallback)) // Resolve our cursor against the query
.flatMap { $0.captures }
.compactMap {
// Sometimes `cursor.setRange` just doesn't work :( so we have to do a redundant check for a valid range
// in the included range
let intersectionRange = $0.range.intersection(includedRange) ?? .zero
// Check that the capture name is one CESE can parse. If not, ignore it completely.
if intersectionRange.length > 0, let captureName = CaptureName.fromString($0.name) {
return HighlightRange(range: intersectionRange, capture: captureName)
.reversed() // SwiftTreeSitter returns captures in the reverse order of what we need to filter with.
.compactMap { capture in
let range = capture.range
let index = capture.index

// Lower indexed captures are favored over higher, this is why we reverse it above
if let existingLevel = ranges[range], existingLevel <= index {
return nil
}

guard let captureName = CaptureName.fromString(capture.name) else {
return nil
}
return nil

// Update the filter level to the current index since it's lower and a 'valid' capture
ranges[range] = index

// Validate range and capture name
let intersectionRange = range.intersection(includedRange) ?? .zero
guard intersectionRange.length > 0 else {
return nil
}

return HighlightRange(range: intersectionRange, capture: captureName)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ public final class TreeSitterClient: HighlightProviding {
completion: @escaping @MainActor (Result<[HighlightRange], Error>) -> Void
) {
let operation = { [weak self] in
return self?.queryHighlightsForRange(range: range) ?? []
return (self?.queryHighlightsForRange(range: range) ?? []).sorted { $0.range.location < $1.range.location }
}

let longQuery = range.length > Constants.maxSyncQueryLength
Expand Down

0 comments on commit 696a7f1

Please sign in to comment.