-
-
Notifications
You must be signed in to change notification settings - Fork 103
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(common/models): mid-context suggestions & reversions, fix(common/models): correction-search SMP issues #4427
Conversation
…redictions' into feat/common/models/mid-context-suggestions
…redictions' into feat/common/models/mid-context-suggestions
if numCharsToRightDelete > 0 { | ||
for _ in 0..<numCharsToRightDelete { | ||
textDocumentProxy.adjustTextPosition(byCharacterOffset: 1) | ||
textDocumentProxy.deleteBackward() | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably be done more properly. It was sufficient for initial testing, at least.
Unfortunately, there is no deleteForward
command on that object, hence the text-position shenanigans.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not possible to adjustTextPosition(byCharacterOffset: numCharsToRightDelete)
?
I am guessing this needs some real testing for interactions with clusters, SMP because it's likely that the cursor cannot be positioned in the middle of a cluster.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sure it's possible to do that... but it may make any SMP-double-checking even rougher.
I'll definitely need to reference this block:
keyman/ios/engine/KMEI/KeymanEngine/Classes/InputViewController.swift
Lines 366 to 378 in 58a2bda
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])) | |
} | |
} |
Noting the step-by-step deletion procedure it follows... and the need to compare/contrast Swift's preferred UTF-8 representation and UTF-16, it's probably best to go step-by-step here too.
@darcywong00 Might need your help on this one. I can identify the system-keyboard block that handles this pretty easily: keyman/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java Lines 2547 to 2559 in 58a2bda
There simply is no equivalent for the in-app, and the surrounding code is remarkably different between the two cases. All the in-app one does: keyman/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java Lines 2354 to 2356 in 58a2bda
I'd prefer not to make things worse there than they already are, hence the call for help here. |
I hope you've got a time machine cause you wrote both right-deletion blocks in #1732 😄 |
And I can see why I didn't - take a look at how different the rest of the |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the improvements in general. I have a few questions, and a concern around testing on iOS (presumably similar on Android although that may be coming later?)
if numCharsToRightDelete > 0 { | ||
for _ in 0..<numCharsToRightDelete { | ||
textDocumentProxy.adjustTextPosition(byCharacterOffset: 1) | ||
textDocumentProxy.deleteBackward() | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not possible to adjustTextPosition(byCharacterOffset: numCharsToRightDelete)
?
I am guessing this needs some real testing for interactions with clusters, SMP because it's likely that the cursor cannot be positioned in the middle of a cluster.
@@ -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, numCharsToDelete: Int, newText: String) | |||
func insertText(_ keymanWeb: KeymanWebViewController, numCharsToLeftDelete: Int, newText: String, numCharsToRightDelete: Int) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd have kinda liked this to have a different parameter order:
func insertText(_ keymanWeb: KeymanWebViewController, numCharsToLeftDelete: Int, newText: String, numCharsToRightDelete: Int) | |
func insertText(_ keymanWeb: KeymanWebViewController, numCharsToLeftDelete: Int, numCharsToRightDelete: Int, newText: String) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
From Web:
Lines 633 to 638 in 58a2bda
/** | |
* @param {number} dn Number of pre-caret characters to delete | |
* @param {string} s Text to insert | |
* @param {number=} dr Number of post-caret characters to delete | |
*/ | |
['oninserttext']: (dn: number, s: string, dr?: number) => void; |
keyman/web/source/dom/domOverrides.ts
Lines 36 to 38 in 58a2bda
if(keyman.isEmbedded) { | |
// A special embedded callback used to setup direct callbacks to app-native code. | |
keyman['oninserttext'](ruleTransform.deleteLeft, ruleTransform.insert, ruleTransform.deleteRight); |
From Android:
keyman/android/KMEA/app/src/main/java/com/tavultesoft/kmea/KMManager.java
Lines 2351 to 2353 in 58a2bda
// This annotation is required in Jelly Bean and later: | |
@JavascriptInterface | |
public void insertText(final int dn, final String s, final int dr) { |
That said, both of these arose during 14.0 - there's no evidence of them in 13.0. In their original versions, they closely matched the iOS function above: deleteLeft
, then newText
.
Admittedly, the order was chosen b/c it's a new parameter, and new things - especially potentially-undefined parameters (b/c JS/TS) - go on the right-hand side. That said, there is a beauty to the order:
abc de | fg hij
With respect to the caret, we first delete-left, then insert the text at the caret's position, then delete-right if needed after the caret. Though, I suppose that temporal order doesn't particularly matter - it's largely transitive as long as delete-lefts come before text insertion. It does make sense with spatial order, though.
That said... I don't mind changing it... but only if we also change the parameter order in those locations. And I think that's best left to a separate PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay with that refactor being a separate PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tracked as #4529.
// Use it when we're ready to implement that. | ||
// Our .insertText will need to be adjusted accordingly. | ||
_ = Int(fragment[drRange.upperBound...])! | ||
let dr = Int(fragment[drRange.upperBound...])! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
let dr = Int(fragment[drRange.upperBound...])! | |
let numCharsToRightDelete = Int(fragment[drRange.upperBound...])! |
Perhaps could rename numCharsToDelete
to numCharsToLeftDelete
as well?
if(postContextTokenization) { | ||
// Handles display string for reversions triggered by accepting a suggestion mid-token. | ||
revertedPrefix = postContextTokenization.left[postContextTokenization.left.length-1]; | ||
revertedPrefix += postContextTokenization.caretSplitsToken ? postContextTokenization.right[0] : ''; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we be certain that postContextTokenization.right
always contains at least one element?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If postContextTokenization.caretSplitsToken == true
, yes. Otherwise, no.
suggestions.forEach(function(suggestion) { | ||
// A reversion's transform ID is the additive inverse of its original suggestion; | ||
// we revert to the state of said original suggestion. | ||
suggestion.transformId = -reversion.transformId; | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Given this is repeated on linees 565-569, do you want to extract it into a function?
@@ -255,7 +255,7 @@ namespace com.keyman.text.prediction { | |||
// the input will be automatically rewound to the preInput state. | |||
transform: original.transform, | |||
// The ID part is critical; the reversion can't be applied without it. | |||
transformId: original.token, // reversions use the additive inverse. | |||
transformId: -original.token, // reversions use the additive inverse. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm trying to understand the significance of this change. Is it a bug fix -- the comment suggests as such? Or is it just to support the other changes you are making now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Bug fix. I think at some point, the intent was to use the base ID (which already used the additive inverse), but transformID
is the field in active use for ID checks now. Not sure when reversions broke. Anyway, that comment was very important in figuring out why things were broken.
…redictions' into feat/common/models/mid-context-suggestions
Right now, iOS seems covered, even for SMP cases... except for reverting suggestions that were accepted mid-word. (The reversions aren't showing up at the moment; it's related to async [sadly] context-reset ops within the iOS Keyman engine.) Android in-app also still needs work. |
|
Well... thanks, Apple: We were definitely right to be concerned about how clusters would be handled. For those who can't read Khmer script, that's a four-character jump. (Three of them visible.) So, my assumption later in the loop for repositioning the caret is incorrect, causing issues on later loop iterations. |
Okay, I've got a fair bit of the core worked out there, though there's something really weird going on now. There seems to be a desync between the text-manipulation method and what actually gets output - of course, only when right-deletions are happening. So, let's take this as our starting point: I've confirmed via temporary debug-log statements that this, according to the Possibility 1 Uh... that's not what Possibility 2 (Note: these screenshots were taken from a clean context, rather than with the English text present at the start.) Uh... what, mate? Didn't even do anything? Turns out, it actually did. If you hit BKSP, it'll remove the hidden 'subconsonant' marker. Alternatively, if you reselect the same suggestion again... And again... So... the true result incrementally inches closer to the desired suggestion. Wha? Again, note that in both cases, the actual text-manipulation handling itself computes the correct text immediately, and even the There's also the fact that the result isn't even 100% predictable, as noted by the two variations seen above! |
Since the iOS engine is having trouble with right-deletions and a resolution is proving tricky, I've gone ahead and turned off predictive text's right-deletions for now. Instead, any suggestions accepted mid-token will insert a standard word-break afterward. (Note: this is a perfect match for the behavior of iOS's default predictive text; it doesn't right-delete.) I can simply add the right-deletion aspect as a 'feature request' for the future, allowing us to revisit it at another time. I have tried a few other approaches, and one seemed to get remarkably close much of the time... the issue being that it also gave way worse results some of the other times. So... yeah, not changing it over until it's stable. |
Sounds good to me. Have you opened an issue for this yet? |
It's now up as #4538. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM.
Appreciate you going the extra mile on this one -- it's been a bit of a challenge to get it right given the trickiness of the iOS API around right-deletion.
If I was going to be really nitpicky, I'd suggest reverting the whitespace only change in InputViewController.swift so that we have no changes to the Swift code at all, but it's pretty unimportant!
Changes in this pull request will be available for download in Keyman version 14.0.248-beta |
Retest on Android 10 (on both emulator and physical device) based on #4427 (comment):
For any more specific test, ping me again. :) |
Changes in this pull request will be available for download in Keyman version 15.0.19-alpha |
While working on #4411, I noticed that our predictive text has, to this point, often assumed that it will always be operating at the end of the current context. This PR seeks to round out that rough edge and provide support for mid-context scenarios:
Support won't be completely perfect, but it's a definite upgrade from how things were before. The main issue: the caret will always be placed at the end of text affected by a reversion, even if it was originally before some of the reverted characters. (Because a suggestion triggered right-deletions.)
Also note that no post-caret text will actually be used by the predictive text engine, same as before.