From 2c4d8e34e38a152576e9d1715318729b3180cc50 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Thu, 4 Apr 2024 12:00:09 -0500 Subject: [PATCH 01/12] fix(core): skip leading trail surrogate char in km_core_state_context_set_if_needed() --- core/src/km_core_state_context_set_if_needed.cpp | 7 +++++++ core/tests/unit/ldml/test_context_normalization.cpp | 10 +++++----- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/core/src/km_core_state_context_set_if_needed.cpp b/core/src/km_core_state_context_set_if_needed.cpp index fd6106fa7a..7107815249 100644 --- a/core/src/km_core_state_context_set_if_needed.cpp +++ b/core/src/km_core_state_context_set_if_needed.cpp @@ -15,6 +15,7 @@ #include "state.hpp" #include "debuglog.h" #include "core_icu.h" +#include "kmx/kmx_xstring.h" // for Unicode routines using namespace km::core; @@ -59,6 +60,12 @@ km_core_state_context_set_if_needed( return KM_CORE_CONTEXT_STATUS_INVALID_ARGUMENT; } + // if the app context begins with a trailing surrogate, + // skip over it. + if (Uni_IsSurrogate2(*new_app_context)) { + new_app_context++; + } + auto app_context = km_core_state_app_context(state); auto cached_context = km_core_state_context(state); diff --git a/core/tests/unit/ldml/test_context_normalization.cpp b/core/tests/unit/ldml/test_context_normalization.cpp index f9c9a694ff..6a26c198ed 100644 --- a/core/tests/unit/ldml/test_context_normalization.cpp +++ b/core/tests/unit/ldml/test_context_normalization.cpp @@ -109,12 +109,12 @@ void test_context_normalization_hefty() { } void test_context_normalization_invalid_unicode() { - // unpaired surrogate illegal - km_core_cp const application_context[] = { 0xDC01, 0x0020, 0x0020, 0xFFFF, 0x0000 }; - km_core_cp const cached_context[] = { 0xDC01, 0x0020, 0x0020, 0xFFFF, 0x0000 }; + // unpaired surrogate illegal + km_core_cp const application_context[] = { 0xDC01, 0x0020, 0x0020, 0xFFFF, 0x0000 }; + km_core_cp const cached_context[] = {/*skipped*/ 0x0020, 0x0020, 0xFFFF, 0x0000 }; setup("k_001_tiny.kmx"); assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UPDATED); - assert(is_identical_context(application_context, KM_CORE_DEBUG_CONTEXT_APP)); + assert(is_identical_context(application_context+1, KM_CORE_DEBUG_CONTEXT_APP)); // skip first char assert(is_identical_context(cached_context, KM_CORE_DEBUG_CONTEXT_CACHED)); teardown(); } @@ -123,7 +123,7 @@ void test_context_normalization() { test_context_normalization_already_nfd(); test_context_normalization_basic(); test_context_normalization_hefty(); - // TODO: we need to strip illegal chars: test_context_normalization_invalid_unicode(); // -- unpaired surrogate, illegals + test_context_normalization_invalid_unicode(); // -- unpaired surrogate, illegals } //------------------------------------------------------------------------------------- From 3a3d2a81779f852947d6d9d5b5b1d8ffa71c1726 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 2 Apr 2024 16:23:34 +0700 Subject: [PATCH 02/12] change(web): merges split async method in gesture engine --- .../headless/asyncClosureDispatchQueue.ts | 72 ++++++++++--------- 1 file changed, 37 insertions(+), 35 deletions(-) diff --git a/common/web/gesture-recognizer/src/engine/headless/asyncClosureDispatchQueue.ts b/common/web/gesture-recognizer/src/engine/headless/asyncClosureDispatchQueue.ts index bc91297534..a15265cb92 100644 --- a/common/web/gesture-recognizer/src/engine/headless/asyncClosureDispatchQueue.ts +++ b/common/web/gesture-recognizer/src/engine/headless/asyncClosureDispatchQueue.ts @@ -36,51 +36,53 @@ export class AsyncClosureDispatchQueue { return this.queue.length == 0 && !this.waitLock; } - private async setWaitLock(promise: Promise) { - this.waitLock = promise; + private async triggerNextClosure() { + if(this.queue.length == 0) { + return; + } + + const functor = this.queue.shift(); + + // A stand-in so that `ready` doesn't report true while the closure runs. + this.waitLock = Promise.resolve(); + + /* + It is imperative that any errors triggered by the functor do not prevent this method from setting + the wait lock that will trigger the following event (if it exists). Failure to do so will + result in all further queued closures never getting the opportunity to run! + */ + let result: undefined | Promise; + try { + // Is either undefined (return type: void) or is a Promise. + result = functor() as undefined | Promise; + /* c8 ignore start */ + } catch (err) { + reportError('Error from queued closure', err); + } + /* c8 ignore end */ + + /* + Replace the stand-in with the _true_ post-closure wait. + + If the closure returns a Promise, the implication is that the further processing of queued + functions should be blocked until that Promise is fulfilled. + + If not, we just add a default delay. + */ + result = result ?? this.defaultWaitFactory(); + this.waitLock = result; try { - await promise; + await result; } catch(err) { reportError('Async error from queued closure', err); } this.waitLock = null; + // if queue is length zero, auto-returns. this.triggerNextClosure(); } - private async triggerNextClosure() { - if(this.queue.length > 0) { - const functor = this.queue.shift(); - - // A stand-in so that `ready` doesn't report true while the closure runs. - this.waitLock = Promise.resolve(); - - /* - It is imperative that any errors triggered by the functor do not prevent this method from setting - the wait lock that will trigger the following event (if it exists). Failure to do so will - result in all further queued closures never getting the opportunity to run! - */ - let result: undefined | Promise; - try { - // Is either undefined (return type: void) or is a Promise. - result = functor() as undefined | Promise; - /* c8 ignore start */ - } catch (err) { - reportError('Error from queued closure', err); - } - /* c8 ignore end */ - - /* - If the closure returns a Promise, the implication is that the further processing of queued - functions should be blocked until that Promise is fulfilled. - - If not, we still add a delay according to the specified default. - */ - this.setWaitLock(result ?? this.defaultWaitFactory()); - } - } - runAsync(closure: QueueClosure) { // Check before putting the closure on the internal queue; the check is based in part // upon the existing queue length. From bf130e36e08dfbf5731c0e6fb84c38e37126baec Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 5 Apr 2024 14:09:14 +0700 Subject: [PATCH 03/12] fix(web): nearest-key row lookup when touch moves out-of-bounds --- web/src/engine/osk/src/keyboard-layout/oskLayerGroup.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/engine/osk/src/keyboard-layout/oskLayerGroup.ts b/web/src/engine/osk/src/keyboard-layout/oskLayerGroup.ts index 1803269ef7..589ca96f8c 100644 --- a/web/src/engine/osk/src/keyboard-layout/oskLayerGroup.ts +++ b/web/src/engine/osk/src/keyboard-layout/oskLayerGroup.ts @@ -198,7 +198,7 @@ export default class OSKLayerGroup { Assumes there is no fine-tuning of the row ranges to be done - each takes a perfect fraction of the overall layer height without any padding above or below. */ - const rowIndex = Math.floor(proportionalCoords.y * layer.rows.length); + const rowIndex = Math.max(0, Math.min(layer.rows.length-1, Math.floor(proportionalCoords.y * layer.rows.length))); const row = layer.rows[rowIndex]; // Assertion: row no longer `null`. From e43aa3c737a5da29f52f9e221f605fe61795bb23 Mon Sep 17 00:00:00 2001 From: "Steven R. Loomis" Date: Fri, 5 Apr 2024 11:07:07 -0500 Subject: [PATCH 04/12] fix(core): skip leading trail surrogate char - update tests per review --- .../unit/ldml/test_context_normalization.cpp | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/core/tests/unit/ldml/test_context_normalization.cpp b/core/tests/unit/ldml/test_context_normalization.cpp index 6a26c198ed..07fe82fdbd 100644 --- a/core/tests/unit/ldml/test_context_normalization.cpp +++ b/core/tests/unit/ldml/test_context_normalization.cpp @@ -109,12 +109,23 @@ void test_context_normalization_hefty() { } void test_context_normalization_invalid_unicode() { - // unpaired surrogate illegal - km_core_cp const application_context[] = { 0xDC01, 0x0020, 0x0020, 0xFFFF, 0x0000 }; - km_core_cp const cached_context[] = {/*skipped*/ 0x0020, 0x0020, 0xFFFF, 0x0000 }; + // unpaired surrogate illegal + km_core_cp const application_context[] = { 0xDC01, 0x0020, 0x0020, 0xFFFF, 0x0000 }; + km_core_cp const cached_context[] = { 0xDC01, 0x0020, 0x0020, 0xFFFF, 0x0000 }; setup("k_001_tiny.kmx"); assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UPDATED); - assert(is_identical_context(application_context+1, KM_CORE_DEBUG_CONTEXT_APP)); // skip first char + assert(is_identical_context(application_context, KM_CORE_DEBUG_CONTEXT_APP)); + assert(is_identical_context(cached_context, KM_CORE_DEBUG_CONTEXT_CACHED)); + teardown(); +} + +void test_context_normalization_lone_trailing_surrogate() { + // unpaired trail surrogate + km_core_cp const application_context[] = { 0xDC01, 0x0020, 0x0020, 0x0000 }; + km_core_cp const cached_context[] = /* skipped*/ { 0x0020, 0x0020, 0x0000 }; + setup("k_001_tiny.kmx"); + assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UPDATED); + assert(is_identical_context(application_context+1, KM_CORE_DEBUG_CONTEXT_APP)); // first code unit is skipped assert(is_identical_context(cached_context, KM_CORE_DEBUG_CONTEXT_CACHED)); teardown(); } @@ -123,7 +134,8 @@ void test_context_normalization() { test_context_normalization_already_nfd(); test_context_normalization_basic(); test_context_normalization_hefty(); - test_context_normalization_invalid_unicode(); // -- unpaired surrogate, illegals + // TODO: see #10392 we need to strip illegal chars: test_context_normalization_invalid_unicode(); // -- unpaired surrogate, illegals + test_context_normalization_lone_trailing_surrogate(); } //------------------------------------------------------------------------------------- From edd5b54040bbb031b51f7990a39393833e819875 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Fri, 5 Apr 2024 14:05:24 -0400 Subject: [PATCH 05/12] auto: increment beta version to 17.0.304 --- HISTORY.md | 5 +++++ VERSION.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 879970b253..1aa044f802 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,10 @@ # Keyman Version History +## 17.0.303 beta 2024-04-05 + +* fix(windows): decode uri for Package ID and filename (#11152) +* fix(common/models): suggestion stability after multiple whitespaces (#11164) + ## 17.0.302 beta 2024-04-04 * fix(mac): load only 80 characters from context when processing keystrokes (#11141) diff --git a/VERSION.md b/VERSION.md index d43a7b030b..29d39b7194 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -17.0.303 \ No newline at end of file +17.0.304 \ No newline at end of file From 5ca37943121cad9c9ef6fea12d7ddee7e57bbef6 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 8 Apr 2024 09:18:16 +0700 Subject: [PATCH 06/12] chore(web): better ref for spacebar key btn --- web/src/engine/osk/src/keyboard-layout/oskLayer.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web/src/engine/osk/src/keyboard-layout/oskLayer.ts b/web/src/engine/osk/src/keyboard-layout/oskLayer.ts index a721f3ed48..3f73620949 100644 --- a/web/src/engine/osk/src/keyboard-layout/oskLayer.ts +++ b/web/src/engine/osk/src/keyboard-layout/oskLayer.ts @@ -84,12 +84,12 @@ export default class OSKLayer { if(this.spaceBarKey) { const spacebarLabel = this.spaceBarKey.label; - let tParent = spacebarLabel.parentNode; + let tButton = this.spaceBarKey.btn; - if (typeof (tParent.className) == 'undefined' || tParent.className == '') { - tParent.className = 'kmw-spacebar'; - } else if (tParent.className.indexOf('kmw-spacebar') == -1) { - tParent.className += ' kmw-spacebar'; + if (typeof (tButton.className) == 'undefined' || tButton.className == '') { + tButton.className = 'kmw-spacebar'; + } else if (tButton.className.indexOf('kmw-spacebar') == -1) { + tButton.className += ' kmw-spacebar'; } if (spacebarLabel.className != 'kmw-spacebar-caption') { From b6bd677e1410738423c71326e522f52605ee022f Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 8 Apr 2024 13:31:42 +0700 Subject: [PATCH 07/12] fix(android): atomically updates selection with text --- .../java/com/keyman/android/SystemKeyboard.java | 6 ++---- .../main/java/com/keyman/engine/KMKeyboard.java | 17 +++++++++++++++-- .../main/java/com/keyman/engine/KMManager.java | 13 ++++--------- .../main/java/com/keyman/engine/KMTextView.java | 6 ++---- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index e9cab39752..85c14dfcb9 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -130,7 +130,7 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven @Override public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart, candidatesEnd); - KMManager.updateSelectionRange(KMManager.KeyboardType.KEYBOARD_TYPE_SYSTEM, newSelStart, newSelEnd); + KMManager.updateSelectionRange(KMManager.KeyboardType.KEYBOARD_TYPE_SYSTEM); } /** @@ -170,9 +170,7 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven ExtractedText icText = ic.getExtractedText(new ExtractedTextRequest(), 0); if (icText != null) { boolean didUpdateText = KMManager.updateText(KeyboardType.KEYBOARD_TYPE_SYSTEM, icText.text.toString()); - int selStart = icText.startOffset + icText.selectionStart; - int selEnd = icText.startOffset + icText.selectionEnd; - boolean didUpdateSelection = KMManager.updateSelectionRange(KeyboardType.KEYBOARD_TYPE_SYSTEM, selStart, selEnd); + boolean didUpdateSelection = KMManager.updateSelectionRange(KeyboardType.KEYBOARD_TYPE_SYSTEM); if (!didUpdateText || !didUpdateSelection) exText = icText; } diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java index bdd0f4f65c..5381d77dbb 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java @@ -163,7 +163,7 @@ final class KMKeyboard extends WebView { return result; } - protected boolean updateSelectionRange(int selStart, int selEnd) { + protected boolean updateSelectionRange() { boolean result = false; InputConnection ic = KMManager.getInputConnection(this.keyboardType); if (ic != null) { @@ -175,6 +175,17 @@ final class KMKeyboard extends WebView { String rawText = icText.text.toString(); updateText(rawText.toString()); + // To determine: may need `icText.startOffset +`? + int selStart = icText.selectionStart; + int selEnd = icText.selectionEnd; + + int selMin = selStart, selMax = selEnd; + if (selStart > selEnd) { + // Selection is reversed so "swap" + selMin = selEnd; + selMax = selStart; + } + /* The values of selStart & selEnd provided by the system are in code units, not code-points. We need to account for surrogate pairs here. @@ -187,14 +198,16 @@ final class KMKeyboard extends WebView { */ // Count the number of characters which are surrogate pairs. + int stringLen = rawText.length(); + int pairsAtStart = CharSequenceUtil.countSurrogatePairs(rawText.substring(0, selStart), rawText.length()); String selectedText = rawText.substring(selStart, selEnd); int pairsSelected = CharSequenceUtil.countSurrogatePairs(selectedText, selectedText.length()); selStart -= pairsAtStart; selEnd -= (pairsAtStart + pairsSelected); + this.loadJavascript(KMString.format("updateKMSelectionRange(%d,%d)", selStart, selEnd)); } - this.loadJavascript(KMString.format("updateKMSelectionRange(%d,%d)", selStart, selEnd)); result = true; return result; diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index c113ab313d..50365aaba0 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -2137,23 +2137,18 @@ public final class KMManager { return result; } - public static boolean updateSelectionRange(KeyboardType kbType, int selStart, int selEnd) { + public static boolean updateSelectionRange(KeyboardType kbType) { boolean result = false; - int selMin = selStart, selMax = selEnd; - if (selStart > selEnd) { - // Selection is reversed so "swap" - selMin = selEnd; - selMax = selStart; - } + if (kbType == KeyboardType.KEYBOARD_TYPE_INAPP) { if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_INAPP) && !InAppKeyboard.shouldIgnoreSelectionChange()) { - result = InAppKeyboard.updateSelectionRange(selMin, selMax); + result = InAppKeyboard.updateSelectionRange(); } InAppKeyboard.setShouldIgnoreSelectionChange(false); } else if (kbType == KeyboardType.KEYBOARD_TYPE_SYSTEM) { if (isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM) && !SystemKeyboard.shouldIgnoreSelectionChange()) { - result = SystemKeyboard.updateSelectionRange(selMin, selMax); + result = SystemKeyboard.updateSelectionRange(); } SystemKeyboard.setShouldIgnoreSelectionChange(false); diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMTextView.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMTextView.java index 70774a4e2c..3c5959fc9d 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMTextView.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMTextView.java @@ -63,10 +63,8 @@ public final class KMTextView extends AppCompatEditText { */ public static void updateTextContext() { KMTextView textView = (KMTextView) activeView; - int selStart = textView.getSelectionStart(); - int selEnd = textView.getSelectionEnd(); KMManager.updateText(KeyboardType.KEYBOARD_TYPE_INAPP, textView.getText().toString()); - if (KMManager.updateSelectionRange(KeyboardType.KEYBOARD_TYPE_INAPP, selStart, selEnd)) { + if (KMManager.updateSelectionRange(KeyboardType.KEYBOARD_TYPE_INAPP)) { KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_INAPP); } } @@ -167,7 +165,7 @@ public final class KMTextView extends AppCompatEditText { protected void onSelectionChanged(int selStart, int selEnd) { super.onSelectionChanged(selStart, selEnd); if (activeView != null && activeView.equals(this)) { - if (KMManager.updateSelectionRange(KMManager.KeyboardType.KEYBOARD_TYPE_INAPP, selStart, selEnd)) { + if (KMManager.updateSelectionRange(KMManager.KeyboardType.KEYBOARD_TYPE_INAPP)) { KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_INAPP); } } From 37b8c1ccb7bd6e0a672350760c8ad59a69cd33f3 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 8 Apr 2024 13:32:51 +0700 Subject: [PATCH 08/12] docs(android): cleans in-dev comment --- android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java | 1 - 1 file changed, 1 deletion(-) diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java index 5381d77dbb..d781e54452 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java @@ -175,7 +175,6 @@ final class KMKeyboard extends WebView { String rawText = icText.text.toString(); updateText(rawText.toString()); - // To determine: may need `icText.startOffset +`? int selStart = icText.selectionStart; int selEnd = icText.selectionEnd; From 75bde80a054df1c0eaee61e9bfad4ed030b6cb2b Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 8 Apr 2024 14:44:32 +0700 Subject: [PATCH 09/12] fix(web): quick multitap-modipress use --- .../gestures/matchers/gestureMatcher.ts | 28 +++++++++++-------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts b/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts index 4257ec8c25..8a3c14fc6e 100644 --- a/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts +++ b/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts @@ -157,14 +157,14 @@ export class GestureMatcher implements PredecessorMatch< return; case 'full': contact = srcContact.constructSubview(false, true); - this.addContactInternal(contact, srcContact.path.stats); + this.addContactInternal(contact, srcContact.path.stats, true); continue; case 'partial': preserveBaseItem = true; // Intentional fall-through case 'chop': contact = srcContact.constructSubview(true, preserveBaseItem); - this.addContactInternal(contact, srcContact.path.stats); + this.addContactInternal(contact, srcContact.path.stats, true); break; } } @@ -367,7 +367,7 @@ export class GestureMatcher implements PredecessorMatch< return this._result; } - private addContactInternal(simpleSource: GestureSourceSubview, basePathStats: CumulativePathStats) { + private addContactInternal(simpleSource: GestureSourceSubview, basePathStats: CumulativePathStats, whileInitializing?: boolean) { // The number of already-active contacts tracked for this gesture const existingContacts = this.pathMatchers.length; @@ -491,16 +491,22 @@ export class GestureMatcher implements PredecessorMatch< const result = contactModel.update(); if(result?.type == 'reject') { /* - Refer to the earlier comment in this method re: use of 'cancelled'; we need to - prevent any and all further attempts to match against this model We'd - instantly reject it anyway due to its rejected initial state. Failing to do so - can cause an infinite async loop. + Refer to the earlier comment in this method re: use of 'cancelled'; we + need to prevent any and all further attempts to match against this model + We'd instantly reject it anyway due to its rejected initial state. + Failing to do so can cause an infinite async loop. - If we weren't using 'cancelled', 'path' would correspond best with a rejection - here, as the decision is made due to the GestureSource's current path being - rejected by one of the `PathModel`s comprising the `GestureModel`. + If we weren't using 'cancelled', 'path' would correspond best with a + rejection here, as the decision is made due to the GestureSource's + current path being rejected by one of the `PathModel`s comprising the + `GestureModel`. + + If the model's already been initialized, it's possible that a _new_ + incoming touch needs special handling. We'll allow one reset. In the + case that it would try to restart itself, the restarted model will + instantly fail and thus cancel. */ - this.finalize(false, 'cancelled'); + this.finalize(false, whileInitializing ? 'cancelled' : 'path'); } // Standard path: trigger either resolution or rejection when the contact model signals either. From 522d929522512ea446bfa00f22072306a31b741c Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 8 Apr 2024 15:10:33 +0700 Subject: [PATCH 10/12] fix(android): restores API method, deprecates + docs old form --- .../java/com/keyman/engine/KMManager.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java index 50365aaba0..b31fe42506 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMManager.java @@ -2137,6 +2137,28 @@ public final class KMManager { return result; } + /** + * Updates the active range for selected text. + * @deprecated + * This method no longer needs the `selStart` and `selEnd` parameters. + *

Use {@link KMManager#updateSelectionRange(KeyboardType)} instead.

+ * + * @param kbType A value indicating if this request is for the in-app keyboard or the system keyboard + * @param selStart (deprecated) the start index for the range + * @param selEnd (deprecated) the end index for the selected range + * @return + */ + @Deprecated + public static boolean updateSelectionRange(KeyboardType kbType, int selStart, int selEnd) { + return updateSelectionRange(kbType); + } + + /** + * Performs a synchronization check for the active range for selected text, + * ensuring it matches the text-editor's current state. + * @param kbType A value indicating if this request is for the in-app or system keyboard. + * @return + */ public static boolean updateSelectionRange(KeyboardType kbType) { boolean result = false; From 9a1ef32e1567beb478fac787648aca341f820fe3 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 9 Apr 2024 08:30:35 +0700 Subject: [PATCH 11/12] chore(android): removal of new but unused var --- .../KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java index d781e54452..a356a20ad5 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java @@ -197,8 +197,6 @@ final class KMKeyboard extends WebView { */ // Count the number of characters which are surrogate pairs. - int stringLen = rawText.length(); - int pairsAtStart = CharSequenceUtil.countSurrogatePairs(rawText.substring(0, selStart), rawText.length()); String selectedText = rawText.substring(selStart, selEnd); int pairsSelected = CharSequenceUtil.countSurrogatePairs(selectedText, selectedText.length()); From a0b7ede32af9ef4bc98334e718ece9fb47c3ac1b Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Tue, 9 Apr 2024 14:04:23 -0400 Subject: [PATCH 12/12] auto: increment beta version to 17.0.305 --- HISTORY.md | 5 +++++ VERSION.md | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index 1aa044f802..80aba04e02 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,10 @@ # Keyman Version History +## 17.0.304 beta 2024-04-09 + +* fix(android): atomically updates selection with text (#11188) +* (#11178) + ## 17.0.303 beta 2024-04-05 * fix(windows): decode uri for Package ID and filename (#11152) diff --git a/VERSION.md b/VERSION.md index 29d39b7194..f9d9a9bbd8 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -17.0.304 \ No newline at end of file +17.0.305 \ No newline at end of file