From 2c1c46e0ddd585acc1ba714f631c4fbc0c78452d Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Tue, 12 Aug 2025 08:58:06 -0500 Subject: [PATCH] feat(web): add Web engine support for auto-reverting whitespace appended to Suggestions on punctuation input Relates-to: #7163 Relates-to: #12013 This does not outright _fix_ them because we still need to add the ability to set language-specific punctuation mark sets within the model, and the model needs to use those to return an appropriate configuration. (This commit sets defaults that are English-centric and do not generalize to all languages.) --- common/web/types/src/lexical-model-types.ts | 24 ++- web/build.sh | 7 +- .../prediction/languageProcessor.interface.ts | 2 +- .../src/prediction/predictionContext.ts | 43 ++++-- .../main/src/headless/inputProcessor.ts | 104 ++++++++++++- .../main/src/headless/languageProcessor.ts | 139 ++++++++++-------- web/src/engine/main/src/keymanEngineBase.ts | 4 +- .../worker-main/src/lmlayer.ts | 5 +- .../src/main/correction/context-state.ts | 19 ++- .../src/main/correction/context-tracker.ts | 2 +- .../src/main/correction/context-transition.ts | 30 +++- .../worker-thread/src/main/index.ts | 12 +- .../src/main/model-compositor.ts | 30 +++- .../src/main/worker-interfaces.ts | 6 + .../context/context-tracker.tests.ts | 11 +- .../context/context-transition.tests.ts | 42 +++++- .../worker-model-compositor.tests.ts | 14 +- 17 files changed, 351 insertions(+), 143 deletions(-) diff --git a/common/web/types/src/lexical-model-types.ts b/common/web/types/src/lexical-model-types.ts index 9938abec5a..37a761608d 100644 --- a/common/web/types/src/lexical-model-types.ts +++ b/common/web/types/src/lexical-model-types.ts @@ -531,14 +531,28 @@ export interface Configuration { rightContextCodeUnits?: number, /** - * Whether or not the model appends characters to Suggestions for - * wordbreaking purposes. (These characters need not be whitespace - * or actual wordbreak characters.) + * Specifies behaviors related to transforms that the active model appends + * to Suggestions for wordbreaking purposes. (The Transforms need not apply + * whitespace or actual wordbreak characters.) * * If not specified, this will be auto-detected based on the model's - * punctuation properties (if they exist). + * punctuation properties (if they exist). If left null/undefined, the model + * does not append wordbreaking transforms to Suggestions. */ - wordbreaksAfterSuggestions?: boolean + appendsWordbreaks?: { + /** + * Specifies strings that, when input, always act as word-boundaries on the + * input - both when typed after a manually-applied suggestion (replacing + * appended whitespace) and when typed with an auto-selected suggestion + * available (thus accepting it directly, as with whitespace). + * + * This is designed to allow language-appropriate punctuation marks to + * automatically remove whitespace (or other wordbreak characters) as + * appropriate, and to auto-accept for inputs that clearly signal intent + * to end the current word, both in order to improve user UX with autocorrect. + */ + breakingMarks?: string[]; + } } diff --git a/web/build.sh b/web/build.sh index 4cb01f840e..ebc82b9c6c 100755 --- a/web/build.sh +++ b/web/build.sh @@ -166,6 +166,10 @@ builder_run_child_actions build:engine/keyboard builder_run_child_actions build:engine/js-processor builder_run_child_actions build:engine/element-wrappers builder_run_child_actions build:engine/events + +# Builds the predictive-text components +builder_run_child_actions build:engine/predictive-text + builder_run_child_actions build:engine/interfaces # Uses engine/dom-utils and engine/interfaces @@ -177,9 +181,6 @@ builder_run_child_actions build:engine/attachment # Uses engine/interfaces (due to resource-path config interface) builder_run_child_actions build:engine/keyboard-storage -# Builds the predictive-text components -builder_run_child_actions build:engine/predictive-text - # Uses engine/interfaces, engine/keyboard-storage, engine/predictive-text, & engine/osk builder_run_child_actions build:engine/main diff --git a/web/src/engine/interfaces/src/prediction/languageProcessor.interface.ts b/web/src/engine/interfaces/src/prediction/languageProcessor.interface.ts index eafdac2d39..381fbed011 100644 --- a/web/src/engine/interfaces/src/prediction/languageProcessor.interface.ts +++ b/web/src/engine/interfaces/src/prediction/languageProcessor.interface.ts @@ -70,7 +70,7 @@ export interface LanguageProcessorSpec extends EventEmitter; - get wordbreaksAfterSuggestions(): boolean; + get wordbreaksAfterSuggestions(): LexicalModelTypes.Configuration['appendsWordbreaks']; get mayAutoCorrect(): boolean; } diff --git a/web/src/engine/interfaces/src/prediction/predictionContext.ts b/web/src/engine/interfaces/src/prediction/predictionContext.ts index 902566eed2..c6eba250eb 100644 --- a/web/src/engine/interfaces/src/prediction/predictionContext.ts +++ b/web/src/engine/interfaces/src/prediction/predictionContext.ts @@ -23,11 +23,24 @@ export default class PredictionContext extends EventEmitter; private swallowPrediction: boolean = false; @@ -161,8 +174,6 @@ export default class PredictionContext extends EventEmitter`; else, `null`. */ public accept(suggestion: Suggestion): Promise | Promise { - const _this = this; - // Selecting a suggestion or a reversion should both clear selection // and clear the reversion-displaying state of the banner. this.selected = null; @@ -173,23 +184,23 @@ export default class PredictionContext extends EventEmitter { // Always null-check! if(suggestion) { - _this.revertSuggestion = suggestion; + this._revertSuggestion = suggestion; } }); // By default, we assume we were triggered by the banner. // Acceptance by keystroke will overwrite this later (in `tryAccept`) - this.recentAcceptCause = 'banner'; + this._recentAcceptCause = 'banner'; this.recentRevert = false; this.swallowPrediction = true; @@ -219,9 +230,9 @@ export default class PredictionContext extends EventEmitter