Skip to content

Commit

Permalink
TokenApplication API
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmassicotte committed Apr 11, 2022
1 parent a9cb52f commit 99d8794
Show file tree
Hide file tree
Showing 4 changed files with 53 additions and 31 deletions.
38 changes: 9 additions & 29 deletions Sources/Neon/Highlighter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,6 @@ import Foundation
import Rearrange
import os.log

public protocol TextSystemInterface {
func clearStyle(in range: NSRange)
func applyStyle(to token: Token)

var length: Int { get }
var visibleRange: NSRange { get }
}

public class Highlighter {
public var textInterface: TextSystemInterface
Expand All @@ -17,14 +10,12 @@ public class Highlighter {
private var pendingSet: IndexSet
private var log: OSLog
public var tokenProvider: TokenProvider
private var failed: Bool

public init(textInterface: TextSystemInterface, tokenProvider: TokenProvider? = nil) {
self.textInterface = textInterface
self.validSet = IndexSet()
self.pendingSet = IndexSet()
self.tokenProvider = tokenProvider ?? { _, block in block(.success([]))}
self.failed = false

self.log = OSLog(subsystem: "com.chimehq.Neon", category: "Highlighter")
}
Expand All @@ -34,7 +25,10 @@ extension Highlighter {
public func invalidate(_ set: IndexSet) {
dispatchPrecondition(condition: .onQueue(.main))

self.failed = false
if set.isEmpty {
return
}

validSet.subtract(set)
pendingSet.subtract(set)

Expand Down Expand Up @@ -123,19 +117,19 @@ extension Highlighter {
}

private func makeNextTokenRequest() {
guard failed == false else { return }

guard let range = nextNeededTokenRange() else { return }

self.pendingSet.insert(range: range)

// this can be called 0 or more times
tokenProvider(range) { result in
dispatchPrecondition(condition: .onQueue(.main))

switch result {
case .failure(let error):
os_log("failed to get tokens: %{public}@", log: self.log, type: .error, String(describing: error))

DispatchQueue.main.async {
self.failed = true
self.pendingSet.remove(integersIn: range)
}
case .success(let tokens):
Expand All @@ -156,22 +150,8 @@ extension Highlighter {

let receivedSet = IndexSet(integersIn: range)

applyStyle(with: tokenApplication, in: receivedSet)
}

private func applyStyle(with tokenApplication: TokenApplication, in set: IndexSet) {
precondition(set.isEmpty == false)

if tokenApplication.action == .replace {
for range in set.nsRangeView {
textInterface.clearStyle(in: range)
}
}

for token in tokenApplication.tokens {
textInterface.applyStyle(to: token)
}
textInterface.apply(tokenApplication, to: receivedSet)

validSet.formUnion(set)
validSet.formUnion(receivedSet)
}
}
31 changes: 31 additions & 0 deletions Sources/Neon/TextSystemInterface.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import Foundation

public protocol TextSystemInterface {
func clearStyle(in range: NSRange)
func applyStyle(to token: Token)

var length: Int { get }
var visibleRange: NSRange { get }
}

public extension TextSystemInterface {
func clearStyles(in set: IndexSet) {
for range in set.nsRangeView {
clearStyle(in: range)
}
}

func applyStyles(to tokens: [Token]) {
for token in tokens {
applyStyle(to: token)
}
}

func apply(_ tokenApplication: TokenApplication, to set: IndexSet) {
if tokenApplication.action == .replace {
clearStyles(in: set)
}

applyStyles(to: tokenApplication.tokens)
}
}
12 changes: 11 additions & 1 deletion Sources/Neon/Token.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ extension Token: Hashable {
public struct TokenApplication {
public enum Action {
case replace
case merge
case apply
}

public let tokens: [Token]
Expand All @@ -26,6 +26,8 @@ public struct TokenApplication {
self.tokens = tokens
self.action = action
}

public static let noChange = TokenApplication(tokens: [], action: .apply)
}

extension TokenApplication: ExpressibleByArrayLiteral {
Expand All @@ -36,4 +38,12 @@ extension TokenApplication: ExpressibleByArrayLiteral {
}
}

/// A source of `Token` information
///
/// The callback argument of this function is a little funny. It is ok
/// to invoke it 0 to N times. Always invoke it on the main queue.
///
/// Note: failures are always assumed to be transient. The
/// best way to indicate a permenant failure is to just return
/// `TokenApplication.noChange`.
public typealias TokenProvider = (NSRange, @escaping (Result<TokenApplication, Error>) -> Void) -> Void
3 changes: 2 additions & 1 deletion Sources/Neon/TreeSitterClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -260,8 +260,9 @@ extension TreeSitterClient {
/// Determine if it is likely that a synchronous query will execute quickly
public func canAttemptSynchronousQuery(in range: NSRange) -> Bool {
let largeRange = range.length > synchronousLengthThreshold
let largeLocation = range.max > synchronousContentLengthThreshold

return hasQueuedWork == false && largeRange == false
return (hasQueuedWork || largeRange || largeLocation) == false
}

/// Executes a query and returns a ResolvingQueryCursor
Expand Down

0 comments on commit 99d8794

Please sign in to comment.