chore: reverts right-delete changes

This commit is contained in:
jahorton 2021-02-26 10:32:48 +07:00
parent fc2a17890c
commit a232f32ea4
6 changed files with 51 additions and 182 deletions

View file

@ -36,7 +36,6 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.text.Editable;
import android.text.InputType;
import android.util.Log;
import android.view.KeyEvent;
@ -2350,6 +2349,10 @@ public final class KMManager {
// This annotation is required in Jelly Bean and later:
@JavascriptInterface
public void insertText(final int dn, final String s, final int dr) {
if(dr != 0) {
Log.d(TAG, "Right deletions requested but are not presently supported by the in-app keyboard.");
}
Handler mainLoop = new Handler(Looper.getMainLooper());
mainLoop.post(new Runnable() {
public void run() {
@ -2381,28 +2384,6 @@ public final class KMManager {
start = temp;
}
// As we depend upon the caret's current position for the in-app implementation,
// we need to perform right-deletions BEFORE left-deletions & text insertion.
if(dr > 0) {
for (int i = 0; i < dr; i++) {
Editable context = textView.getText();
CharSequence chars = context.subSequence(end, context.length());
if (chars != null && chars.length() > 0) {
char c = chars.charAt(0);
InAppKeyboardShouldIgnoreTextChange = true;
InAppKeyboardShouldIgnoreSelectionChange = true;
if (Character.isHighSurrogate(c)) {
textView.getText().delete(start, end+2);
} else {
textView.getText().delete(start, end+1);
}
start = textView.getSelectionStart();
end = textView.getSelectionEnd();
}
}
}
if (dn <= 0) {
if (start == end) {
if (!s.isEmpty() && s.charAt(0) == '\n') {

View file

@ -341,12 +341,6 @@ class ModelCompositor {
// A predictive text default (on iOS, at least) - immediately wordbreak
// on suggestions accepted mid-word.
suggestion.transform.insert += punctuation.insertAfterWord;
// // Unfortunately, we can't quite enable the next two lines in iOS yet; there's an
// // odd issue with its textDocumentProxy that gives us the wrong results.
//
// let righthandSplit = tokenization.right[0];
// suggestion.transform.deleteRight = (suggestion.transform.deleteRight || 0) + righthandSplit.kmwLength();
}
}
@ -458,7 +452,6 @@ class ModelCompositor {
// Step 1: generate and save the reversion's Transform.
let sourceTransform = suggestion.transform;
let deletedLeftChars = context.left.kmwSubstr(-sourceTransform.deleteLeft, sourceTransform.deleteLeft);
let deletedRightChars = context.right ? context.right.kmwSubstr(0, sourceTransform.deleteRight || 0) : '';
let insertedLength = sourceTransform.insert.kmwLength();
let reversionTransform: Transform = {
@ -466,18 +459,12 @@ class ModelCompositor {
deleteLeft: insertedLength
};
let postCaretReversionTransform: Transform = {
insert: deletedRightChars,
deleteLeft: 0
}
// Step 2: building the proper 'displayAs' string for the Reversion
let postContext = context;
if(postTransform) {
// The code above restores the state to the context at the time the `Suggestion` was created.
// `postTransform` handles any missing context that came later.
reversionTransform = models.buildMergedTransform(reversionTransform, postTransform);
reversionTransform = models.buildMergedTransform(reversionTransform, postCaretReversionTransform);
// Now that we've built the reversion based upon the Suggestion's original context,
// we manipulate it in order to get a proper 'displayAs' string.

View file

@ -168,7 +168,6 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
private var keymanWeb: KeymanWebViewController
private var swallowBackspaceTextChange: Bool = false
private var swallowContextChangeCount: Int = 0
open class var isPortrait: Bool {
return UIScreen.main.bounds.width < UIScreen.main.bounds.height
@ -322,13 +321,6 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
self.swallowBackspaceTextChange = false
return
}
// Currently motivated by the need to shift caret position to perform
// right-deletions.
if self.swallowContextChangeCount > 0 {
self.swallowContextChangeCount -= 1
return
}
let contextBeforeInput = textDocumentProxy.documentContextBeforeInput ?? ""
let contextAfterInput = textDocumentProxy.documentContextAfterInput ?? ""
@ -346,7 +338,7 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
// We should NOT call .resetContext() here for this reason.
}
func insertText(_ keymanWeb: KeymanWebViewController, numCharsToLeftDelete: Int, newText: String, numCharsToRightDelete: Int) {
func insertText(_ keymanWeb: KeymanWebViewController, numCharsToDelete: Int, newText: String) {
if keymanWeb.isSubKeysMenuVisible {
return
}
@ -359,7 +351,9 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
perform(#selector(self.enableInputClickSound), with: nil, afterDelay: 0.1)
}
if numCharsToLeftDelete <= 0 {
if numCharsToDelete <= 0 {
textDocumentProxy.insertText(newText)
// A full-context deletion will report numCharsToDelete == 0 and won't
// otherwise delete selected text.
if #available(iOSApplicationExtension 11.0, *) {
@ -369,143 +363,43 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
}
}
}
} else {
for _ in 0..<numCharsToLeftDelete {
let oldContext = textDocumentProxy.documentContextBeforeInput ?? ""
textDocumentProxy.deleteBackward()
let newContext = textDocumentProxy.documentContextBeforeInput ?? ""
let unitsDeleted = oldContext.utf16.count - newContext.utf16.count
if unitsDeleted > 1 {
if !InputViewController.isSurrogate(oldContext.utf16.last!) {
let lowerIndex = oldContext.utf16.index(oldContext.utf16.startIndex,
offsetBy: newContext.utf16.count)
let upperIndex = oldContext.utf16.index(lowerIndex, offsetBy: unitsDeleted - 1)
let remnant = String(oldContext.utf16[lowerIndex..<upperIndex]) ?? ""
textDocumentProxy.insertText(remnant)
}
}
return
}
// Refer to `func textDidChange()` and https://github.com/keymanapp/keyman/pull/2770 for context.
if textDocumentProxy.documentContextBeforeInput == nil ||
(textDocumentProxy.documentContextBeforeInput == "\n" && Manager.shared.isSystemKeyboard) {
if(self.swallowBackspaceTextChange) {
// A single keyboard processing command should never trigger two of these in a row;
// only one output function will perform deletions.
// This should allow us to debug any failures of this assumption.
// So far, only occurs when debugging a breakpoint during a touch event on BKSP,
// so all seems good.
log.verbose("Failed to swallow a recent textDidChange call!")
}
self.swallowBackspaceTextChange = true
break
for _ in 0..<numCharsToDelete {
let oldContext = textDocumentProxy.documentContextBeforeInput ?? ""
textDocumentProxy.deleteBackward()
let newContext = textDocumentProxy.documentContextBeforeInput ?? ""
let unitsDeleted = oldContext.utf16.count - newContext.utf16.count
if unitsDeleted > 1 {
if !InputViewController.isSurrogate(oldContext.utf16.last!) {
let lowerIndex = oldContext.utf16.index(oldContext.utf16.startIndex,
offsetBy: newContext.utf16.count)
let upperIndex = oldContext.utf16.index(lowerIndex, offsetBy: unitsDeleted - 1)
textDocumentProxy.insertText(String(oldContext[lowerIndex..<upperIndex]))
}
}
// Refer to `func textDidChange()` and https://github.com/keymanapp/keyman/pull/2770 for context.
if textDocumentProxy.documentContextBeforeInput == nil ||
(textDocumentProxy.documentContextBeforeInput == "\n" && Manager.shared.isSystemKeyboard) {
if(self.swallowBackspaceTextChange) {
// A single keyboard processing command should never trigger two of these in a row;
// only one output function will perform deletions.
// This should allow us to debug any failures of this assumption.
// So far, only occurs when debugging a breakpoint during a touch event on BKSP,
// so all seems good.
log.verbose("Failed to swallow a recent textDidChange call!")
}
self.swallowBackspaceTextChange = true
break
}
}
if !newText.isEmpty {
textDocumentProxy.insertText(newText)
}
// NOTE: when a script uses character clusters, the code below fails to
// produce the correct effects despite `textDocumentProxy` reporting that
// the code correctly manipulates the text. There's either a bug in Apple's
// code or something critical that we're missing here.
//
// The `textDidChange` call that occurs after this `insertText` will
// report the final, 'incorrect' details, rather than what is reported
// at the end of this method.
if numCharsToRightDelete > 0 {
// Should never change throughout the next loop; right-deletions only!
let oldLeftContext = textDocumentProxy.documentContextBeforeInput ?? ""
var pointsToDelete = numCharsToRightDelete
while pointsToDelete > 0 {
let oldContext = textDocumentProxy.documentContextAfterInput ?? ""
self.swallowContextChangeCount += 1
// Asynchronously triggers a context change.
textDocumentProxy.adjustTextPosition(byCharacterOffset: 1)
let newContext = textDocumentProxy.documentContextAfterInput ?? ""
var failsafeCount = 0
while (textDocumentProxy.documentContextBeforeInput ?? "" != oldLeftContext)
&& failsafeCount < numCharsToRightDelete { // mild breakage > locked kbd
// While adjustTextPosition makes cluster-based jumps...
// deleteBackward does not. So, we 'force' it.
textDocumentProxy.deleteBackward()
failsafeCount += 1
}
// Determine the removed codepoint count. Also, find the index
// of the first character that shouldn't be removed, if possible.
let unitsDeleted = oldContext.utf16.count - newContext.utf16.count
var pointsDeleted = 0
// Our current index within the deleted context.
var deletedIndex = oldContext.utf16.startIndex
// Marks the first index we wish to NOT remove.
var remnantIndex: String.Index? = nil
// Marks the first index that was NOT removed by deleteBackward.
let undeletedStartIndex = oldContext.utf16.index(deletedIndex, offsetBy: unitsDeleted)
while deletedIndex < undeletedStartIndex {
if(InputViewController.isSurrogate(oldContext.utf16[deletedIndex])) {
// Check - is it truly a surrogate pair?
let pairedIndex = oldContext.utf16.index(after: deletedIndex)
if(InputViewController.isSurrogate(oldContext.utf16[pairedIndex])) {
// If so, pre-emptively increase the index - the pair will count
// as a single character as a result.
deletedIndex = pairedIndex
}
}
pointsDeleted += 1
deletedIndex = oldContext.utf16.index(after: deletedIndex)
// Intended end of deletion found!
if pointsDeleted == pointsToDelete {
remnantIndex = deletedIndex
break // There's nothing to be gained by further loop iterations.
}
}
if let remnantIndex = remnantIndex, remnantIndex < undeletedStartIndex {
// We need to restore some of the deleted text!
let remnant = String(oldContext.utf16[remnantIndex..<undeletedStartIndex]) ?? ""
textDocumentProxy.insertText(remnant)
// Must place the caret back in its correct position!
// Unfortunately, Apple's adjustTextPosition is definitely cluster-based.
var leftContext = textDocumentProxy.documentContextBeforeInput ?? ""
// Ideally, we want to stop at the exact position, but the lack of
// granularity on adjustTextPosition may not cooperate with that goal.
while(leftContext.count > oldLeftContext.count) {
textDocumentProxy.adjustTextPosition(byCharacterOffset: -remnant.count)
leftContext = textDocumentProxy.documentContextBeforeInput ?? ""
}
// FAILSAFE, non-ideal behavior below.
//
// It's probably (?) better to keep the caret at the end of a cluster that
// previously started before the caret, even if right-deletions add new
// characters to the cluster. I think.
//
// Khmer example: if right-deletes leave a joeung-S that can attach
// to a main consonant to the left of my cursor, it will 'snap' into
// a cluster. It'd be more natural to have that cluster on the left,
// so I can add more chars to the cluster if desired.
//
// It's also easier to immediately backspace the extra chars if needed
// this way, rather than forcing a caret reposition.
if(leftContext.count < oldLeftContext.count) {
log.debug("Could not reposition caret perfectly after a right-delete!")
textDocumentProxy.adjustTextPosition(byCharacterOffset: 1)
}
}
// We've handled a set of code points; make sure we mark our progress!
pointsToDelete -= pointsDeleted
}
}
}
func menuKeyUp(_ keymanWeb: KeymanWebViewController) {
@ -657,9 +551,7 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate {
}
func resetContext() {
if self.swallowContextChangeCount <= 0 {
keymanWeb.resetContext()
}
keymanWeb.resetContext()
}
internal func setSentryState(enabled: Bool) {

View file

@ -16,7 +16,7 @@ protocol KeymanWebDelegate: class {
/// - Parameters:
/// - numCharsToDelete: The number of UTF-16 code units to delete before inserting the new text.
/// - newText: The string to insert.
func insertText(_ keymanWeb: KeymanWebViewController, numCharsToLeftDelete: Int, newText: String, numCharsToRightDelete: Int)
func insertText(_ keymanWeb: KeymanWebViewController, numCharsToDelete: Int, newText: String)
/// - Parameters:
func beep(_ keymanWeb: KeymanWebViewController)

View file

@ -388,14 +388,16 @@ extension KeymanWebViewController: WKScriptMessageHandler {
let dn = Int(fragment[dnRange.upperBound..<sRange.lowerBound])!
let s = fragment[sRange.upperBound..<drRange.lowerBound]
// This computes the number of requested right-deletion characters.
let dr = Int(fragment[drRange.upperBound...])!
// Use it when we're ready to implement that.
// Our .insertText will need to be adjusted accordingly.
_ = Int(fragment[drRange.upperBound...])!
// KMW uses dn == -1 to perform special processing of deadkeys.
// This is handled outside of Swift so we don't delete any characters.
let numCharsToDelete = max(0, dn)
let newText = String(s).stringFromUTF16CodeUnits() ?? ""
insertText(self, numCharsToDelete: numCharsToDelete, newText: newText)
delegate?.insertText(self, numCharsToLeftDelete: numCharsToDelete, newText: newText, numCharsToRightDelete: dr)
delegate?.insertText(self, numCharsToDelete: numCharsToDelete, newText: newText)
} else if fragment.hasPrefix("#showKeyPreview-") {
let xKey = fragment.range(of: "+x=")!
let yKey = fragment.range(of: "+y=")!
@ -636,7 +638,7 @@ extension KeymanWebViewController: KeymanWebDelegate {
}
}
func insertText(_ view: KeymanWebViewController, numCharsToLeftDelete: Int, newText: String, numCharsToRightDelete: Int) {
func insertText(_ view: KeymanWebViewController, numCharsToDelete: Int, newText: String) {
dismissHelpBubble()
Manager.shared.isKeymanHelpOn = false
}

View file

@ -114,6 +114,13 @@ public class TextView: UITextView, KeymanResponder {
}
}
public override var selectedTextRange: UITextRange? {
didSet {
Manager.shared.setContextState(text: self.text, range: selectedRange)
Manager.shared.resetContext()
}
}
// MARK: - Keyman notifications
private func keyboardChanged(_ kb: InstallableKeyboard) {
if !shouldSetCustomFontOnKeyboardChange {