Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 8 additions & 8 deletions Sources/CodeEditSourceEditor/Highlighting/Highlighter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import OSLog
///
/// This class manages multiple objects that help perform this task:
/// - ``StyledRangeContainer``
/// - ``StyledRangeStore``
/// - ``RangeStore``
/// - ``VisibleRangeProvider``
/// - ``HighlightProviderState``
///
Expand All @@ -34,12 +34,12 @@ import OSLog
/// |
/// | Queries coalesced styles
/// v
/// +-------------------------------+ +-----------------------------+
/// | StyledRangeContainer | ------> | StyledRangeStore[] |
/// | | | | Stores styles for one provider
/// | - manages combined ranges | | - stores raw ranges & |
/// | - layers highlight styles | | captures |
/// | + getAttributesForRange() | +-----------------------------+
/// +-------------------------------+ +-------------------------+
/// | StyledRangeContainer | ------> | RangeStore[] |
/// | | | | Stores styles for one provider
/// | - manages combined ranges | | - stores raw ranges & |
/// | - layers highlight styles | | captures |
/// | + getAttributesForRange() | +-------------------------+
/// +-------------------------------+
/// ^
/// | Sends highlighted runs
Expand Down Expand Up @@ -276,7 +276,7 @@ extension Highlighter: StyledRangeContainerDelegate {
guard let range = NSRange(location: offset, length: run.length).intersection(range) else {
continue
}
storage?.setAttributes(attributeProvider.attributesFor(run.capture), range: range)
storage?.setAttributes(attributeProvider.attributesFor(run.value?.capture), range: range)
offset += range.length
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,34 @@ protocol StyledRangeContainerDelegate: AnyObject {
/// See ``runsIn(range:)`` for more details on how conflicting highlights are handled.
@MainActor
class StyledRangeContainer {
var _storage: [ProviderID: StyledRangeStore] = [:]
struct StyleElement: RangeStoreElement, CustomDebugStringConvertible {
var capture: CaptureName?
var modifiers: CaptureModifierSet

var isEmpty: Bool {
capture == nil && modifiers.isEmpty
}

func combineLowerPriority(_ other: StyleElement?) -> StyleElement {
StyleElement(
capture: self.capture ?? other?.capture,
modifiers: modifiers.union(other?.modifiers ?? [])
)
}

func combineHigherPriority(_ other: StyleElement?) -> StyleElement {
StyleElement(
capture: other?.capture ?? self.capture,
modifiers: modifiers.union(other?.modifiers ?? [])
)
}

var debugDescription: String {
"\(capture?.stringValue ?? "(empty)"), \(modifiers)"
}
}

var _storage: [ProviderID: RangeStore<StyleElement>] = [:]
weak var delegate: StyledRangeContainerDelegate?

/// Initialize the container with a list of provider identifiers. Each provider is given an id, they should be
Expand All @@ -28,13 +55,13 @@ class StyledRangeContainer {
/// - providers: An array of identifiers given to providers.
init(documentLength: Int, providers: [ProviderID]) {
for provider in providers {
_storage[provider] = StyledRangeStore(documentLength: documentLength)
_storage[provider] = RangeStore<StyleElement>(documentLength: documentLength)
}
}

func addProvider(_ id: ProviderID, documentLength: Int) {
assert(!_storage.keys.contains(id), "Provider already exists")
_storage[id] = StyledRangeStore(documentLength: documentLength)
_storage[id] = RangeStore<StyleElement>(documentLength: documentLength)
}

func removeProvider(_ id: ProviderID) {
Expand All @@ -55,10 +82,18 @@ class StyledRangeContainer {
///
/// - Parameter range: The range to query.
/// - Returns: An array of continuous styled runs.
func runsIn(range: NSRange) -> [StyledRangeStoreRun] {
func runsIn(range: NSRange) -> [RangeStoreRun<StyleElement>] {
func combineLowerPriority(_ lhs: inout RangeStoreRun<StyleElement>, _ rhs: RangeStoreRun<StyleElement>) {
lhs.value = lhs.value?.combineLowerPriority(rhs.value) ?? rhs.value
}

func combineHigherPriority(_ lhs: inout RangeStoreRun<StyleElement>, _ rhs: RangeStoreRun<StyleElement>) {
lhs.value = lhs.value?.combineHigherPriority(rhs.value) ?? rhs.value
}

// Ordered by priority, lower = higher priority.
var allRuns = _storage.sorted(by: { $0.key < $1.key }).map { $0.value.runs(in: range.intRange) }
var runs: [StyledRangeStoreRun] = []
var runs: [RangeStoreRun<StyleElement>] = []

var minValue = allRuns.compactMap { $0.last }.enumerated().min(by: { $0.1.length < $1.1.length })

Expand All @@ -70,9 +105,9 @@ class StyledRangeContainer {
for idx in (0..<allRuns.count).reversed() where idx != minRunIdx {
guard let last = allRuns[idx].last else { continue }
if idx < minRunIdx {
minRun.combineHigherPriority(last)
combineHigherPriority(&minRun, last)
} else {
minRun.combineLowerPriority(last)
combineLowerPriority(&minRun, last)
}

if last.length == minRun.length {
Expand All @@ -93,8 +128,8 @@ class StyledRangeContainer {
}

func storageUpdated(replacedContentIn range: Range<Int>, withCount newLength: Int) {
_storage.values.forEach {
$0.storageUpdated(replacedCharactersIn: range, withCount: newLength)
for key in _storage.keys {
_storage[key]?.storageUpdated(replacedCharactersIn: range, withCount: newLength)
}
}
}
Expand All @@ -109,11 +144,11 @@ extension StyledRangeContainer: HighlightProviderStateDelegate {
/// - rangeToHighlight: The range to apply the highlights to.
func applyHighlightResult(provider: ProviderID, highlights: [HighlightRange], rangeToHighlight: NSRange) {
assert(rangeToHighlight != .notFound, "NSNotFound is an invalid highlight range")
guard let storage = _storage[provider] else {
guard var storage = _storage[provider] else {
assertionFailure("No storage found for the given provider: \(provider)")
return
}
var runs: [StyledRangeStoreRun] = []
var runs: [RangeStoreRun<StyleElement>] = []
var lastIndex = rangeToHighlight.lowerBound

for highlight in highlights {
Expand All @@ -123,10 +158,9 @@ extension StyledRangeContainer: HighlightProviderStateDelegate {
continue // Skip! Overlapping
}
runs.append(
StyledRangeStoreRun(
RangeStoreRun<StyleElement>(
length: highlight.range.length,
capture: highlight.capture,
modifiers: highlight.modifiers
value: StyleElement(capture: highlight.capture, modifiers: highlight.modifiers)
)
)
lastIndex = highlight.range.max
Expand All @@ -137,6 +171,7 @@ extension StyledRangeContainer: HighlightProviderStateDelegate {
}

storage.set(runs: runs, for: rangeToHighlight.intRange)
_storage[provider] = storage
delegate?.styleContainerDidUpdate(in: rangeToHighlight)
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
//
// StyledRangeStore+Internals.swift
// RangeStore+Internals.swift
// CodeEditSourceEditor
//
// Created by Khan Winter on 10/25/24
//

import _RopeModule

extension StyledRangeStore {
extension RangeStore {
/// Coalesce items before and after the given range.
///
/// Compares the next run with the run at the given range. If they're the same, removes the next run and grows the
Expand All @@ -16,7 +16,7 @@ extension StyledRangeStore {
/// rather than the queried one.
///
/// - Parameter range: The range of the item to coalesce around.
func coalesceNearby(range: Range<Int>) {
mutating func coalesceNearby(range: Range<Int>) {
var index = findIndex(at: range.lastIndex).index
if index < _guts.endIndex && _guts.index(after: index) != _guts.endIndex {
coalesceRunAfter(index: &index)
Expand All @@ -30,11 +30,11 @@ extension StyledRangeStore {
}

/// Check if the run and the run after it are equal, and if so remove the next one and concatenate the two.
private func coalesceRunAfter(index: inout Index) {
private mutating func coalesceRunAfter(index: inout Index) {
let thisRun = _guts[index]
let nextRun = _guts[_guts.index(after: index)]

if thisRun.styleCompare(nextRun) {
if thisRun.compareValue(nextRun) {
_guts.update(at: &index, by: { $0.length += nextRun.length })

var nextIndex = index
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
//
// StyledRangeStore+FindIndex.swift
// RangeStore+FindIndex.swift
// CodeEditSourceEditor
//
// Created by Khan Winter on 1/6/25.
//

extension StyledRangeStore {
extension RangeStore {
/// Finds a Rope index, given a string offset.
/// - Parameter offset: The offset to query for.
/// - Returns: The index of the containing element in the rope.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// RangeStore+OffsetMetric.swift
// CodeEditSourceEditor
//
// Created by Khan Winter on 10/25/24
//

import _RopeModule

extension RangeStore {
struct OffsetMetric: RopeMetric {
typealias Element = StoredRun

func size(of summary: RangeStore.StoredRun.Summary) -> Int {
summary.length
}

func index(at offset: Int, in element: RangeStore.StoredRun) -> Int {
return offset
}
}
}
Loading