Skip to content

Commit

Permalink
Synchronous parsing would never succeed #38
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmassicotte committed Jan 30, 2024
1 parent d804e19 commit e72bd70
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/ChimeHQ/Rearrange",
"state" : {
"revision" : "0fb658e721c68495f6340c211cc6d4719e6b52d8",
"version" : "1.6.0"
"revision" : "5ff7f3363f7a08f77e0d761e38e6add31c2136e1",
"version" : "1.8.1"
}
},
{
"identity" : "swifttreesitter",
"kind" : "remoteSourceControl",
"location" : "https://github.com/ChimeHQ/SwiftTreeSitter",
"state" : {
"revision" : "87ed52a71d4ad6b5e6a11185b42f6f74eb5b47da"
"revision" : "10cb68c00a9963c2884b30f168a9de377790d812"
}
},
{
Expand Down
33 changes: 16 additions & 17 deletions Sources/TreeSitterClient/BackgroundingLanguageLayerTree.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ final class BackgroundingLanguageLayerTree {
}

private let queue = DispatchQueue(label: "com.chimehq.QueuedLanguageLayerTree")
private var version = 0
private var commitedVersion = 0
private var currentVersion = 0
private var committedVersion = 0
private var pendingOldPoint: Point?
private let rootLayer: LanguageLayer
private let configuration: Configuration
Expand All @@ -40,21 +40,18 @@ final class BackgroundingLanguageLayerTree {
self.rootLayer = try LanguageLayer(languageConfig: rootLanguageConfig, configuration: configuration.layerConfiguration)
}

private var pendingWork: Bool {
version != commitedVersion
}

private func accessTreeSynchronously() -> LanguageLayer? {
guard pendingWork == false else { return nil }
private func accessTreeSynchronously(version: Int) -> LanguageLayer? {
guard version == committedVersion else { return nil }

return rootLayer
}

private func accessTree<T>(
version: Int,
operation: @escaping (LanguageLayer) throws -> T,
completion: @escaping @MainActor (Result<T, Error>) -> Void
) {
if let tree = accessTreeSynchronously() {
if let tree = accessTreeSynchronously(version: version) {
let result = Result(catching: { try operation(tree) })
completion(result)
return
Expand All @@ -77,15 +74,17 @@ final class BackgroundingLanguageLayerTree {
public func didChangeContent(_ content: LanguageLayer.Content, in range: NSRange, delta: Int, completion: @escaping @MainActor (IndexSet) -> Void) {
let transformer = configuration.locationTransformer

self.version += 1
// here, we know that the text has changed, but accessing the immediately-proceeding version is exactly how re-parsing works and we want that to be ok in this situation.
let version = self.currentVersion
self.currentVersion += 1

let oldEndPoint = pendingOldPoint ?? transformer(range.max) ?? .zero
let edit = InputEdit(range: range, delta: delta, oldEndPoint: oldEndPoint, transformer: transformer)

accessTree { tree in
accessTree(version: version) { tree in
tree.didChangeContent(content, using: edit, resolveSublayers: false)
} completion: { result in
self.commitedVersion += 1
self.committedVersion += 1

do {
completion(try result.get())
Expand All @@ -96,7 +95,7 @@ final class BackgroundingLanguageLayerTree {
}

public func languageConfigurationChanged(for name: String, content: LanguageLayer.Content, completion: @escaping @MainActor (Result<IndexSet, Error>) -> Void) {
accessTree { tree in
accessTree(version: currentVersion) { tree in
try tree.languageConfigurationChanged(for: name, content: content)
} completion: {
completion($0)
Expand All @@ -106,7 +105,7 @@ final class BackgroundingLanguageLayerTree {

extension BackgroundingLanguageLayerTree {
public func executeQuery(_ queryDef: Query.Definition, in set: IndexSet) throws -> LanguageTreeQueryCursor {
guard let tree = accessTreeSynchronously() else {
guard let tree = accessTreeSynchronously(version: currentVersion) else {
throw BackgroundingLanguageLayerTreeError.unavailable
}

Expand All @@ -115,7 +114,7 @@ extension BackgroundingLanguageLayerTree {

public func executeQuery(_ queryDef: Query.Definition, in set: IndexSet) async throws -> [QueryMatch] {
try await withCheckedThrowingContinuation { continuation in
accessTree { tree in
accessTree(version: currentVersion) { tree in
guard let snapshot = tree.snapshot(in: set) else {
throw BackgroundingLanguageLayerTreeError.unableToSnapshot
}
Expand All @@ -141,7 +140,7 @@ extension BackgroundingLanguageLayerTree {

extension BackgroundingLanguageLayerTree {
public func resolveSublayers(with content: LanguageLayer.Content, in set: IndexSet) throws -> IndexSet {
guard let tree = accessTreeSynchronously() else {
guard let tree = accessTreeSynchronously(version: currentVersion) else {
throw BackgroundingLanguageLayerTreeError.unavailable
}

Expand All @@ -150,7 +149,7 @@ extension BackgroundingLanguageLayerTree {

public func resolveSublayers(with content: LanguageLayer.Content, in set: IndexSet) async throws -> IndexSet {
try await withCheckedThrowingContinuation { continuation in
accessTree { tree in
accessTree(version: currentVersion) { tree in
try tree.resolveSublayers(with: content, in: set)
} completion: { result in
continuation.resume(with: result)
Expand Down
72 changes: 48 additions & 24 deletions Tests/TreeSitterClientTests/TreeSitterClientTests.swift
Original file line number Diff line number Diff line change
@@ -1,9 +1,56 @@
import XCTest

import Rearrange
import SwiftTreeSitter
@testable import TreeSitterClient
import TreeSitterClient
import NeonTestsTreeSitterSwift

final class TreeSitterClientTests: XCTestCase {
@MainActor
func testSynchronousQuery() throws {
let language = Language(tree_sitter_swift())

let queryText = """
("func" @a)
"""
let query = try Query(language: language, data: Data(queryText.utf8))

let languageConfig = LanguageConfiguration(
tree_sitter_swift(),
name: "Swift",
queries: [.highlights: query]
)

let source = """
func main() {
print("hello!")
}
"""

let clientConfig = TreeSitterClient.Configuration(
languageProvider: { _ in nil },
contentProvider: { _ in .init(string: source) },
lengthProvider: { source.utf16.count },
invalidationHandler: { _ in },
locationTransformer: { _ in nil }
)

let client = try TreeSitterClient(
rootLanguageConfig: languageConfig,
configuration: clientConfig
)

let provider = source.predicateTextProvider

let highlights = try client.highlights(in: NSRange(0..<24), provider: provider, mode: .required)
let expected = [
NamedRange(name: "a", range: NSRange(0..<4), pointRange: Point(row: 0, column: 0)..<Point(row: 0, column: 8))
]

XCTAssertEqual(highlights, expected)
}
}

//final class TreeSitterClientTests: XCTestCase {
// func testInsertAffectedRange() {
// let mutation = RangeMutation(range: .zero, delta: 10, limit: 10)
Expand Down Expand Up @@ -59,29 +106,6 @@ import NeonTestsTreeSitterSwift
//}
//
//extension TreeSitterClientTests {
// private func makeSwiftClient() throws -> TreeSitterClient {
// let language = Language(language: tree_sitter_swift())
//
// return try TreeSitterClient(language: language)
// }
//
// func testBasicParse() async throws {
// let language = Language(language: tree_sitter_swift())
//
// let client = try TreeSitterClient(language: language)
//
//let content = """
//func main() { print("hello" }
//"""
// await MainActor.run {
// client.didChangeContent(to: content, in: .zero, delta: content.utf16.count, limit: content.utf16.count)
// }
//
// let tree = try await client.currentTree()
// let root = try XCTUnwrap(tree.rootNode)
//
// XCTAssertEqual(root.childCount, 1)
// }
//
// func testRegularQuery() async throws {
// let language = Language(language: tree_sitter_swift())
Expand Down

0 comments on commit e72bd70

Please sign in to comment.