Skip to content

Commit

Permalink
Expose and fix sublayer reparsing
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmassicotte committed Jan 8, 2025
1 parent 55f2c2f commit 77a793c
Show file tree
Hide file tree
Showing 2 changed files with 73 additions and 8 deletions.
31 changes: 23 additions & 8 deletions Sources/SwiftTreeSitterLayer/LanguageLayer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -145,29 +145,37 @@ extension LanguageLayer {
private func applyEdit(_ edit: InputEdit) {
state.applyEdit(edit)

// and now update the included ranges
if rangeRestricted, let tree = state.tree {
parser.includedRanges = tree.includedRanges
}

for sublayer in sublayers.values {
sublayer.applyEdit(edit)
}
}

private func shallowParse(with content: Content) -> IndexSet {
private func parse(with content: Content) -> IndexSet {
let newState = parser.parse(state: state, readHandler: content.readHandler)

let oldState = state

self.state = newState

// manual included range bookkeeping, but only if we have previously been restricted
if rangeRestricted, let tree = newState.tree {
self.parser.includedRanges = tree.includedRanges
var invalidations = oldState.changedSet(for: newState)

for layer in sublayers.values {
let subset = layer.parse(with: content)

invalidations.formUnion(subset)
}

return oldState.changedSet(for: newState)
return invalidations
}

private func parse(with content: Content, affecting affectedSet: IndexSet, resolveSublayers resolve: Bool) -> IndexSet {
// afer shallowParse completes, rangeSet is valid again
var set = shallowParse(with: content)
// afer this completes, affectedSet is valid again
var set = parse(with: content)

set.formUnion(affectedSet)

Expand Down Expand Up @@ -205,6 +213,13 @@ extension LanguageLayer {
return didChangeContent(content, using: edit, resolveSublayers: true)
}

/// Inform the layer tree that content has changed.
///
/// By default, this function will eagerly resolve sublayers. However, there could be a significant benefit to deferring that work until a query. Layer resolution is always performed at that point anyways, because it is needed to compute query results.
///
/// - Parameter content: The means of determining the state of the current content.
/// - Parameter edit: Describes how the content has changed
/// - Parameter resolveSublayers: If false, this will defer sublayer resolution.
public func didChangeContent(_ content: LanguageLayer.Content, using edit: InputEdit, resolveSublayers: Bool = true) -> IndexSet {
// includedRangeSet becomes invalid here
applyEdit(edit)
Expand Down Expand Up @@ -331,7 +346,7 @@ extension LanguageLayer {
// included ranges must be sorted and the above algorithm does not guarantee that
self.parser.includedRanges = allRanges.sorted()

let set = shallowParse(with: content)
let set = parse(with: content)

invalidation.formUnion(set)

Expand Down
50 changes: 50 additions & 0 deletions Tests/SwiftTreeSitterLayerTests/LanguageLayerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,56 @@ func main() {}
XCTAssertEqual(highlights, expected)
}


func testReparsingWithinInjection() throws {
let config = LanguageLayer.Configuration(languageProvider: { name in
precondition(name == "swift")

return Self.swiftConfig
})

let tree = try LanguageLayer(languageConfig: Self.selfInjectingSwiftConfig, configuration: config)

let text = """
let a = "var a = 1"
func main() {}
"""
tree.replaceContent(with: text)

print(try tree.highlights(in: NSRange(0..<text.utf16.count), provider: { _, _ in nil }))

// make a sufficient change so we can be sure we've actually invalidated the right stuff
let newText = """
let a = "func b() {}"
func main() {}
"""

let edit = InputEdit(
startByte: 9*2,
oldEndByte: 18*2,
newEndByte: 20*2,
startPoint: Point(0, 9*2),
oldEndPoint: Point(0, 18*2),
newEndPoint: Point(0, 20*2)
)

let invalidation = tree.didChangeContent(.init(string: newText), using: edit, resolveSublayers: false)

XCTAssertEqual(invalidation, IndexSet(integersIn: 9..<20))

let highlights = try tree.highlights(in: NSRange(0..<newText.utf16.count), provider: { _, _ in nil })

let expected = [
NamedRange(name: "keyword", range: NSRange(0..<3), pointRange: Point(0, 0)..<Point(0, 6)),
NamedRange(name: "keyword.function", range: NSRange(23..<27), pointRange: Point(2, 0)..<Point(2, 8)),
NamedRange(name: "keyword.function", range: NSRange(9..<13), pointRange: Point(0, 9*2)..<Point(0, 13*2)),
]

XCTAssertEqual(highlights, expected)
}

func testMultipleInjectionsinSameLayer() throws {
let config = LanguageLayer.Configuration(languageProvider: { name in
precondition(name == "swift")
Expand Down

0 comments on commit 77a793c

Please sign in to comment.