Skip to content

Commit

Permalink
Fixed: suggestion + autocorrection + reentrance selection flash…
Browse files Browse the repository at this point in the history
…. Added: `Lock` (again).
  • Loading branch information
oscbyspro committed Aug 27, 2022
1 parent 1a88752 commit cb8eb65
Show file tree
Hide file tree
Showing 3 changed files with 206 additions and 15 deletions.
69 changes: 69 additions & 0 deletions Sources/DiffableTextKit/Utilities/Lock.swift
@@ -0,0 +1,69 @@
//=----------------------------------------------------------------------------=
// This source file is part of the DiffableTextViews open source project.
//
// Copyright (c) 2022 Oscar Byström Ericsson
// Licensed under Apache License, Version 2.0
//
// See http://www.apache.org/licenses/LICENSE-2.0 for license information.
//=----------------------------------------------------------------------------=

//*============================================================================*
// MARK: * Lock
//*============================================================================*

@MainActor public final class Lock {

//=------------------------------------------------------------------------=
// MARK: State
//=------------------------------------------------------------------------=

@usableFromInline private(set) var count: UInt = 0

//=------------------------------------------------------------------------=
// MARK: Initializers
//=------------------------------------------------------------------------=

@inlinable public init() { }

//=------------------------------------------------------------------------=
// MARK: Accessors
//=------------------------------------------------------------------------=

@inlinable @inline(__always)
public var isLocked: Bool {
self.count != 0
}

//=------------------------------------------------------------------------=
// MARK: Transformations
//=------------------------------------------------------------------------=

@inlinable @inline(__always) func lock() {
self.count += 1
}

@inlinable @inline(__always) func open() {
self.count -= 1
}

//=------------------------------------------------------------------------=
// MARK: Utilities
//=------------------------------------------------------------------------=

@inlinable public func perform(action: () throws -> Void) {
self.lock(); try? action(); self.open()
}

@inlinable public func task(operation: @escaping () async throws -> Void) {
_ = asynchronous(operation: operation)
}

@inlinable public func task(operation: @escaping () async throws -> Void) async {
await asynchronous(operation: operation).value
}

@inlinable @inline(__always) @discardableResult func asynchronous(
operation: @escaping () async throws -> Void) -> Task<Void, Never> {
self.lock(); return Task { try? await operation(); self.open() }
}
}
45 changes: 30 additions & 15 deletions Sources/DiffableTextKitXUIKit/DiffableTextField.swift
Expand Up @@ -94,6 +94,8 @@ public struct DiffableTextField<Style: DiffableTextStyle>: UIViewRepresentable {
// MARK: State
//=--------------------------------------------------------------------=

@usableFromInline let lock = Lock()

@usableFromInline var cache: Cache!
@usableFromInline var context: Context!
@usableFromInline var update = Update()
Expand Down Expand Up @@ -174,7 +176,7 @@ public struct DiffableTextField<Style: DiffableTextStyle>: UIViewRepresentable {
shouldChangeCharactersIn nsrange: NSRange,
replacementString text: String) -> Bool {
//=----------------------------------=
// Lock
// Wait
//=----------------------------------=
guard !update else { return false }
//=----------------------------------=
Expand Down Expand Up @@ -205,9 +207,21 @@ public struct DiffableTextField<Style: DiffableTextStyle>: UIViewRepresentable {

@inlinable @inline(never) public func textFieldDidChangeSelection(_ view: UITextField) {
//=----------------------------------=
// Locked
//=----------------------------------=
guard !lock.isLocked else { return }
//=----------------------------------=
// Reentrance, Known Due To Push Lock
//=----------------------------------=
if !update.isEmpty {
//=------------------------------=
// Push
//=------------------------------=
self.push([.text, .selection])
//=----------------------------------=
// Marked
//=----------------------------------=
if let _ = view.markedTextRange {
} else if let _ = view.markedTextRange {
//=------------------------------=
// Push
//=------------------------------=
Expand All @@ -216,10 +230,6 @@ public struct DiffableTextField<Style: DiffableTextStyle>: UIViewRepresentable {
// Normal
//=----------------------------------=
} else {
//=------------------------------=
// Lock
//=------------------------------=
guard !update else { return }
//=------------------------------=
// Pull
//=------------------------------=
Expand Down Expand Up @@ -290,16 +300,21 @@ public struct DiffableTextField<Style: DiffableTextStyle>: UIViewRepresentable {
return
}
//=----------------------------------=
// Text
//=----------------------------------=
if let _ = self.update.remove(.text) {
self.downstream.text = context.text
}
//=----------------------------------=
// Selection
// Lock
//=----------------------------------=
if let _ = self.update.remove(.selection) {
self.downstream.selection = context.selection()
self.lock.perform {
//=------------------------------=
// Text
//=------------------------------=
if let _ = self.update.remove(.text) {
self.downstream.text = context.text
}
//=------------------------------=
// Selection
//=------------------------------=
if let _ = self.update.remove(.selection) {
self.downstream.selection = context.selection()
}
}
}
}
Expand Down
107 changes: 107 additions & 0 deletions Tests/DiffableTextKitTests/Utilities/Lock.swift
@@ -0,0 +1,107 @@
//=----------------------------------------------------------------------------=
// This source file is part of the DiffableTextViews open source project.
//
// Copyright (c) 2022 Oscar Byström Ericsson
// Licensed under Apache License, Version 2.0
//
// See http://www.apache.org/licenses/LICENSE-2.0 for license information.
//=----------------------------------------------------------------------------=
#if DEBUG

@testable import DiffableTextKit

import XCTest

//*============================================================================*
// MARK: * Lock x Tests
//*============================================================================*

@MainActor final class LockTests: XCTestCase {

//=------------------------------------------------------------------------=
// MARK: State
//=------------------------------------------------------------------------=

var lock = Lock()

//=------------------------------------------------------------------------=
// MARK: State x Setup
//=------------------------------------------------------------------------=

override func setUp() {
lock = Lock()
}

//=------------------------------------------------------------------------=
// MARK: Tests x State
//=------------------------------------------------------------------------=

func testIsNotLockedByDefault() {
XCTAssertFalse(lock.isLocked)
}

func testIsLockedIsSameAsCountIsZero() {
XCTAssertFalse(lock.isLocked)
XCTAssertEqual(lock.count, 0)

lock.lock()
XCTAssert(lock.isLocked)
XCTAssertEqual(lock.count, 1)
}

//=------------------------------------------------------------------------=
// MARK: Tests x Transformations
//=------------------------------------------------------------------------=

func testLockIncrementsCountOpenDecrementsCount() {
XCTAssertEqual(lock.count, 0)

lock.lock()
lock.lock()
lock.lock()

XCTAssertEqual(lock.count, 3)

lock.open()
lock.open()
lock.open()

XCTAssertEqual(lock.count, 0)
}

//=------------------------------------------------------------------------=
// MARK: Tests x Utilities
//=------------------------------------------------------------------------=

func testSynchronousActionLocksUntilCompletion() {
XCTAssertFalse(lock.isLocked)
//=--------------------------------------=
// Start
//=--------------------------------------=
lock.perform {
XCTAssert(self.lock.isLocked)
}
//=--------------------------------------=
// End
//=--------------------------------------=
XCTAssertFalse(lock.isLocked)
}

func testAsynchronousOperationLocksUntilCompletion() async {
XCTAssertFalse(lock.isLocked)
//=--------------------------------------=
// Start
//=--------------------------------------=
let task = lock.asynchronous {
XCTAssert(self.lock.isLocked) // 2nd
}; XCTAssert(self.lock.isLocked) // 1st

await task.value
//=--------------------------------------=
// End
//=--------------------------------------=
XCTAssertFalse(lock.isLocked)
}
}

#endif

0 comments on commit cb8eb65

Please sign in to comment.