Skip to content
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

epic: autocorrect 🚂 #12893

Draft
wants to merge 14 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,11 @@ public void onCreate(Bundle savedInstanceState) {
RadioGroup radioGroup = (RadioGroup) findViewById(R.id.suggestion_radio_group);
radioGroup.clearCheck();

// Auto-correct disabled for Keyman 18.0 #12767
int[] RadioButtonArray = {
R.id.suggestion_radio_0,
R.id.suggestion_radio_1,
R.id.suggestion_radio_2};
R.id.suggestion_radio_2,
R.id.suggestion_radio_3};
RadioButton radioButton = (RadioButton)radioGroup.findViewById(RadioButtonArray[maySuggest]);
radioButton.setChecked(true);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,12 @@
android:layout_gravity="center_vertical"
android:text="@string/suggestions_radio_2" />

<!-- Auto-correct disabled for Keyman 18.0 #12767 -->
<!--com.google.android.material.radiobutton.MaterialRadioButton
<com.google.android.material.radiobutton.MaterialRadioButton
android:id="@+id/suggestion_radio_3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:text="@string/suggestions_radio_3" /-->
android:text="@string/suggestions_radio_3" />

</RadioGroup>

Expand Down
2 changes: 1 addition & 1 deletion web/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Keyman Engine for Web
The Original Code is (C) SIL International
The Original Code is (C) SIL Global

## Prerequisites
See [build configuration](../docs/build/index.md) for details on how to
Expand Down
2 changes: 1 addition & 1 deletion web/src/engine/main/src/headless/languageProcessor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class LanguageProcessor extends EventEmitter<LanguageProcessorEventMap> {

private _mayPredict: boolean = true;
private _mayCorrect: boolean = true;
private _mayAutoCorrect: boolean = false; // initialized to false - #12767
private _mayAutoCorrect: boolean = true;

private _state: StateChangeEnum = 'inactive';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export interface DefaultWordBreakerOptions {
* @see http://unicode.org/reports/tr29/#Word_Boundaries
* @see https://github.com/eddieantonio/unicode-default-word-boundary/tree/v12.0.0
*/
export default function default_(text: string, options?: DefaultWordBreakerOptions): LexicalModelTypes.Span[] {
function default_(text: string, options?: DefaultWordBreakerOptions): LexicalModelTypes.Span[] {
let boundaries = findBoundaries(text, options);
if (boundaries.length == 0) {
return [];
Expand All @@ -64,6 +64,16 @@ export default function default_(text: string, options?: DefaultWordBreakerOptio
return spans;
}

// Exposes `searchForProperty` for external use while associating it with this wordbreaker.
const def = Object.assign(default_, {
/**
* This method returns enum values corresponding to the character type as perceived by the wordbreaking algorithm.
*/
searchForProperty: searchForProperty
});

export default def;

/**
* A span that does not cut out the substring until it absolutely has to!
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import placeholder from "./placeholder.js";
import ascii from "./ascii.js";
import default_ from "./default/index.js";
import { WordBreakProperty } from "./default/data.inc.js";

export { placeholder, ascii, default_ as default, default_ as defaultWordbreaker };
export { placeholder, ascii, default_ as default, default_ as defaultWordbreaker, WordBreakProperty };
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import { ContextTracker, TrackedContextState } from './correction/context-tracke
import { ExecutionTimer } from './correction/execution-timer.js';
import ModelCompositor from './model-compositor.js';
import { LexicalModelTypes } from '@keymanapp/common-types';
import { defaultWordbreaker, WordBreakProperty } from '@keymanapp/models-wordbreakers';
const searchForProperty = defaultWordbreaker.searchForProperty;

import Context = LexicalModelTypes.Context;
import Distribution = LexicalModelTypes.Distribution;
import Keep = LexicalModelTypes.Keep;
Expand Down Expand Up @@ -615,6 +618,35 @@ export function processSimilarity(
});
}

/**
* This function may be used to prevent auto-selection/auto-correct from applying in
* unexpected ways. For example, when typing numbers in English, we don't expect
* '5' to auto-correct to '5th' just because there are no pure-number entries in
* the lexicon rooted on '5'.
* @param correction
* @returns
*/
export function correctionValidForAutoSelect(correction: string) {
let chars = [...correction];

// If the _correction_ - the actual, existing text - does not include any letters,
// then predictions built upon it should not be considered valid for auto-correction.
for(let c of chars) {
// Found even one letter? We'll consider it valid.
switch(searchForProperty(c.codePointAt(0))) {
case WordBreakProperty.ALetter:
case WordBreakProperty.Hebrew_Letter:
case WordBreakProperty.Katakana:
return true;
default:
}
}

// Only reached when the correction has nothing that passes as a letter in-context.
// (MidLet and MidNumLet only count when there are adjacent letters.)
return false;
}

export function predictionAutoSelect(suggestionDistribution: CorrectionPredictionTuple[]) {
if(suggestionDistribution.length == 0) {
return;
Expand All @@ -633,6 +665,11 @@ export function predictionAutoSelect(suggestionDistribution: CorrectionPredictio
suggestionDistribution = suggestionDistribution.slice(1);

if(suggestionDistribution.length == 1) {
// Prevent auto-acceptance when the root doesn't meet validation criteria.
if(!correctionValidForAutoSelect(suggestionDistribution[0].correction.sample)) {
return;
}

// Mark for auto-acceptance; there are no alternatives.
suggestionDistribution[0].prediction.sample.autoAccept = true;
return;
Expand Down Expand Up @@ -676,6 +713,10 @@ export function predictionAutoSelect(suggestionDistribution: CorrectionPredictio
return;
}

if(!correctionValidForAutoSelect(bestSuggestion.correction.sample)) {
return;
}

// compare correction-cost aspects? We disable if the base correction is lower than best,
// but should we do other comparisons too?

Expand Down
Loading