diff --git a/Sources/Runestone/TextView/Core/TextInputStringTokenizer.swift b/Sources/Runestone/TextView/Core/TextInputStringTokenizer.swift index 295252a0..12f45fab 100644 --- a/Sources/Runestone/TextView/Core/TextInputStringTokenizer.swift +++ b/Sources/Runestone/TextView/Core/TextInputStringTokenizer.swift @@ -3,6 +3,10 @@ import UIKit final class TextInputStringTokenizer: UITextInputStringTokenizer { var lineManager: LineManager var stringView: StringView + // Used to ensure we can workaround bug where multi-stage input, like when entering Korean text + // does not work properly. If we do not treat navigation between word boundies as a special case then + // navigating with Shift + Option + Arrow Keys followed by Shift + Arrow Keys will not work correctly. + var didCallPositionFromPositionToWordBoundary = false private let lineControllerStorage: LineControllerStorage private var newlineCharacters: [Character] { @@ -206,6 +210,7 @@ private extension TextInputStringTokenizer { guard let indexedPosition = position as? IndexedPosition else { return nil } + didCallPositionFromPositionToWordBoundary = true let location = indexedPosition.index let alphanumerics = CharacterSet.alphanumerics if direction.isForward { diff --git a/Sources/Runestone/TextView/Core/TextInputView.swift b/Sources/Runestone/TextView/Core/TextInputView.swift index a512f836..b6f61d0b 100644 --- a/Sources/Runestone/TextView/Core/TextInputView.swift +++ b/Sources/Runestone/TextView/Core/TextInputView.swift @@ -47,6 +47,16 @@ final class TextInputView: UIView, UITextInput { shouldNotifyInputDelegate = true didCallPositionFromPositionInDirectionWithOffset = false } + // This is a consequence of our workaround that ensures multi-stage input, such as when entering Korean, + // works correctly. The workaround causes bugs when selecting words using Shift + Option + Arrow Keys + // followed by Shift + Arrow Keys if we do not treat it as a special case. + // The consequence of not having this workaround is that Shift + Arrow Keys may adjust the wrong end of + // the selected text when followed by navigating between word boundaries usign Shift + Option + Arrow Keys. + if customTokenizer.didCallPositionFromPositionToWordBoundary && !didCallDeleteBackward { + shouldNotifyInputDelegate = true + customTokenizer.didCallPositionFromPositionToWordBoundary = false + } + didCallDeleteBackward = false notifyInputDelegateAboutSelectionChangeInLayoutSubviews = !shouldNotifyInputDelegate if shouldNotifyInputDelegate { inputDelegate?.selectionWillChange(self) @@ -594,6 +604,7 @@ final class TextInputView: UIView, UITextInput { private var notifyInputDelegateAboutSelectionChangeInLayoutSubviews = false private var notifyDelegateAboutSelectionChangeInLayoutSubviews = false private var didCallPositionFromPositionInDirectionWithOffset = false + private var didCallDeleteBackward = false private var hasDeletedTextWithPendingLayoutSubviews = false private var preserveUndoStackWhenSettingString = false private var cancellables: [AnyCancellable] = [] @@ -1116,6 +1127,7 @@ extension TextInputView { } func deleteBackward() { + didCallDeleteBackward = true guard let selectedRange = markedRange ?? selectedRange, selectedRange.length > 0 else { return }