mirror of
https://github.com/keymanapp/keyman.git
synced 2026-09-21 07:07:39 +00:00
chore: reverts right-delete changes
This commit is contained in:
parent
fc2a17890c
commit
a232f32ea4
6 changed files with 51 additions and 182 deletions
|
|
@ -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') {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue