From e72bd70be57e70b0fa92b6af0ed75be00e8a34b9 Mon Sep 17 00:00:00 2001 From: Matt <85322+mattmassicotte@users.noreply.github.com> Date: Tue, 30 Jan 2024 12:12:46 -0500 Subject: [PATCH] Synchronous parsing would never succeed #38 --- .../xcshareddata/swiftpm/Package.resolved | 6 +- .../BackgroundingLanguageLayerTree.swift | 33 +++++---- .../TreeSitterClientTests.swift | 72 ++++++++++++------- 3 files changed, 67 insertions(+), 44 deletions(-) diff --git a/Projects/NeonExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Projects/NeonExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 8e85cbe..a1fe135 100644 --- a/Projects/NeonExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Projects/NeonExample.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -5,8 +5,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/ChimeHQ/Rearrange", "state" : { - "revision" : "0fb658e721c68495f6340c211cc6d4719e6b52d8", - "version" : "1.6.0" + "revision" : "5ff7f3363f7a08f77e0d761e38e6add31c2136e1", + "version" : "1.8.1" } }, { @@ -14,7 +14,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/ChimeHQ/SwiftTreeSitter", "state" : { - "revision" : "87ed52a71d4ad6b5e6a11185b42f6f74eb5b47da" + "revision" : "10cb68c00a9963c2884b30f168a9de377790d812" } }, { diff --git a/Sources/TreeSitterClient/BackgroundingLanguageLayerTree.swift b/Sources/TreeSitterClient/BackgroundingLanguageLayerTree.swift index 9f9e809..816be2e 100644 --- a/Sources/TreeSitterClient/BackgroundingLanguageLayerTree.swift +++ b/Sources/TreeSitterClient/BackgroundingLanguageLayerTree.swift @@ -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 @@ -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( + version: Int, operation: @escaping (LanguageLayer) throws -> T, completion: @escaping @MainActor (Result) -> Void ) { - if let tree = accessTreeSynchronously() { + if let tree = accessTreeSynchronously(version: version) { let result = Result(catching: { try operation(tree) }) completion(result) return @@ -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()) @@ -96,7 +95,7 @@ final class BackgroundingLanguageLayerTree { } public func languageConfigurationChanged(for name: String, content: LanguageLayer.Content, completion: @escaping @MainActor (Result) -> Void) { - accessTree { tree in + accessTree(version: currentVersion) { tree in try tree.languageConfigurationChanged(for: name, content: content) } completion: { completion($0) @@ -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 } @@ -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 } @@ -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 } @@ -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) diff --git a/Tests/TreeSitterClientTests/TreeSitterClientTests.swift b/Tests/TreeSitterClientTests/TreeSitterClientTests.swift index 5c45f83..37caf25 100644 --- a/Tests/TreeSitterClientTests/TreeSitterClientTests.swift +++ b/Tests/TreeSitterClientTests/TreeSitterClientTests.swift @@ -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).. 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())