From 4ed88d8fa6295104bd00a01bf7c9c3be1740c85c Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 24 May 2024 15:41:10 +0700 Subject: [PATCH 1/9] refactor(developer): move developer-utils files into src/ and sub-folder In preparation for moving source file types into developer-utils, move the existing developer-utils files into src/ and src/utils subfolders. --- developer/src/common/web/utils/index.ts | 5 ----- developer/src/common/web/utils/package.json | 2 +- developer/src/common/web/utils/src/index.ts | 6 ++++++ .../src/common/web/utils/src/{ => utils}/KeymanSentry.ts | 0 .../src/common/web/utils/src/{ => utils}/keyman-urls.ts | 0 developer/src/common/web/utils/src/{ => utils}/markdown.ts | 0 developer/src/common/web/utils/src/{ => utils}/options.ts | 0 .../web/utils/src/{ => utils}/validate-mit-license.ts | 0 developer/src/common/web/utils/test/test-license.ts | 2 +- developer/src/common/web/utils/tsconfig.json | 1 - 10 files changed, 8 insertions(+), 8 deletions(-) delete mode 100644 developer/src/common/web/utils/index.ts create mode 100644 developer/src/common/web/utils/src/index.ts rename developer/src/common/web/utils/src/{ => utils}/KeymanSentry.ts (100%) rename developer/src/common/web/utils/src/{ => utils}/keyman-urls.ts (100%) rename developer/src/common/web/utils/src/{ => utils}/markdown.ts (100%) rename developer/src/common/web/utils/src/{ => utils}/options.ts (100%) rename developer/src/common/web/utils/src/{ => utils}/validate-mit-license.ts (100%) diff --git a/developer/src/common/web/utils/index.ts b/developer/src/common/web/utils/index.ts deleted file mode 100644 index 84651750639..00000000000 --- a/developer/src/common/web/utils/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { validateMITLicense } from './src/validate-mit-license.js'; -export { KeymanSentry, SentryNodeOptions } from './src/KeymanSentry.js'; -export { getOption, loadOptions, clearOptions } from './src/options.js'; -export { escapeMarkdownChar } from './src/markdown.js'; -export { KeymanUrls } from './src/keyman-urls.js'; \ No newline at end of file diff --git a/developer/src/common/web/utils/package.json b/developer/src/common/web/utils/package.json index b93cca8efa3..ad3b796a215 100644 --- a/developer/src/common/web/utils/package.json +++ b/developer/src/common/web/utils/package.json @@ -3,7 +3,7 @@ "description": "Keyman Developer utilities", "type": "module", "exports": { - ".": "./build/index.js" + ".": "./build/src/index.js" }, "files": [ "/build/" diff --git a/developer/src/common/web/utils/src/index.ts b/developer/src/common/web/utils/src/index.ts new file mode 100644 index 00000000000..5da5e4c48c7 --- /dev/null +++ b/developer/src/common/web/utils/src/index.ts @@ -0,0 +1,6 @@ +export { validateMITLicense } from './utils/validate-mit-license.js'; +export { KeymanSentry, SentryNodeOptions } from './utils/KeymanSentry.js'; +export { getOption, loadOptions, clearOptions } from './utils/options.js'; +export { escapeMarkdownChar } from './utils/markdown.js'; +export { KeymanUrls } from './utils/keyman-urls.js'; + diff --git a/developer/src/common/web/utils/src/KeymanSentry.ts b/developer/src/common/web/utils/src/utils/KeymanSentry.ts similarity index 100% rename from developer/src/common/web/utils/src/KeymanSentry.ts rename to developer/src/common/web/utils/src/utils/KeymanSentry.ts diff --git a/developer/src/common/web/utils/src/keyman-urls.ts b/developer/src/common/web/utils/src/utils/keyman-urls.ts similarity index 100% rename from developer/src/common/web/utils/src/keyman-urls.ts rename to developer/src/common/web/utils/src/utils/keyman-urls.ts diff --git a/developer/src/common/web/utils/src/markdown.ts b/developer/src/common/web/utils/src/utils/markdown.ts similarity index 100% rename from developer/src/common/web/utils/src/markdown.ts rename to developer/src/common/web/utils/src/utils/markdown.ts diff --git a/developer/src/common/web/utils/src/options.ts b/developer/src/common/web/utils/src/utils/options.ts similarity index 100% rename from developer/src/common/web/utils/src/options.ts rename to developer/src/common/web/utils/src/utils/options.ts diff --git a/developer/src/common/web/utils/src/validate-mit-license.ts b/developer/src/common/web/utils/src/utils/validate-mit-license.ts similarity index 100% rename from developer/src/common/web/utils/src/validate-mit-license.ts rename to developer/src/common/web/utils/src/utils/validate-mit-license.ts diff --git a/developer/src/common/web/utils/test/test-license.ts b/developer/src/common/web/utils/test/test-license.ts index 653f89ba69c..501f4f4711e 100644 --- a/developer/src/common/web/utils/test/test-license.ts +++ b/developer/src/common/web/utils/test/test-license.ts @@ -2,7 +2,7 @@ import * as fs from 'fs'; import { assert } from 'chai'; import 'mocha'; import { makePathToFixture } from './helpers/index.js'; -import { validateMITLicense } from '../src/validate-mit-license.js'; +import { validateMITLicense } from '../src/utils/validate-mit-license.js'; function verifyLicenseFile(filename: string) { return validateMITLicense(fs.readFileSync(makePathToFixture('license', filename), 'utf-8')); diff --git a/developer/src/common/web/utils/tsconfig.json b/developer/src/common/web/utils/tsconfig.json index c5980c82f9c..fa41799c2e6 100644 --- a/developer/src/common/web/utils/tsconfig.json +++ b/developer/src/common/web/utils/tsconfig.json @@ -7,7 +7,6 @@ "baseUrl": ".", }, "include": [ - "index.ts", "src/**/*.ts", ], } \ No newline at end of file From 9cf09a5140cc828e9bc79764a47f584e6d59787b Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 24 May 2024 15:42:42 +0700 Subject: [PATCH 2/9] refactor(common): move kpj-related files into developer-utils Relates to #9665. --- common/web/types/src/main.ts | 4 ---- developer/src/common/web/utils/src/index.ts | 3 +++ .../src/types}/kpj/keyman-developer-project.ts | 3 +-- .../web/utils/src/types}/kpj/kpj-file-reader.ts | 13 ++++++------- .../src/common/web/utils/src/types}/kpj/kpj-file.ts | 0 .../web/utils}/test/kpj/test-kpj-file-reader.ts | 6 +++--- .../kmc/src/commands/buildClasses/BuildProject.ts | 3 ++- developer/src/kmc/src/util/projectLoader.ts | 3 ++- 8 files changed, 17 insertions(+), 18 deletions(-) rename {common/web/types/src => developer/src/common/web/utils/src/types}/kpj/keyman-developer-project.ts (98%) rename {common/web/types/src => developer/src/common/web/utils/src/types}/kpj/kpj-file-reader.ts (92%) rename {common/web/types/src => developer/src/common/web/utils/src/types}/kpj/kpj-file.ts (100%) rename {common/web/types => developer/src/common/web/utils}/test/kpj/test-kpj-file-reader.ts (97%) diff --git a/common/web/types/src/main.ts b/common/web/types/src/main.ts index 3d18812876c..f2f91cba181 100644 --- a/common/web/types/src/main.ts +++ b/common/web/types/src/main.ts @@ -43,10 +43,6 @@ export * as TouchLayout from './keyman-touch-layout/keyman-touch-layout-file.js' export { TouchLayoutFileReader } from './keyman-touch-layout/keyman-touch-layout-file-reader.js'; export { TouchLayoutFileWriter, TouchLayoutFileWriterOptions } from './keyman-touch-layout/keyman-touch-layout-file-writer.js'; -export * as KPJ from './kpj/kpj-file.js'; -export { KPJFileReader } from './kpj/kpj-file-reader.js'; -export { KeymanDeveloperProject, KeymanDeveloperProjectFile, KeymanDeveloperProjectType, } from './kpj/keyman-developer-project.js'; - export * as KpsFile from './package/kps-file.js'; export * as KmpJsonFile from './package/kmp-json-file.js'; diff --git a/developer/src/common/web/utils/src/index.ts b/developer/src/common/web/utils/src/index.ts index 5da5e4c48c7..bfbb6d97c52 100644 --- a/developer/src/common/web/utils/src/index.ts +++ b/developer/src/common/web/utils/src/index.ts @@ -4,3 +4,6 @@ export { getOption, loadOptions, clearOptions } from './utils/options.js'; export { escapeMarkdownChar } from './utils/markdown.js'; export { KeymanUrls } from './utils/keyman-urls.js'; +export * as KPJ from './types/kpj/kpj-file.js'; +export { KPJFileReader } from './types/kpj/kpj-file-reader.js'; +export { KeymanDeveloperProject, KeymanDeveloperProjectFile, KeymanDeveloperProjectType, } from './types/kpj/keyman-developer-project.js'; diff --git a/common/web/types/src/kpj/keyman-developer-project.ts b/developer/src/common/web/utils/src/types/kpj/keyman-developer-project.ts similarity index 98% rename from common/web/types/src/kpj/keyman-developer-project.ts rename to developer/src/common/web/utils/src/types/kpj/keyman-developer-project.ts index 76f3ab709a2..7ddcb43c183 100644 --- a/common/web/types/src/kpj/keyman-developer-project.ts +++ b/developer/src/common/web/utils/src/types/kpj/keyman-developer-project.ts @@ -2,8 +2,7 @@ // Version 1.0 and 2.0 of Keyman Developer Project .kpj file // -import { KeymanFileTypes } from '../main.js'; -import { CompilerCallbacks } from '../util/compiler-interfaces.js'; +import { CompilerCallbacks, KeymanFileTypes } from '@keymanapp/common-types'; export class KeymanDeveloperProject { options: KeymanDeveloperProjectOptions; diff --git a/common/web/types/src/kpj/kpj-file-reader.ts b/developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts similarity index 92% rename from common/web/types/src/kpj/kpj-file-reader.ts rename to developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts index eb2a168a48c..a0ef422b01a 100644 --- a/common/web/types/src/kpj/kpj-file-reader.ts +++ b/developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts @@ -1,9 +1,8 @@ import * as xml2js from '../deps/xml2js/xml2js.js'; import { KPJFile, KPJFileProject } from './kpj-file.js'; -import { boxXmlArray } from '../util/util.js'; +import { util } from '@keymanapp/common-types'; import { KeymanDeveloperProject, KeymanDeveloperProjectFile10, KeymanDeveloperProjectType } from './keyman-developer-project.js'; -import { CompilerCallbacks } from '../util/compiler-interfaces.js'; -import SchemaValidators from '../schema-validators.js'; +import { CompilerCallbacks, SchemaValidators } from '@keymanapp/common-types'; export class KPJFileReader { constructor(private callbacks: CompilerCallbacks) { @@ -40,11 +39,11 @@ export class KPJFileReader { } public validate(source: KPJFile): void { - if(!SchemaValidators.kpj(source)) { - if(!SchemaValidators.kpj90(source)) { + if(!SchemaValidators.default.kpj(source)) { + if(!SchemaValidators.default.kpj90(source)) { // If the legacy schema also does not validate, then we will only report // the errors against the modern schema - throw new Error(JSON.stringify((SchemaValidators.kpj).errors)); + throw new Error(JSON.stringify((SchemaValidators.default.kpj).errors)); } } } @@ -123,7 +122,7 @@ export class KPJFileReader { if(!source.KeymanDeveloperProject.Files || typeof source.KeymanDeveloperProject.Files == 'string') { source.KeymanDeveloperProject.Files = {File:[]}; } - boxXmlArray(source.KeymanDeveloperProject.Files, 'File'); + util.boxXmlArray(source.KeymanDeveloperProject.Files, 'File'); return source; } } \ No newline at end of file diff --git a/common/web/types/src/kpj/kpj-file.ts b/developer/src/common/web/utils/src/types/kpj/kpj-file.ts similarity index 100% rename from common/web/types/src/kpj/kpj-file.ts rename to developer/src/common/web/utils/src/types/kpj/kpj-file.ts diff --git a/common/web/types/test/kpj/test-kpj-file-reader.ts b/developer/src/common/web/utils/test/kpj/test-kpj-file-reader.ts similarity index 97% rename from common/web/types/test/kpj/test-kpj-file-reader.ts rename to developer/src/common/web/utils/test/kpj/test-kpj-file-reader.ts index cbebdd79207..4b154af0606 100644 --- a/common/web/types/test/kpj/test-kpj-file-reader.ts +++ b/developer/src/common/web/utils/test/kpj/test-kpj-file-reader.ts @@ -2,9 +2,9 @@ import * as fs from 'fs'; import 'mocha'; import {assert} from 'chai'; import { makePathToFixture } from '../helpers/index.js'; -import { KPJFileReader } from "../../src/kpj/kpj-file-reader.js"; -import { KeymanDeveloperProjectFile10, KeymanDeveloperProjectType } from '../../src/kpj/keyman-developer-project.js'; -import { TestCompilerCallbacks } from '../helpers/TestCompilerCallbacks.js'; +import { KPJFileReader } from "../../src/types/kpj/kpj-file-reader.js"; +import { KeymanDeveloperProjectFile10, KeymanDeveloperProjectType } from '../../src/types/kpj/keyman-developer-project.js'; +import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; const callbacks = new TestCompilerCallbacks(); diff --git a/developer/src/kmc/src/commands/buildClasses/BuildProject.ts b/developer/src/kmc/src/commands/buildClasses/BuildProject.ts index 4a5f9148bc6..ccdb85b6a2e 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildProject.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildProject.ts @@ -1,6 +1,7 @@ import * as path from 'path'; import * as fs from 'fs'; -import { CompilerCallbacks, CompilerFileCallbacks, KeymanDeveloperProject, KeymanDeveloperProjectFile, KeymanDeveloperProjectType, KeymanFileTypes } from '@keymanapp/common-types'; +import { CompilerCallbacks, CompilerFileCallbacks, KeymanFileTypes } from '@keymanapp/common-types'; +import { KeymanDeveloperProject, KeymanDeveloperProjectFile, KeymanDeveloperProjectType } from '@keymanapp/developer-utils'; import { BuildActivity } from './BuildActivity.js'; import { buildActivities, buildKeyboardInfoActivity, buildModelInfoActivity } from './buildActivities.js'; import { InfrastructureMessages } from '../../messages/infrastructureMessages.js'; diff --git a/developer/src/kmc/src/util/projectLoader.ts b/developer/src/kmc/src/util/projectLoader.ts index b26399ee395..1900008f306 100644 --- a/developer/src/kmc/src/util/projectLoader.ts +++ b/developer/src/kmc/src/util/projectLoader.ts @@ -1,7 +1,8 @@ import * as path from 'path'; import * as fs from 'fs'; -import { CompilerCallbacks, KeymanDeveloperProject, KeymanFileTypes, KPJFileReader } from "@keymanapp/common-types"; +import { CompilerCallbacks, KeymanFileTypes } from "@keymanapp/common-types"; +import { KeymanDeveloperProject, KPJFileReader } from "@keymanapp/developer-utils"; import { InfrastructureMessages } from "../messages/infrastructureMessages.js"; export const isProject = (filename: string): boolean => From 74e2a8ccead3e9b27b5b684b777a03d63d57a856 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 24 May 2024 15:46:47 +0700 Subject: [PATCH 3/9] refactor(common): move kpj test fixtures and cleanup post-move Moves the kpj test fixtures into developer-utils, moves from let to const in code where required by eslint, and reduces the coverage threshold in order to get tests to pass for developer-utils. --- developer/src/common/web/utils/build.sh | 7 +++++-- .../utils/src/types/kpj/keyman-developer-project.ts | 12 ++++++------ .../web/utils/src/types/kpj/kpj-file-reader.ts | 12 ++++++------ .../web/utils}/test/fixtures/kpj/khmer_angkor.kpj | 0 .../project-missing-file/project_missing_file.kpj | 0 .../project-missing-file/project_missing_files.kpj | 0 6 files changed, 17 insertions(+), 14 deletions(-) rename {common/web/types => developer/src/common/web/utils}/test/fixtures/kpj/khmer_angkor.kpj (100%) rename {common/web/types => developer/src/common/web/utils}/test/fixtures/kpj/project-missing-file/project_missing_file.kpj (100%) rename {common/web/types => developer/src/common/web/utils}/test/fixtures/kpj/project-missing-file/project_missing_files.kpj (100%) diff --git a/developer/src/common/web/utils/build.sh b/developer/src/common/web/utils/build.sh index e58d585c5d3..cd50df5a166 100755 --- a/developer/src/common/web/utils/build.sh +++ b/developer/src/common/web/utils/build.sh @@ -21,7 +21,7 @@ builder_describe "Build Keyman Developer web utility module" \ builder_describe_outputs \ configure /node_modules \ - build /developer/src/common/web/utils/build/index.js + build /developer/src/common/web/utils/build/src/index.js builder_parse "$@" @@ -34,7 +34,10 @@ builder_run_action build tsc --build if builder_start_action test; then eslint . tsc --build test - c8 --reporter=lcov --reporter=text --exclude-after-remap mocha + readonly C8_THRESHOLD=60 + c8 --reporter=lcov --reporter=text --exclude-after-remap --lines $C8_THRESHOLD --statements $C8_THRESHOLD --branches $C8_THRESHOLD --functions $C8_THRESHOLD mocha + builder_echo warning "Coverage thresholds are currently $C8_THRESHOLD%, which is lower than ideal." + builder_echo warning "Please increase threshold in build.sh as test coverage improves." builder_finish_action success test fi diff --git a/developer/src/common/web/utils/src/types/kpj/keyman-developer-project.ts b/developer/src/common/web/utils/src/types/kpj/keyman-developer-project.ts index 7ddcb43c183..a215bfbaae2 100644 --- a/developer/src/common/web/utils/src/types/kpj/keyman-developer-project.ts +++ b/developer/src/common/web/utils/src/types/kpj/keyman-developer-project.ts @@ -28,13 +28,13 @@ export class KeymanDeveloperProject { if(this.options.version != '2.0') { throw new Error('populateFiles can only be called on a v2.0 project'); } - let sourcePath = this.resolveProjectPath(this.options.sourcePath); + const sourcePath = this.resolveProjectPath(this.options.sourcePath); if(!this.callbacks.fs.existsSync(sourcePath)) { return false; } - let files = this.callbacks.fs.readdirSync(sourcePath); - for(let filename of files) { - let fullPath = this.callbacks.path.join(sourcePath, filename); + const files = this.callbacks.fs.readdirSync(sourcePath); + for(const filename of files) { + const fullPath = this.callbacks.path.join(sourcePath, filename); if(KeymanFileTypes.filenameIs(filename, KeymanFileTypes.Source.LdmlKeyboard)) { try { const data = this.callbacks.loadFile(fullPath); @@ -50,7 +50,7 @@ export class KeymanDeveloperProject { } } if(KeymanFileTypes.sourceTypeFromFilename(filename) !== null) { - let file = new KeymanDeveloperProjectFile20(fullPath, this.callbacks); + const file = new KeymanDeveloperProjectFile20(fullPath, this.callbacks); this.files.push(file); } } @@ -108,7 +108,7 @@ export class KeymanDeveloperProject { p = this.resolveProjectPath(p); - let f = file.filename.replace(new RegExp(`\\${sourceExt}$`, 'i'), targetExt); + const f = file.filename.replace(new RegExp(`\\${sourceExt}$`, 'i'), targetExt); return this.callbacks.path.normalize(this.callbacks.path.join(p, f)); } diff --git a/developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts b/developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts index a0ef422b01a..9c33aaedd7d 100644 --- a/developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts +++ b/developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts @@ -28,7 +28,7 @@ export class KPJFileReader { }); data = this.boxArrays(data); if(data.KeymanDeveloperProject?.Files?.File?.length) { - for(let file of data.KeymanDeveloperProject?.Files?.File) { + for(const file of data.KeymanDeveloperProject?.Files?.File) { // xml2js imports
as '' so we will just delete the empty string if(typeof file.Details == 'string') { delete file.Details; @@ -59,8 +59,8 @@ export class KPJFileReader { // NOTE: at this point, the xml should have been validated // and matched the schema result so we can assume the source // is a valid shape - let project = source.KeymanDeveloperProject; - let result: KeymanDeveloperProject = new KeymanDeveloperProject(projectFilename, project.Options?.Version || "1.0", this.callbacks); + const project = source.KeymanDeveloperProject; + const result: KeymanDeveloperProject = new KeymanDeveloperProject(projectFilename, project.Options?.Version || "1.0", this.callbacks); if(result.options.version == '2.0') { result.options.buildPath = (project.Options?.BuildPath || result.options.buildPath).replace(/\\/g, '/'); result.options.sourcePath = (project.Options?.SourcePath || result.options.sourcePath).replace(/\\/g, '/'); @@ -87,9 +87,9 @@ export class KPJFileReader { } private transformFilesVersion10(project: KPJFileProject, result: KeymanDeveloperProject) { - let ids: { [id: string]: KeymanDeveloperProjectFile10; } = {}; - for (let sourceFile of project.Files?.File) { - let file: KeymanDeveloperProjectFile10 = new KeymanDeveloperProjectFile10( + const ids: { [id: string]: KeymanDeveloperProjectFile10; } = {}; + for (const sourceFile of project.Files?.File) { + const file: KeymanDeveloperProjectFile10 = new KeymanDeveloperProjectFile10( sourceFile.ID || '', (sourceFile.Filepath || '').replace(/\\/g, '/'), sourceFile.FileVersion || '', diff --git a/common/web/types/test/fixtures/kpj/khmer_angkor.kpj b/developer/src/common/web/utils/test/fixtures/kpj/khmer_angkor.kpj similarity index 100% rename from common/web/types/test/fixtures/kpj/khmer_angkor.kpj rename to developer/src/common/web/utils/test/fixtures/kpj/khmer_angkor.kpj diff --git a/common/web/types/test/fixtures/kpj/project-missing-file/project_missing_file.kpj b/developer/src/common/web/utils/test/fixtures/kpj/project-missing-file/project_missing_file.kpj similarity index 100% rename from common/web/types/test/fixtures/kpj/project-missing-file/project_missing_file.kpj rename to developer/src/common/web/utils/test/fixtures/kpj/project-missing-file/project_missing_file.kpj diff --git a/common/web/types/test/fixtures/kpj/project-missing-file/project_missing_files.kpj b/developer/src/common/web/utils/test/fixtures/kpj/project-missing-file/project_missing_files.kpj similarity index 100% rename from common/web/types/test/fixtures/kpj/project-missing-file/project_missing_files.kpj rename to developer/src/common/web/utils/test/fixtures/kpj/project-missing-file/project_missing_files.kpj From b2c4979cf4eed79c402ecf19cd4b1fbe5491c3d9 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 24 May 2024 15:53:23 +0700 Subject: [PATCH 4/9] chore(common): reduce c8 threshold for common-types As we move some better-tested areas out of common-types, it looks like our overall coverage goes down. --- common/web/types/build.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/common/web/types/build.sh b/common/web/types/build.sh index df11540b424..b57a516893e 100755 --- a/common/web/types/build.sh +++ b/common/web/types/build.sh @@ -100,7 +100,10 @@ function do_build() { function do_test() { eslint . tsc --build test - c8 --skip-full --reporter=lcov --reporter=text mocha "${builder_extra_params[@]}" + readonly C8_THRESHOLD=75 + c8 -skip-full --reporter=lcov --reporter=text --lines $C8_THRESHOLD --statements $C8_THRESHOLD --branches $C8_THRESHOLD --functions $C8_THRESHOLD mocha "${builder_extra_params[@]}" + builder_echo warning "Coverage thresholds are currently $C8_THRESHOLD%, which is lower than ideal." + builder_echo warning "Please increase threshold in build.sh as test coverage improves." } #------------------------------------------------------------------------------------------------------------------- From 185c020bf12364f100c685c778d197ba3b3a54b8 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Wed, 12 Jun 2024 16:43:28 +0700 Subject: [PATCH 5/9] chore(developer): update xml2js reference for kpj-file-reader --- developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts b/developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts index 9c33aaedd7d..f65440f74ba 100644 --- a/developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts +++ b/developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts @@ -1,4 +1,4 @@ -import * as xml2js from '../deps/xml2js/xml2js.js'; +import * as xml2js from '@keymanapp/common-types'; import { KPJFile, KPJFileProject } from './kpj-file.js'; import { util } from '@keymanapp/common-types'; import { KeymanDeveloperProject, KeymanDeveloperProjectFile10, KeymanDeveloperProjectType } from './keyman-developer-project.js'; From 071fe5ccb5bbff401b00e811638183613bfed0ed Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 13 Jun 2024 08:49:05 +1000 Subject: [PATCH 6/9] chore(developer): fixup reference to xml2js --- developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts b/developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts index f65440f74ba..bff0b299a0f 100644 --- a/developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts +++ b/developer/src/common/web/utils/src/types/kpj/kpj-file-reader.ts @@ -1,4 +1,4 @@ -import * as xml2js from '@keymanapp/common-types'; +import { xml2js } from '@keymanapp/common-types'; import { KPJFile, KPJFileProject } from './kpj-file.js'; import { util } from '@keymanapp/common-types'; import { KeymanDeveloperProject, KeymanDeveloperProjectFile10, KeymanDeveloperProjectType } from './keyman-developer-project.js'; From d8986be12fe0fb2078b93fa4650e2a7fe06f16d1 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 1 Aug 2024 07:07:07 +0700 Subject: [PATCH 7/9] refactor(common): merge master into branch --- .clang-format | 4 +- .github/workflows/deb-packaging.yml | 2 +- HISTORY.md | 248 +- VERSION.md | 2 +- android/.gitignore | 2 - .../src/main/res/values-b+el/strings.xml | 164 + .../src/main/res/values-pt-rPT/strings.xml | 10 +- .../KMEA/app/src/main/assets/android-host.js | 14 +- .../app/src/main/assets/keyboard.es5.html | 30 - .../com/keyman/engine/DisplayLanguages.java | 1 + .../java/com/keyman/engine/KMKeyboard.java | 6 + .../java/com/keyman/engine/KMManager.java | 32 +- .../app/src/main/res/values-b+el/strings.xml | 172 + .../src/main/res/values-pt-rPT/strings.xml | 20 +- android/KMEA/build.sh | 2 - common/models/templates/src/common.ts | 5 +- common/models/templates/src/index.ts | 1 - common/models/templates/src/trie-model.ts | 246 +- .../templates/test/test-trie-traversal.js | 153 +- common/models/types/index.d.ts | 49 +- .../models/wordbreakers/src/default/index.ts | 2 +- .../headless/worker-trie-integration.js | 5 +- .../cases/top-level-lmlayer.spec.ts | 4 +- .../cases/worker-dummy-integration.spec.ts | 16 +- .../cases/worker-trie-integration.spec.ts | 3 +- .../i_got_distracted_by_hazel.json | 2 +- common/test/resources/model-helpers.mjs | 13 +- common/test/resources/models/simple-dummy.js | 30 +- common/test/resources/test-timeouts.mjs | 1 + common/test/resources/timeout-adapter.js | 43 - .../sourcemap-path-remapper/tsconfig.json | 2 +- common/web/es-bundling/build.sh | 3 +- common/web/es-bundling/src/common-bundle.mts | 4 +- common/web/es-bundling/src/configuration.mts | 7 +- .../src/test/auto/browser/cases/canary.def.ts | 4 +- .../auto/browser/cases/ignoredInputs.def.ts | 4 +- common/web/input-processor/src/corrections.ts | 7 +- .../src/text/prediction/languageProcessor.ts | 34 +- .../src/text/prediction/predictionContext.ts | 82 +- .../tests/cases/predictionContext.js | 2 +- common/web/keyboard-processor/build.sh | 24 +- .../src/keyboards/activeLayout.ts | 306 +- .../src/keyboards/defaultLayouts.ts | 16 +- .../src/keyboards/keyboard.ts | 14 +- .../src/text/stringDivergence.ts | 5 +- .../tests/dom/cases/domKeyboardLoader.spec.ts | 32 +- .../tests/dom/web-test-runner.config.mjs | 4 +- .../keyboard-processor/tests/tsconfig.json | 11 + .../web/keyboard-processor/tsconfig.all.json | 5 +- common/web/keyboard-processor/tsconfig.json | 4 +- common/web/keyman-version/tsconfig.json | 2 +- common/web/lm-message-types/tsconfig.json | 2 +- common/web/lm-worker/build-polyfiller.js | 7 +- common/web/lm-worker/build.sh | 34 +- common/web/lm-worker/package.json | 6 +- .../src/main/correction/distance-modeler.ts | 366 +- .../src/main/correction/execution-timer.ts | 437 ++ .../lm-worker/src/main/correction/index.ts | 3 +- common/web/lm-worker/src/main/index.ts | 21 +- .../lm-worker/src/main/model-compositor.ts | 631 +- .../web/lm-worker/src/main/model-helpers.ts | 106 + .../lm-worker/src/main/models/dummy-model.ts | 39 +- .../web/lm-worker/src/main/predict-helpers.ts | 791 +++ .../web/lm-worker/src/polyfills/array.from.js | 44 + .../src/test/mocha/cases/auto-correct.js | 598 ++ .../src/test/mocha/cases/casing-detection.js | 386 ++ .../cases/early-correction-search-stopping.js | 61 + .../cases/edit-distance/distance-modeler.js | 127 +- .../cases/edit-distance/execution-timer.js | 383 ++ .../mocha/cases/predict-from-corrections.js | 259 + .../mocha/cases/suggestion-deduplication.js | 154 + .../mocha/cases/suggestion-finalization.js | 368 ++ .../test/mocha/cases/suggestion-similarity.js | 512 ++ .../mocha/cases/worker-custom-punctuation.js | 14 +- .../mocha/cases/worker-model-compositor.js | 153 +- .../src/test/mocha/cases/worker-predict.js | 15 +- .../src/test/test-runner/cases/worker.spec.ts | 3 +- common/web/sentry-manager/src/index.ts | 7 +- common/web/tslib/README.md | 15 - common/web/tslib/build.sh | 25 - common/web/tslib/package.json | 28 - common/web/tslib/src/index.ts | 3 - common/web/tslib/tsconfig.json | 16 - common/web/types/src/deps/xml2js/parser.js | 8 +- common/web/types/src/kmx/element-string.ts | 3 +- common/web/types/src/kmx/kmx.ts | 50 +- .../types/src/ldml-keyboard/pattern-parser.ts | 3 +- common/web/types/src/main.ts | 1 + common/web/types/src/util/consts.ts | 12 + common/web/types/src/util/file-types.ts | 10 + common/web/types/src/util/util.ts | 21 +- common/web/utils/src/index.ts | 4 +- .../utils}/src/priority-queue.ts | 8 +- common/web/utils/src/surrogates.ts | 27 - .../utils/src/test/priorityQueue.js} | 2 +- common/windows/build.sh | 2 +- common/windows/cpp/src/registry.cpp | 2 +- core/commands.inc.sh | 9 +- core/src/actions_normalize.cpp | 146 +- core/src/context.hpp | 24 + core/src/core_icu.cpp | 72 + core/src/core_icu.h | 64 +- core/src/km_core_context_api.cpp | 7 + core/src/kmx/kmx_processevent.cpp | 4 - core/src/kmx/kmx_xstring.cpp | 14 + core/src/kmx/kmx_xstring.h | 3 + core/src/ldml/ldml_markers.cpp | 35 +- core/src/ldml/ldml_markers.hpp | 4 - core/src/ldml/ldml_transforms.cpp | 174 +- core/src/ldml/ldml_transforms.hpp | 10 +- core/src/meson.build | 44 +- core/src/util_normalize.cpp | 199 +- core/src/util_normalize.hpp | 21 + core/src/util_normalize_table_generator.cpp | 108 + core/src/util_regex.cpp | 352 ++ core/src/util_regex.hpp | 48 + core/tests/unit/kmnkbd/meson.build | 2 +- core/tests/unit/ldml/core_ldml_min.cpp | 39 + core/tests/unit/ldml/ldml_test_source.cpp | 3 + core/tests/unit/ldml/meson.build | 13 +- core/tests/unit/ldml/test_kmx_plus.cpp | 1 + core/tests/unit/ldml/test_transforms.cpp | 127 +- core/tests/unit/ldml/test_unicode.cpp | 50 + crowdin.yml | 8 +- developer/docs/api/etc/kmc-analyze.api.md | 2 +- .../docs/api/etc/kmc-keyboard-info.api.md | 36 +- developer/docs/api/etc/kmc-kmn.api.md | 104 +- developer/docs/api/etc/kmc-ldml.api.md | 98 +- developer/docs/api/etc/kmc-package.api.md | 14 + developer/src/.gitignore | 1 + .../src/common/include/kmn_compiler_errors.h | 20 +- .../src/common/web/test-helpers/tsconfig.json | 6 - developer/src/common/web/utils/src/index.ts | 1 + .../common/web/utils/src/is-valid-email.ts | 18 + .../web/utils/test/test-is-valid-email.ts | 35 + .../src/common/web/utils/test/tsconfig.json | 8 +- developer/src/kmc-analyze/build.sh | 1 + .../src/{messages.ts => analyzer-messages.ts} | 3 + developer/src/kmc-analyze/src/index.ts | 2 +- .../src/osk-character-use/index.ts | 8 +- .../kmc-analyze/src/osk-rewrite-pua/index.ts | 8 +- developer/src/kmc-analyze/tsconfig.json | 10 - developer/src/kmc-keyboard-info/build.sh | 1 + .../src/keyboard-info-compiler.ts | 9 +- .../khmer_angkor.kps | 67 + .../multiple-email-addresses/LICENSE.md | 21 + .../build/.gitattributes | 1 + .../build/khmer_angkor.js | 5462 +++++++++++++++++ .../build/khmer_angkor.kmp | Bin 0 -> 4291221 bytes .../build/khmer_angkor.kmx | Bin 0 -> 27666 bytes .../multiple-email-addresses/khmer_angkor.kpj | 187 + .../source/khmer_angkor.kps | 49 + .../test-keyboard-info-compiler-messages.ts | 43 +- .../test/test-keyboard-info-compiler.ts | 47 +- .../src/kmc-keyboard-info/test/tsconfig.json | 6 - developer/src/kmc-keyboard-info/tsconfig.json | 10 - developer/src/kmc-kmn/build.sh | 3 +- developer/src/kmc-kmn/package.json | 4 +- .../src/kmc-kmn/src/compiler/compiler.ts | 30 +- .../src/compiler/kmn-compiler-messages.ts | 36 +- .../src/kmw-compiler/compiler-globals.ts | 99 +- .../src/kmw-compiler/javascript-strings.ts | 40 +- .../src/kmw-compiler/kmw-compiler-messages.ts | 11 +- .../kmc-kmn/src/kmw-compiler/kmw-compiler.ts | 33 +- .../src/kmw-compiler/validate-layout-file.ts | 20 +- developer/src/kmc-kmn/src/main.ts | 2 +- .../keyboards/hint_index_store_long.kmn | 11 + .../keyboards/hint_index_store_long_key.kmn | 11 + .../keyboards/warn_index_store_short.kmn | 11 + .../keyboards/warn_index_store_short_key.kmn | 11 + .../test/fixtures/kmw/version_9_caps_lock.kmn | 11 + .../kmw/version_9_chiral_modifiers.kmn | 11 + .../fixtures/kmw/version_auto_caps_lock.kmn | 10 + .../kmw/version_auto_chiral_modifiers.kmn | 10 + .../kmw/version_gestures.keyman-touch-layout | 51 + .../test/fixtures/kmw/version_gestures.kmn | 9 + .../test/fixtures/kmw/version_gestures_16.kmn | 11 + .../test/fixtures/kmw/version_notany.kmn | 12 + .../test/fixtures/kmw/version_notany_10.kmn | 13 + ...rsion_special_key_caps.keyman-touch-layout | 45 + .../fixtures/kmw/version_special_key_caps.kmn | 9 + .../kmw/version_special_key_caps_14.kmn | 12 + .../version_u_xxxx_yyyy.keyman-touch-layout | 53 + .../test/fixtures/kmw/version_u_xxxx_yyyy.kmn | 7 + .../fixtures/kmw/version_u_xxxx_yyyy_14.kmn | 8 + .../src/kmc-kmn/test/kmw/test-kmw-compiler.ts | 175 +- .../src/kmc-kmn/test/kmw/test-kmw-messages.ts | 15 + developer/src/kmc-kmn/test/test-features.ts | 12 +- developer/src/kmc-kmn/test/test-messages.ts | 24 +- developer/src/kmc-kmn/test/test-wasm-uset.ts | 14 +- developer/src/kmc-kmn/test/tsconfig.json | 4 - developer/src/kmc-kmn/tsconfig.json | 10 - .../src/kmc-ldml/src/compiler/compiler.ts | 56 +- developer/src/kmc-ldml/src/compiler/disp.ts | 8 +- .../kmc-ldml/src/compiler/empty-compiler.ts | 10 +- developer/src/kmc-ldml/src/compiler/keys.ts | 22 +- developer/src/kmc-ldml/src/compiler/layr.ts | 8 +- ...{messages.ts => ldml-compiler-messages.ts} | 36 +- .../kmc-ldml/src/compiler/linter-keycaps.ts | 51 + developer/src/kmc-ldml/src/compiler/linter.ts | 23 + developer/src/kmc-ldml/src/compiler/loca.ts | 8 +- developer/src/kmc-ldml/src/compiler/meta.ts | 8 +- developer/src/kmc-ldml/src/compiler/tran.ts | 28 +- developer/src/kmc-ldml/src/compiler/vars.ts | 16 +- .../src/compiler/visual-keyboard-compiler.ts | 6 +- developer/src/kmc-ldml/src/main.ts | 2 +- .../fixtures/sections/keys/warn-no-keycap.xml | 29 + .../sections/layr/error-bogus-modifiers.xml | 12 + .../src/kmc-ldml/test/test-compiler-e2e.ts | 60 +- developer/src/kmc-ldml/test/test-disp.ts | 14 +- developer/src/kmc-ldml/test/test-helpers.ts | 14 +- developer/src/kmc-ldml/test/test-keys.ts | 34 +- developer/src/kmc-ldml/test/test-layr.ts | 14 +- developer/src/kmc-ldml/test/test-linter.ts | 30 + developer/src/kmc-ldml/test/test-loca.ts | 12 +- developer/src/kmc-ldml/test/test-messages.ts | 8 +- developer/src/kmc-ldml/test/test-meta.ts | 8 +- developer/src/kmc-ldml/test/test-strs.ts | 74 + developer/src/kmc-ldml/test/test-tran.ts | 50 +- developer/src/kmc-ldml/test/test-vars.ts | 24 +- developer/src/kmc-ldml/test/tsconfig.json | 10 - developer/src/kmc-ldml/tsconfig.json | 14 - .../kmc-model-info/src/model-info-compiler.ts | 7 +- .../src/kmc-model-info/test/tsconfig.json | 6 - developer/src/kmc-model-info/tsconfig.json | 9 - developer/src/kmc-model/package.json | 2 +- developer/src/kmc-model/test/tsconfig.json | 4 - developer/src/kmc-model/tsconfig.json | 5 - .../kmc-package/src/compiler/kmp-compiler.ts | 16 +- .../src/compiler/package-compiler-messages.ts | 6 +- .../package-keyboard-target-validator.ts | 6 +- .../compiler/package-metadata-collector.ts | 12 +- .../src/compiler/package-validation.ts | 46 +- .../src/compiler/package-version-validator.ts | 10 +- .../windows-package-installer-compiler.ts | 8 +- developer/src/kmc-package/src/main.ts | 2 +- .../invalid/error_invalid_author_email.kps | 32 + .../error_invalid_author_email_multiple.kps | 32 + .../src/kmc-package/test/test-messages.ts | 76 +- .../kmc-package/test/test-package-compiler.ts | 8 +- developer/src/kmc-package/test/tsconfig.json | 6 - developer/src/kmc-package/tsconfig.json | 4 - developer/src/kmc/build.sh | 3 +- developer/src/kmc/package.json | 2 +- .../buildClasses/BuildKeyboardInfo.ts | 4 +- .../commands/buildClasses/BuildModelInfo.ts | 4 +- .../src/kmc/src/commands/messageCommand.ts | 48 +- .../src/messages/infrastructureMessages.ts | 9 +- .../src/kmc/src/messages/messageNamespaces.ts | 8 +- .../kmc/src/util/extendedCompilerOptions.ts | 50 +- .../src/kmc/src/util/getLastGitCommitDate.ts | 22 +- .../get-last-git-commit-date/README.md | 5 + .../src/kmc/test/test-getLastGitCommitDate.ts | 9 + developer/src/kmc/test/tsconfig.json | 6 - developer/src/kmc/tsconfig.json | 21 - developer/src/kmcmplib/include/kmcompx.h | 1 + developer/src/kmcmplib/src/CompMsg.cpp | 32 +- developer/src/kmcmplib/src/CompMsg.h | 2 +- .../kmcmplib/src/CompileKeyboardBuffer.cpp | 6 +- developer/src/kmcmplib/src/Compiler.cpp | 148 +- .../src/kmcmplib/src/NamedCodeConstants.cpp | 2 +- developer/src/kmcmplib/src/kmx_u16.cpp | 14 +- developer/src/kmcmplib/src/kmx_u16.h | 1 + developer/src/kmcmplib/src/meson.build | 4 +- developer/src/kmcmplib/src/versioning.cpp | 8 +- developer/src/kmcmplib/tests/api-test.cpp | 33 +- .../kmcmplib/tests/gtest-compiler-test.cpp | 917 ++- .../src/kmcmplib/tests/gtest-compmsg-test.cpp | 19 + .../src/kmcmplib/tests/gtest-kmx_u16-test.cpp | 168 + developer/src/kmcmplib/tests/kmcompxtest.cpp | 10 +- developer/src/kmcmplib/tests/meson.build | 22 + .../src/kmcmplib/tests/util_callbacks.cpp | 6 +- ...Developer.System.ImportWindowsKeyboard.pas | 46 +- ....VisualKeyboardToTouchLayoutConverter.pas} | 18 +- developer/src/kmconvert/kmconvert.dpr | 2 +- developer/src/kmconvert/kmconvert.dproj | 2 +- developer/src/server/package.json | 2 +- developer/src/server/tsconfig.json | 8 - .../oskbuilder/UframeTouchLayoutBuilder.pas | 6 +- ...n.Developer.System.Project.ProjectFile.pas | 13 +- ...eveloper.System.Project.kpsProjectFile.pas | 7 + developer/src/tike/tike.dpr | 2 +- developer/src/tike/tike.dproj | 2 +- developer/src/tike/xml/help/contexthelp.xml | 478 +- .../src/tike/xml/layoutbuilder/builder.xsl | 4 + .../xml/layoutbuilder/platform-controls.js | 24 +- docs/build/macos.md | 4 +- docs/linux/keyman-config.md | 3 +- .../KeymanEngine.xcodeproj/project.pbxproj | 7 + .../Classes/el.lproj/ResourceInfoView.strings | 2 + .../KeymanEngine/el.lproj/Localizable.strings | 257 + .../el.lproj/Localizable.stringsdict | 118 + .../pt-PT.lproj/Localizable.strings | 2 +- .../Contents/Resources/ios-host.js | 4 +- .../Keyman/Keyman.xcodeproj/project.pbxproj | 5 +- .../Keyman/el.lproj/Localizable.strings | 81 + linux/.pbuilderrc | 82 - linux/Makefile | 14 +- linux/debian/changelog | 7 + linux/ibus-keyman/.gitignore | 1 + linux/ibus-keyman/build.sh | 9 + linux/ibus-keyman/meson.build | 11 +- linux/ibus-keyman/src/engine.c | 30 +- linux/ibus-keyman/src/keymanutil.c | 218 +- linux/ibus-keyman/src/keymanutil.h | 108 +- linux/ibus-keyman/src/test/keymanutil_tests.c | 310 +- linux/ibus-keyman/tests/meson.build | 2 +- linux/ibus-keyman/tests/scripts/run-tests.sh | 2 +- .../tests/scripts/test-helper.inc.sh | 28 +- linux/ibus-keyman/tests/testfixture.cpp | 2 +- .../keyman-config/keyman_config/convertico.py | 20 +- linux/keyman-config/keyman_config/get_kmp.py | 50 +- .../keyman_config/install_kmp.py | 8 +- .../keyman_config/keyman_option.py | 42 + .../keyman_config/options_widget.py | 12 +- .../keyman_config/sentry_handling.py | 17 +- .../keyman_config/view_installed.py | 61 +- linux/keyman-config/km-kvk2ldml | 4 +- .../resources/com.keyman.gschema.xml | 6 + linux/keyman-system-service/.gitignore | 1 + linux/keyman-system-service/build.sh | 9 + .../resources/{meson.build => meson.build.in} | 1 + linux/scripts/cow.sh | 50 - linux/scripts/deb-packaging.sh | 2 +- linux/scripts/deb.sh | 51 +- linux/scripts/dist.sh | 2 +- linux/scripts/launchpad.sh | 2 +- linux/scripts/upload-to-debian.sh | 28 +- .../KMInfoWindowController.strings | 2 +- .../Keyman4MacIM/KMInputMethodAppDelegate.m | 33 +- .../Keyman4MacIM/KMInputMethodEventHandler.m | 4 + .../pt-PT.lproj/Localizable.strings | 21 +- oem/firstvoices/keyboards.csv | 52 +- package-lock.json | 1300 ++-- package.json | 8 +- resources/stats/stats.sh | 31 +- web/build.sh | 57 +- web/package.json | 1 - web/src/app/browser/build.sh | 12 - web/src/app/browser/src/contextManager.ts | 4 +- web/src/app/browser/src/keymanEngine.ts | 44 +- web/src/app/browser/src/viewsAnchorpoint.ts | 6 +- web/src/app/webview/build.sh | 10 - web/src/app/webview/src/keymanEngine.ts | 37 +- web/src/engine/attachment/src/index.ts | 2 +- web/src/engine/main/src/keymanEngine.ts | 6 +- .../engine/osk/src/banner/suggestionBanner.ts | 48 +- .../osk/src/config/viewConfiguration.ts | 9 - .../src/input/gestures/browser/oskSubKey.ts | 3 +- .../osk/src/keyboard-layout/oskLayerGroup.ts | 88 +- .../engine/osk/src/views/floatingOskView.ts | 24 +- web/src/engine/osk/src/views/oskView.ts | 22 +- web/src/engine/osk/src/visualKeyboard.ts | 29 +- .../attachment/outputTargetForElement.def.ts | 48 +- .../outputTargetForElement.spec.html | 2 +- .../attachment/pageContextAttachment.spec.ts | 341 +- .../dom/cases/browser/contextManager.spec.ts | 83 +- .../element_interfaces.spec.ts | 975 +-- .../element-wrappers/target_mocks.spec.ts | 124 +- .../auto/dom/cases/osk/activation.spec.ts | 8 +- .../test/auto/dom/cases/osk/events.spec.ts | 10 +- .../dom/cases/packages/cloudQueries.spec.ts | 20 +- .../cases/packages/domCloudRequester.spec.ts | 14 +- .../packages/keyboardRequisitioner.spec.ts | 12 +- web/src/test/auto/dom/kbdLoader.ts | 12 +- web/src/test/auto/dom/test_utils.ts | 34 +- .../test/auto/dom/web-test-runner.config.mjs | 16 +- .../test/auto/integrated/cases/basics.spec.ts | 17 +- .../test/auto/integrated/cases/engine.spec.ts | 70 +- .../integrated/cases/engine_chirality.spec.ts | 1 - .../test/auto/integrated/cases/events.spec.ts | 19 +- .../integrated/cases/text_selection.spec.ts | 83 +- web/src/test/auto/integrated/test_utils.ts | 55 +- .../integrated/web-test-runner.config.mjs | 2 +- web/src/test/auto/tsconfig.json | 6 +- web/src/test/manual/web/index.html | 1 + web/src/test/manual/web/issue11785/index.html | 109 + .../web/prediction-mtnt/nrc.en.mtnt.model.js | 43 +- .../tools/testing/recorder/browserDriver.ts | 16 + web/tsconfig.base.json | 2 +- windows/src/.gitignore | 2 +- .../desktop/kmshell/locale/pt-PT/strings.xml | 24 + windows/src/engine/.gitignore | 5 - windows/src/engine/README.md | 8 +- windows/src/engine/build.sh | 3 +- windows/src/engine/engine.sln | 16 +- .../src/engine/keyman32/DebugEventTrace.cpp | 28 +- windows/src/engine/keyman32/K32_load.cpp | 45 +- windows/src/engine/keyman32/SharedBuffers.cpp | 4 +- windows/src/engine/keyman32/appcontext.cpp | 5 - windows/src/engine/keyman32/appint/aiTIP.cpp | 62 +- windows/src/engine/keyman32/appint/aiTIP.h | 10 +- .../keyman32/appint/aiWin2000Unicode.cpp | 12 +- windows/src/engine/keyman32/appint/appint.cpp | 4 +- windows/src/engine/keyman32/build.sh | 60 +- windows/src/engine/keyman32/calldll.cpp | 73 +- windows/src/engine/keyman32/globals.h | 5 +- windows/src/engine/keyman32/glossary.cpp | 14 +- windows/src/engine/keyman32/hookutils.cpp | 10 +- windows/src/engine/keyman32/hotkeys.cpp | 33 +- windows/src/engine/keyman32/k32_dbg.cpp | 159 +- windows/src/engine/keyman32/k32_globals.cpp | 39 +- .../keyman32/k32_lowlevelkeyboardhook.cpp | 57 +- windows/src/engine/keyman32/keybd_shift.cpp | 46 +- .../src/engine/keyman32/keyboardoptions.cpp | 16 +- windows/src/engine/keyman32/keyman32.cpp | 74 +- windows/src/engine/keyman32/keyman32.def | 3 + windows/src/engine/keyman32/keyman32.rc | 1 - windows/src/engine/keyman32/keyman32.vcxproj | 338 +- .../engine/keyman32/keyman32.vcxproj.filters | 6 + windows/src/engine/keyman32/keyman64.def | 45 + windows/src/engine/keyman32/keyman64.rc | 1 + windows/src/engine/keyman32/keymanengine.h | 57 +- .../engine/keyman32/kmhook_callwndproc.cpp | 20 +- .../src/engine/keyman32/kmhook_getmessage.cpp | 335 +- .../src/engine/keyman32/kmhook_keyboard.cpp | 11 +- windows/src/engine/keyman32/kmprocess.cpp | 40 +- .../src/engine/keyman32/kmprocessactions.cpp | 26 +- .../src/engine/keyman32/selectkeyboard.cpp | 46 +- .../engine/keyman32/serialkeyeventclient.cpp | 8 +- .../engine/keyman32/serialkeyeventserver.cpp | 7 +- windows/src/engine/keyman32/syskbd.cpp | 58 +- windows/src/engine/keyman32/syskbdnt.cpp | 37 +- windows/src/engine/keyman32/syskbdnt64.cpp | 44 +- .../version.rc => keyman32/version64.rc} | 0 windows/src/engine/keyman64/build.sh | 50 - windows/src/engine/keyman64/keyman64.rc | 3 - windows/src/engine/keyman64/keyman64.sln | 24 - windows/src/engine/keyman64/keyman64.vcxproj | 332 - .../engine/keyman64/keyman64.vcxproj.filters | 89 - windows/src/engine/kmtip/debug.cpp | 26 +- windows/src/engine/kmtip/globals.cpp | 14 - windows/src/engine/kmtip/globals.h | 50 +- windows/src/engine/kmtip/inserttext.cpp | 190 +- .../src/engine/kmtip/keyman32interface.cpp | 16 +- windows/src/engine/kmtip/keys.cpp | 97 +- windows/src/engine/kmtip/kmkey.cpp | 158 +- windows/src/engine/kmtip/kmtip.cpp | 35 +- windows/src/engine/kmtip/kmtip.h | 8 +- windows/src/engine/kmtip/registryw.cpp | 2 +- windows/src/engine/kmtip/tmgrsink.cpp | 29 +- windows/src/engine/testhost/testhost.cpp | 4 +- .../keymandebuglog/UfrmKeymanDebugLogMain.pas | 2 +- .../i3619tip/i3619tip/i3619tip/kmkey.cpp | 18 +- .../i3619tip/i3619tip/i3619tip/registryw.cpp | 2 +- 445 files changed, 21506 insertions(+), 6701 deletions(-) create mode 100644 android/KMAPro/kMAPro/src/main/res/values-b+el/strings.xml delete mode 100644 android/KMEA/app/src/main/assets/keyboard.es5.html create mode 100644 android/KMEA/app/src/main/res/values-b+el/strings.xml create mode 100644 common/test/resources/test-timeouts.mjs delete mode 100644 common/test/resources/timeout-adapter.js create mode 100644 common/web/keyboard-processor/tests/tsconfig.json create mode 100644 common/web/lm-worker/src/main/correction/execution-timer.ts create mode 100644 common/web/lm-worker/src/main/model-helpers.ts create mode 100644 common/web/lm-worker/src/main/predict-helpers.ts create mode 100644 common/web/lm-worker/src/polyfills/array.from.js create mode 100644 common/web/lm-worker/src/test/mocha/cases/auto-correct.js create mode 100644 common/web/lm-worker/src/test/mocha/cases/casing-detection.js create mode 100644 common/web/lm-worker/src/test/mocha/cases/early-correction-search-stopping.js create mode 100644 common/web/lm-worker/src/test/mocha/cases/edit-distance/execution-timer.js create mode 100644 common/web/lm-worker/src/test/mocha/cases/predict-from-corrections.js create mode 100644 common/web/lm-worker/src/test/mocha/cases/suggestion-deduplication.js create mode 100644 common/web/lm-worker/src/test/mocha/cases/suggestion-finalization.js create mode 100644 common/web/lm-worker/src/test/mocha/cases/suggestion-similarity.js delete mode 100644 common/web/tslib/README.md delete mode 100755 common/web/tslib/build.sh delete mode 100644 common/web/tslib/package.json delete mode 100644 common/web/tslib/src/index.ts delete mode 100644 common/web/tslib/tsconfig.json create mode 100644 common/web/types/src/util/consts.ts rename common/{models/templates => web/utils}/src/priority-queue.ts (96%) delete mode 100644 common/web/utils/src/surrogates.ts rename common/{models/templates/test/test-priority-queue.js => web/utils/src/test/priorityQueue.js} (97%) create mode 100644 core/src/core_icu.cpp create mode 100644 core/src/util_normalize_table_generator.cpp create mode 100644 core/src/util_regex.cpp create mode 100644 core/src/util_regex.hpp create mode 100644 core/tests/unit/ldml/core_ldml_min.cpp create mode 100644 developer/src/common/web/utils/src/is-valid-email.ts create mode 100644 developer/src/common/web/utils/test/test-is-valid-email.ts rename developer/src/kmc-analyze/src/{messages.ts => analyzer-messages.ts} (88%) create mode 100644 developer/src/kmc-keyboard-info/test/fixtures/missing-info-version-in-kps-11856/khmer_angkor.kps create mode 100644 developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/LICENSE.md create mode 100644 developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/build/.gitattributes create mode 100644 developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/build/khmer_angkor.js create mode 100644 developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/build/khmer_angkor.kmp create mode 100644 developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/build/khmer_angkor.kmx create mode 100644 developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/khmer_angkor.kpj create mode 100644 developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/source/khmer_angkor.kps create mode 100644 developer/src/kmc-kmn/test/fixtures/keyboards/hint_index_store_long.kmn create mode 100644 developer/src/kmc-kmn/test/fixtures/keyboards/hint_index_store_long_key.kmn create mode 100644 developer/src/kmc-kmn/test/fixtures/keyboards/warn_index_store_short.kmn create mode 100644 developer/src/kmc-kmn/test/fixtures/keyboards/warn_index_store_short_key.kmn create mode 100644 developer/src/kmc-kmn/test/fixtures/kmw/version_9_caps_lock.kmn create mode 100644 developer/src/kmc-kmn/test/fixtures/kmw/version_9_chiral_modifiers.kmn create mode 100644 developer/src/kmc-kmn/test/fixtures/kmw/version_auto_caps_lock.kmn create mode 100644 developer/src/kmc-kmn/test/fixtures/kmw/version_auto_chiral_modifiers.kmn create mode 100644 developer/src/kmc-kmn/test/fixtures/kmw/version_gestures.keyman-touch-layout create mode 100644 developer/src/kmc-kmn/test/fixtures/kmw/version_gestures.kmn create mode 100644 developer/src/kmc-kmn/test/fixtures/kmw/version_gestures_16.kmn create mode 100644 developer/src/kmc-kmn/test/fixtures/kmw/version_notany.kmn create mode 100644 developer/src/kmc-kmn/test/fixtures/kmw/version_notany_10.kmn create mode 100644 developer/src/kmc-kmn/test/fixtures/kmw/version_special_key_caps.keyman-touch-layout create mode 100644 developer/src/kmc-kmn/test/fixtures/kmw/version_special_key_caps.kmn create mode 100644 developer/src/kmc-kmn/test/fixtures/kmw/version_special_key_caps_14.kmn create mode 100644 developer/src/kmc-kmn/test/fixtures/kmw/version_u_xxxx_yyyy.keyman-touch-layout create mode 100644 developer/src/kmc-kmn/test/fixtures/kmw/version_u_xxxx_yyyy.kmn create mode 100644 developer/src/kmc-kmn/test/fixtures/kmw/version_u_xxxx_yyyy_14.kmn rename developer/src/kmc-ldml/src/compiler/{messages.ts => ldml-compiler-messages.ts} (85%) create mode 100644 developer/src/kmc-ldml/src/compiler/linter-keycaps.ts create mode 100644 developer/src/kmc-ldml/src/compiler/linter.ts create mode 100644 developer/src/kmc-ldml/test/fixtures/sections/keys/warn-no-keycap.xml create mode 100644 developer/src/kmc-ldml/test/fixtures/sections/layr/error-bogus-modifiers.xml create mode 100644 developer/src/kmc-ldml/test/test-linter.ts create mode 100644 developer/src/kmc-ldml/test/test-strs.ts create mode 100644 developer/src/kmc-package/test/fixtures/invalid/error_invalid_author_email.kps create mode 100644 developer/src/kmc-package/test/fixtures/invalid/error_invalid_author_email_multiple.kps create mode 100644 developer/src/kmc/test/fixtures/get-last-git-commit-date/README.md create mode 100644 developer/src/kmcmplib/tests/gtest-compmsg-test.cpp create mode 100644 developer/src/kmcmplib/tests/gtest-kmx_u16-test.cpp rename developer/src/kmconvert/{Keyman.Developer.System.TouchLayoutToVisualKeyboardConverter.pas => Keyman.Developer.System.VisualKeyboardToTouchLayoutConverter.pas} (94%) create mode 100644 ios/engine/KMEI/KeymanEngine/Classes/el.lproj/ResourceInfoView.strings create mode 100644 ios/engine/KMEI/KeymanEngine/el.lproj/Localizable.strings create mode 100644 ios/engine/KMEI/KeymanEngine/el.lproj/Localizable.stringsdict create mode 100644 ios/keyman/Keyman/Keyman/el.lproj/Localizable.strings delete mode 100644 linux/.pbuilderrc create mode 100644 linux/ibus-keyman/.gitignore create mode 100644 linux/keyman-config/keyman_config/keyman_option.py create mode 100644 linux/keyman-system-service/.gitignore rename linux/keyman-system-service/resources/{meson.build => meson.build.in} (84%) delete mode 100755 linux/scripts/cow.sh create mode 100644 web/src/test/manual/web/issue11785/index.html create mode 100644 windows/src/engine/keyman32/keyman64.def create mode 100644 windows/src/engine/keyman32/keyman64.rc rename windows/src/engine/{keyman64/version.rc => keyman32/version64.rc} (100%) delete mode 100755 windows/src/engine/keyman64/build.sh delete mode 100644 windows/src/engine/keyman64/keyman64.rc delete mode 100644 windows/src/engine/keyman64/keyman64.sln delete mode 100644 windows/src/engine/keyman64/keyman64.vcxproj delete mode 100644 windows/src/engine/keyman64/keyman64.vcxproj.filters diff --git a/.clang-format b/.clang-format index 76e36e1d44d..81ca2c82a37 100644 --- a/.clang-format +++ b/.clang-format @@ -19,7 +19,7 @@ AllowShortLambdasOnASingleLine: All AllowShortIfStatementsOnASingleLine: Never AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None -AlwaysBreakAfterReturnType: AllDefinitions +AlwaysBreakAfterReturnType: None AlwaysBreakBeforeMultilineStrings: true AlwaysBreakTemplateDeclarations: MultiLine BinPackArguments: true @@ -58,7 +58,7 @@ ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DeriveLineEnding: true -DerivePointerAlignment: true +DerivePointerAlignment: false DisableFormat: false ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true diff --git a/.github/workflows/deb-packaging.yml b/.github/workflows/deb-packaging.yml index 29f069efd50..9c374f5d050 100644 --- a/.github/workflows/deb-packaging.yml +++ b/.github/workflows/deb-packaging.yml @@ -116,7 +116,7 @@ jobs: strategy: fail-fast: true matrix: - dist: [focal, jammy, mantic, noble] + dist: [focal, jammy, noble] steps: - name: Checkout diff --git a/HISTORY.md b/HISTORY.md index 508218f04a5..94efb781a8e 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,210 @@ # Keyman Version History +## 18.0.80 alpha 2024-07-31 + +* chore(developer): remove redundant references from tsconfig.json (#12037) +* fix(web): add nullish test in setOsk (#12039) +* fix(web): unrevert #11258, leaving OSK hidden before instructed to display (#12049) +* test(developer): check correct use of u16chr when second parameter could be null (#11894) +* change(web): remove support for es5 (#11881) + +## 18.0.79 alpha 2024-07-30 + +* change(mac): add custom tags in sentry to better identify errors (#11947) +* feat(web): add unit tests for case-detection & handling (#11950) +* refactor(web): spin off method for correction-search probability-thresholding check (#11952) + +## 18.0.78 alpha 2024-07-29 + +* chore(common): Update history from 17.0.327 and add missing descriptions (#12021) +* change(web): revert #11174, which loads keyboards before initializing the OSK (#12015) +* feat(web): add unit testing for finalization of generated suggestions (#11946) +* feat(web): add unit tests for prediction lookup component (#11949) + +## 18.0.77 alpha 2024-07-27 + +* refactor(windows): clean up logging (#11921) +* chore(developer): rename to analyzer-messages.ts (#12017) +* fix(developer): remove `paths` from tsconfig.json (#12028) +* chore(developer): api doc refresh (#12029) + +## 18.0.76 alpha 2024-07-26 + +* change(linux): improve changelog PRs after upload to debian (#12024) +* test(developer): kmcmplib compiler unit tests 2 (#11663) +* fix(linux): set local directory if not specified (#12032) + +## 18.0.75 alpha 2024-07-25 + +* chore(windows): remove the posting WM_KEYUP/DOWN events to IM (#12002) +* feat(web): check for low-probability exact + exact-key correction matches (#11876) +* refactor(web): extract suggestion-finalization block into its own function (#11899) +* chore(developer): remove `CompilerMessages` stub and use `KmnCompilerMessages` (#11986) +* chore(developer): rename kmc-ldml `CompilerMessages`, `LdmlKeyboardCompilerMessages` to `LdmlCompilerMessages` (#11988) +* chore(developer): rename kmc-package `CompilerMessages` to `PackageCompilerMessages` (#11989) +* fix(developer): handle errors parsing .kps file when loading project (#12008) +* chore(common): updated stats script to support end date/sprint (#12009) +* fix(windows): align engine.sln platforms and configurations (#12011) +* refactor(web): spin off deduplication, suggestion-similarity sections (#11900) +* refactor(web): extract the correct-and-raw-predict blocks into their own method (#11888) +* refactor(web): convert internal prediction methods to stateless format (#11940) +* feat(web): add unit tests for predict auto-selection method (#11941) +* feat(web): extend unit-test oriented dummy model (#11948) +* feat(web): add unit tests for suggestion-similarity detection (#11944) +* feat(web): add unit testing for suggestion deduplication (#11945) +* feat(developer): add hint when index() store is longer than any() store (#12000) +* chore(android,ios): Add ojibwa ifinal/rdot keyboards to FirstVoices (#11889) + +## 18.0.74 alpha 2024-07-24 + +* fix(developer): correct handling of trailing spaces by GetDelimitedString() in kmcmplib compiler (#11938) +* chore(linux): remove Ubuntu Mantic, add Oracular (#12003) + +## 18.0.73 alpha 2024-07-23 + +* fix(windows): add text selected bool emit backspace key when text selected in TSF (#11884) +* fix(developer): prevent buffer overrun in `u16tok` (#11910) +* fix(developer): prevent invalid values in targets store (#11918) +* feat(developer): automatically detect version for `U_xxxx_yyyy` ids (#11957) +* feat(developer): handle automatic versioning of chiral modifiers (#11965) +* test(developer): Add tests for automatic versioning of notany() with context() (#11980) +* feat(developer): handle automatic versioning of special key caps on normal keys (#11981) +* feat(developer): automatically upgrade version when gestures are found in the touch layout (#11982) +* refactor(developer): rename `verifyMinimumKeymanVersion` (#11983) +* feat(developer): add searching for message identifiers to `kmc message` (#11984) +* fix(developer): kmc-keyboard-info: use default version 1.0 if version information missing (#11985) +* fix(web): present a "keep" option when a context-altering suggestion is auto-selected (#11969) +* fix(web): prevents auto-accept immediately after reversion (#11970) + +## 18.0.72 alpha 2024-07-22 + +* fix(web): remedy unit-test stability issues (#11933) +* refactor(web): fix TypeScript errors and warnings (#11911) + +## 18.0.71 alpha 2024-07-18 + +* chore(windows): add comments for _WIN64 tests (#11929) +* chore(common): Update Crowdin strings for Portuguese (#11974) + +## 18.0.70 alpha 2024-07-08 + +* feat(web): provide lexicon probabilities directly on the search path (#11868) +* feat(common/models): support direct-child access for Trie node iteration (#11869) +* change(common/models/templates): rework Trie predict method to utilize traversals (#11870) +* change(web): track the base correction for generated predictions (#11875) +* feat(web): add and enable auto-correction (#11866) + +## 18.0.69 alpha 2024-07-05 + +* fix(core): allow to successfully build on Ubuntu 24.04 (#11926) +* chore(windows): correct output file for 64-bit build of keyman32 in build.sh (#11930) +* chore(android,ios): Add Crowdin localization for Polytonic Greek (#11877) + +## 18.0.68 alpha 2024-07-04 + +* refactor(windows): merge keyman64 build into keyman32 (#11906) +* refactor(windows): remove wm_keyman_keydown and wm_keyman_keyup (#11920) + +## 18.0.67 alpha 2024-07-03 + +* refactor(common/models): move TS priority-queue implementation to web-utils (#11867) + +## 18.0.66 alpha 2024-07-02 + +* fix(developer): handle second parameter of index correctly in kmcmplib compiler (#11815) + +## 18.0.65 alpha 2024-07-01 + +* fix(developer): prevent non-BMP characters in key part of rule (#11806) +* chore(linux): remove unused building with pbuilder (#11862) + +## 18.0.64 alpha 2024-06-28 + +* fix(web): use fat-finger data with simple keypresses (#11854) + +## 18.0.63 alpha 2024-06-26 + +* feat(linux): implement Linux side of SimulateAltGr option :checkered_flag: (#11852) + +## 18.0.62 alpha 2024-06-25 + +* chore(common): update C/C++ formatting options (#11836) +* chore(linux): use shared meson config (#11863) +* fix(linux): ignore exceptions trying to install cache (#11861) + +## 18.0.61 alpha 2024-06-24 + +* feat(web): optimization via lazy preprocessing of keyboard touch-layout info (#11265) +* fix(android): clear globe highlight when displaying keyboard picker (#11826) +* refactor(linux): add KeymanOption class for options :checkered_flag: (#11850) +* refactor(linux): rename methods that deal with keyboard options :checkered_flag: (#11851) + +## 18.0.60 alpha 2024-06-21 + +* chore(web): define common timeout variable for automated testing (#11839) +* feat(developer): warn on empty keycaps (#11810) +* change(web): optimization for keyboard-layout preprocessing (#11263) +* feat(web): optimization via lazy construction of OSK layers (#11264) +* fix(developer): layr: fix modifier err message on layer w/o id (#11843) + +## 18.0.59 alpha 2024-06-19 + +* chore(deps-dev): bump braces from 3.0.2 to 3.0.3 (#11756) +* chore(developer): clarify project upgrade messages about file locations (#11819) +* chore(deps): bump ws from 8.16.0 to 8.17.1 (#11822) +* chore(common): update base-package node-engine setting (#11798) +* change(web): change after-word whitespace check to be more selective (#11800) +* chore: Revert "chore(common): update base-package node-engine setting" (#11829) +* change(web): drop correction batching (#11768) +* chore(web): move correction-search execution timer to its own file (#11757) +* refactor(web): overhaul predictive-text engine's timer to better detect paused time (#11758) +* feat(web): improve predictive-text responsiveness when typing rapidly (#11784) +* feat(linux): re-create missing files at run-time (#11789) + +## 18.0.58 alpha 2024-06-18 + +* test(core): Add a minimal test that exercises the core API (#11781) +* fix(developer): check HISTORY.md to get last modified date for keyboard_info and model_info (#11805) +* docs(developer): extra context help for keyboard-editor (does not exist in Keyman Developer 17.0) (#11771) + +## 18.0.57 alpha 2024-06-17 + +* fix(web): fix id of longpress keys with modifier set in touch layout (#11783) +* fix(web): prevent desktop OSK crash when addKeyboards is called before engine init (#11786) +* fix(core): serialize tests for core/wasm on mac agents (#11795) +* fix(developer): refactor kmcmplib compiler messages to use map (#11738) +* fix(developer): make native compilation of kmcmplib under Linux possible (#11779) +* feat(core): devolve regex to javascript (#11777) +* feat(core): remove ICU from core under wasm (#11778) +* fix(linux): restart ibus after manual integration test run (#11775) + +## 18.0.56 alpha 2024-06-14 + +* feat(core): devolve normalization to js (#11541) +* fix(developer): show message if no more platforms to add to touch layout editor (#11759) +* docs(developer): context help in package-editor and put the existing context help in their own tab comments (#11760) +* docs(developer): context help in keyboard-editor section (#11754) +* docs(developer): context help in new-project section (#11767) +* docs(developer): context help for new-project-parameters in keyman developer (#11769) +* docs(developer): context help for Select BCP 47 tag in Keyman Developer (#11770) +* change(common): update esbuild to 0.18.9 (#11693) +* change(web): more prep for better async prediction handling (#10347) +* fix(web): set new-context rules' device to match that of the active OSK (#11743) +* chore(linux): Update debian changelog (#11671) +* fix(web): add limited Array.from polyfill for lm-worker use (#11732) + +## 18.0.55 alpha 2024-06-13 + +* fix(developer): handle missing OSK when importing a Windows keyboard into a touch-only project (#11720) +* fix(developer): verify email addresses in .kps and .keyboard_info (#11735) +* change(web): prep for better asynchronous prediction handling (#10343) + +## 18.0.54 alpha 2024-06-12 + +* fix(common): remove subpackage entries for older TS version (#11745) +* chore(common): end use of ts-node (#11746) +* feat(web): add bulk_render variant that loads and renders keyboards from local KMP (#10432) + ## 18.0.53 alpha 2024-06-10 * fix(android): check current orientation when redisplaying system keyboard (#11604) @@ -109,7 +314,7 @@ * fix(web): explicitly terminate banner gesture-handling when banner is swapped (#11599) * chore(web): removes unused locals, imports, and private fields (#11460) * fix(web): use correct parameter name in button UI OSK `hide` event (#11600) -* (#11444) +* chore(mac): rework of main build script (#11444) * fix(ios): do not write to shared storage from system keyboard (#11613) * fix(developer): handle invalid default project path in options (#11555) * fix(developer): handle missing data in .kps `` (#11563) @@ -194,7 +399,7 @@ ## 18.0.35 alpha 2024-05-14 -* (#11340) +* chore(core): update core to C++17 (#11340) ## 18.0.34 alpha 2024-05-13 @@ -347,6 +552,41 @@ * chore(common): move to 18.0 alpha (#10713) * chore: move to 18.0 alpha +## 17.0.327 stable 2024-07-25 + +* fix(android): include DOMRect polyfill for older ES6-supporting devices (#11654) +* fix(web): Don't apply suggestion unless fully configured (#11636) +* fix(mac): handle command keys without crashing (#11675) +* fix(web): get row-height for flick constraints after performing layout (#11692) +* fix(android): handle IllegalArgumentException when initializing CloudDownloadMgr, add logging to check for unhandled side-effects (#11628) +* fix(developer): handle editor initializing after debugger when setting execution point (#11588) +* fix(developer): treat js files with unrecognized encodings as non-keyboard files (#11699) +* fix(developer): disable example edit controls if no examples in Package Editor (#11703) +* chore(developer): add extra logging for assertion failure when pressing backspace in debugger (#11709) +* fix(developer): handle encoding errors when loading wordlists (#11712) +* fix(mac): change build configuration to prevent cycle error in Xcode 15 (#11731) +* fix(developer): handle missing OSK when importing a Windows keyboard into a touch-only project (#11721) +* fix(developer): prevent two touch layout editors opening for the same file (#11727) +* fix(android): check current orientation, fix keyboard size after system keyboard rotations and resumes (#11747) +* chore(linux): Update debian changelog (#11670) +* feat(developer): support language reference in context help (#11741) +* fix(developer): show message if no more platforms to add to touch layout editor (#11766) +* fix(web): add limited Array.from polyfill for lm-worker use (#11733) +* fix(web): set new-context rules' device to match that of the active OSK (#11744) +* fix(web): prevent desktop OSK crash when addKeyboards is called before engine init (#11787) +* fix(windows): add -k parameter for keyboards build.sh (#11811) +* fix(core): serialize tests for core/wasm on mac agents (#11809) +* chore(developer): clarify project upgrade messages about file locations (#11820) +* fix(developer): check HISTORY.md to get last modified date for keyboard_info and model_info (#11808) +* fix(web): fix id of longpress keys with modifier set in touch layout (#11797) +* change(web): change after-word whitespace check to be more selective (#11824) +* fix(android): clear globe highlight when displaying keyboard picker (#11827) +* fix(web): use fat-finger data with simple keypresses (#11871) +* fix(developer): prevent non-BMP characters in key part of rule (#11807) +* fix(linux): ignore exceptions trying to install cache (#11885) +* chore(common): Update Crowdin strings for Portuguese (#11976) +* chore(linux): remove Ubuntu 23.10 Mantic (#12004) + ## 17.0.326 stable 2024-06-02 * cherrypick(android/engine): Handle globe key on lock screen (#11468) @@ -423,8 +663,8 @@ ## 17.0.317 beta 2024-05-01 -* (#11322) -* (#11321) +* chore(web): remove old reference-doc from alpha that has completed its purpose (#11322) +* fix(web): gesture-model initial-state, callback failure handling (#11321) * fix(linux): Fix icon for .kmp files (#11295) ## 17.0.316 beta 2024-04-30 diff --git a/VERSION.md b/VERSION.md index fe04a53f8ce..956e25b0baf 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.54 \ No newline at end of file +18.0.81 \ No newline at end of file diff --git a/android/.gitignore b/android/.gitignore index 4f0c3bb5018..a5720bccc73 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -37,8 +37,6 @@ KMEA/**/assets/keymanandroid.js KMEA/**/assets/keyman.js.map KMEA/**/assets/keymanweb-webview.js KMEA/**/assets/keymanweb-webview.js.map -KMEA/**/assets/keymanweb-webview.es5.js -KMEA/**/assets/keymanweb-webview.es5.js.map KMEA/**/assets/map-polyfill.js KMEA/**/assets/sentry.min.js KMEA/**/assets/keyman-sentry.js diff --git a/android/KMAPro/kMAPro/src/main/res/values-b+el/strings.xml b/android/KMAPro/kMAPro/src/main/res/values-b+el/strings.xml new file mode 100644 index 00000000000..43e43eb4ca9 --- /dev/null +++ b/android/KMAPro/kMAPro/src/main/res/values-b+el/strings.xml @@ -0,0 +1,164 @@ + + + + + Μοιρασθῆτε + + Φυλλομετρητής + + Μέγεθος κειμένου + + Περισσότερα + + Διαγραφὴ κειμένου + + Πληροφορίες + + Ρυθμίσεις + + Εγκατάσταση Ενημερώσεων + + Ἔκδοση %1$s + + Τὸ Keyman ἀπαιτεῖ ἔκδοση Chrome 57 ἢ νεώτερη. + + Ἐνημερῶστε τὸ Chrome + + Ἀρχίστε νὰ γράφετε ἐδῶ… + + + + Μέγεθος κειμένου: %1$d + + Μεγαλῶστε τὸ κείμενο + + Μεγαλῶστε τὸ κείμενο + + Ρύθμιση μεγέθους κειμένου + + \nΤὸ κείμενο θὰ ἐκκαθαρισθεῖ πλήρως\n + + Μικρύνετε τὸ κείμενο + + Προσθέστε πληκτρολόγιο γιὰ τὴν γλῶσσα σας + + Ὁρίστε τὸ Κῆμαν ὡς παν-συστημικὸ πληκτρολόγιο + + Ὁρίστε τὸ Κῆμαν ὡς προεπιλεγμένο πληκτρολόγιο + + Περισσότερες πληροφορίες + + Κατὰ τὴν ἔναρξη νὰ προβάλλεται τὸ \"%1$s + + Γιὰ νὰ ἐγκαταστήσετε πακέτα πληκτρολογίων, ἐπιτρέψτε στὸ Κῆμαν νὰ διαβάζει ἐξωτερικὸ χῶρο ἀποθήκευσης. + + Ἀπερρίφθη αἴτημα χώρου ἀποθήκευσης. Πιθανὴ ἀποτυχία ἐγκατάστασης πακέτου πληκτρολογίου + Ἀπερρίφθη αἴτημα χώρου ἀποθηκεύσεως. Δοκιμάστε τὶς ρυθμίσεις Κῆμαν - Ἐγκατάσταση ἀπὸ τοπικὸ ἀρχεῖο + + Ρυθμίσεις + + + Ἐγκατεστημένες γλῶσσες (%1$d) + Ἐγκατεστημένες γλῶσσες (%1$d) + + + Ἐγκαταστῆστε πληκτρολόγιο ἢ λεξικό + + Γλῶσσα προβολῆς + + Μεταβολὴ ὕψους πληκτρολογίου + + Spacebar caption + + Πληκτρολόγιο + + Γλῶσσα + + Γλῶσσα + Πληκτρολόγιο + + Κενό + + \'Ονομα πληκτρολογίου στὸ πλῆκτρο διαστήματος + + \'Ονομα γλώσσας στὸ πλῆκτρο διαστήματος + + \'Ονομα πληκτρολογίου καὶ γλώσσας στὸ πλῆκτρο διαστήματος + + Καμμία λεζάντα στὸ πλῆκτρο διαστήματος + + Δὸνηση κατὰ τὴν πληκτρολόγηση + + Νὰ ἐμφανίζεται πάντα banner + + Πρὸς ὑλοποίησιν + + Ὅταν εἶναι off, ἐμφανίζεται μόνο ὅταν ἔχει ἐνεργοποιηθεῖ τὸ προγνωστικὸ κείμενο + + Ἐπιτρέψτε τὴν ἀποστολὴ ἀναφορῶν κατάρρευσης μέσῳ δικτύου + + Ὅταν εἶναι ΟΝ, θὰ ἀποστέλλονται ἀναφορὲς κατάρρευσης + + Ὅταν εἶναι off, δὲν θὰ ἀποστέλλονται ἀναφορὲς κατάρρευσης + + Ἐγκατάσταση ἀπὸ τὸ keyman.com + + Ἐγκατάσταση ἀπὸ τοπικὸ ἀρχεῖο + + Ἐγκατάσταση ἀπὸ ἄλλη συσκευή + + Προσθέστε γλῶσσες σὲ ἐγκατεστημένο πληκτρολόγιο + + (ἀπὸ πακέτο πληκτρολογίου) + + Ἐπιλέξτε Πακέτο Πληκτρολογίου + + Ἐπιλέξτε γλῶσσες γιὰ τὸ %1$s + + Προσετέθη ἡ γλῶσσα %1$s στὸ %2$s + + Ὅλες οἱ γλῶσσες ἔχουν ἤδη ἐγκατασταθεῖ + + Σύρετε τὸ πληκτρολόγιο γιὰ νὰ ἀλλάξετε τὸ ὕψος + + Περιστρέψτε τὴν συσκευὴ γιὰ λειτουργία πορτραίτου καὶ τοπίου + + Ἐπαναφέρετε τὶς προεπιλεγμένες ρυθμίσεις + + Ἀναζητῆστε ἢ πληκτρολογῆστε URL + + Σελιδοδεῖκτες + + Δὲν ὑπάρχουν σελιδοδεῖκτες + + Προσθέστε σελιδοδείκτη + + Τίτλος + + URL + + Τὸ πακέτο %1$s ἀπέτυχε νὰ ἐγκατασταθεῖ + + Λήψη πακέτου πληκτρολογίου\n%1$s… + + Ἀποτυχία ἐξαγωγῆς + + Ἐγκαταστῆστε πληκτρολόγιο + + Ἐγκαταστῆστε Λεξικό + + Τὸ %1$s δὲν εἶναι ἔγκυρο ἀρχεῖο πακέτου Κῆμαν.\n%2$s\" + + Τὸ πακέτο πληκτρολογίου δὲν ἔχει βελτιστοποιημένα πληκτρολόγια ἀφῆς πρὸς ἐγκατάστασιν + + Δὲν ὑπάρχει νέο προγνωστικό κείμενο πρὸς ἐγκατάστασιν + + Δὲν ὑπάρχουν πληκτρολόγια ἢ προγνωστικό κείμενο πρὸς ἐγκατάστασιν + + Τὸ πακέτο πληκτρολογίου δὲν ἔχει σχετικὲς μὲ αὐτὸ γλῶσσες πρὸς ἐγκατάστασιν + + Ἄκυρα/ἐλλιπῆ μεταδεδομένα στὸ πακέτο + + Τὸ πληκτρολόγιο ἀπαιτεῖ νεώτερη ἔκδοση τοῦ Κῆμαν + + Ἀδυναμία ἐκκινήσεως φυλλομετρητῆ + diff --git a/android/KMAPro/kMAPro/src/main/res/values-pt-rPT/strings.xml b/android/KMAPro/kMAPro/src/main/res/values-pt-rPT/strings.xml index 3e3580f9311..02f1d9291de 100644 --- a/android/KMAPro/kMAPro/src/main/res/values-pt-rPT/strings.xml +++ b/android/KMAPro/kMAPro/src/main/res/values-pt-rPT/strings.xml @@ -1,5 +1,6 @@ + Compartilhar @@ -32,6 +33,8 @@ Tamanho do texto para cima Tamanho do texto para baixo + + Tamanho do texto deslizante \nTodo o texto será limpo\n @@ -50,6 +53,7 @@ Para instalar pacotes de teclado, permita que o Keyman leia o armazenamento externo. A solicitação de permissão de armazenamento foi negada. Pode falhar na instalação do pacote de teclado + Falha no pedido de permissão de armazenamento. Experimente instalar de um ficheiro local, nas configurações do Keyman Configurações @@ -82,7 +86,7 @@ Não mostrar legenda na barra de espaço - Vibrate when typing + Vibrar ao tocar Sempre mostrar banner @@ -154,7 +158,7 @@ Metadados inválidos/ausentes no pacote - Keyboard requires a newer version of Keyman + O teclado requer uma nova versão do Keyman - Unable to launch web browser + Não foi possível carregar o navegador diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index 39ce3a5f307..9c786e17770 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -37,11 +37,7 @@ function init() { keyman.beepKeyboard = beepKeyboard; // Readies the keyboard stub for instant loading during the init process. - try { - KeymanWeb.registerStub(JSON.parse(jsInterface.initialKeyboard())); - } catch(error) { - console.error(error); - } + KeymanWeb.registerStub(JSON.parse(jsInterface.initialKeyboard())); keyman.init({ 'embeddingApp':device, @@ -337,6 +333,14 @@ function menuKeyUp() { window.location.hash = hash; } +// The keyboard-picker displayed via Android longpress disrupts Web-side +// gesture-handling; this function helps force-clear the globe key's highlighting. +function clearGlobeHighlight() { + if(keyman.osk && keyman.osk.vkbd && keyman.osk.vkbd.currentLayer.globeKey) { + keyman.osk.vkbd.currentLayer.globeKey.highlight(false) + } +} + function hideKeyboard() { fragmentToggle = (fragmentToggle + 1) % 100; window.location.hash = 'hideKeyboard' + fragmentToggle; diff --git a/android/KMEA/app/src/main/assets/keyboard.es5.html b/android/KMEA/app/src/main/assets/keyboard.es5.html deleted file mode 100644 index 6384235456b..00000000000 --- a/android/KMEA/app/src/main/assets/keyboard.es5.html +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - Keyman - - - - - - - - - - - - - diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/DisplayLanguages.java b/android/KMEA/app/src/main/java/com/keyman/engine/DisplayLanguages.java index 27c462e95a0..6a159523a2e 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/DisplayLanguages.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/DisplayLanguages.java @@ -62,6 +62,7 @@ public static final DisplayLanguageType[] getDisplayLanguages(Context context) { new DisplayLanguageType("nl-NL", "Nederlands (Dutch)"), new DisplayLanguageType("ann", "Obolo"), new DisplayLanguageType("pl-PL", "Polski (Polish)"), + new DisplayLanguageType("el", "Polytonic Greek"), new DisplayLanguageType("pt-PT", "Português do Portugal"), new DisplayLanguageType("ff-ZA", "Pulaar-Fulfulde"), // or Fulah new DisplayLanguageType("ru-RU", "Pyccĸий (Russian)"), 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 6788c8f90e0..7d731f2b63d 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 @@ -313,6 +313,12 @@ public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float d public void onLongPress(MotionEvent event) { if (KMManager.getGlobeKeyState() == KMManager.GlobeKeyState.GLOBE_KEY_STATE_DOWN) { KMManager.setGlobeKeyState(KMManager.GlobeKeyState.GLOBE_KEY_STATE_LONGPRESS); + + // When we activate the keyboard picker, this will disrupt the JS-side's control + // flow for gesture-handling; we should pre-emptively clear the globe key, + // as Web will not receive a "globe key up" event. + loadJavascript("clearGlobeHighlight()"); + KMManager.handleGlobeKeyAction(context, true, keyboardType); return; /* For future implementation 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 d1a40be69ae..aa1c43626a7 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 @@ -295,9 +295,7 @@ public String toString() { // Keyman files protected static final String KMFilename_KeyboardHtml = "keyboard.html"; - protected static final String KMFilename_KeyboardHtml_Legacy = "keyboard.es5.html"; protected static final String KMFilename_JSEngine = "keymanweb-webview.js"; - protected static final String KMFilename_JSLegacyEngine = "keymanweb-webview.es5.js"; protected static final String KMFilename_JSSentry = "sentry.min.js"; protected static final String KMFilename_JSSentryInit = "keyman-sentry.js"; protected static final String KMFilename_AndroidHost = "android-host.js"; @@ -858,25 +856,11 @@ public static boolean copyHTMLBannerAssets(Context context, String path) { private static void copyAssets(Context context) { AssetManager assetManager = context.getAssets(); - // Will build a temp WebView in order to check Chrome version internally. - boolean legacyMode = WebViewUtils.getEngineWebViewVersionStatus(context, null, null) != WebViewUtils.EngineWebViewVersionStatus.FULL; - try { // Copy KMW files - if(legacyMode) { - // Replaces the standard ES6-friendly version of the host page with a legacy one that - // includes polyfill requests and that links the legacy, ES5-compatible version of KMW. - copyAssetWithRename(context, KMFilename_KeyboardHtml_Legacy, KMFilename_KeyboardHtml, "", true); - - copyAsset(context, KMFilename_JSLegacyEngine, "", true); - } else { - copyAsset(context, KMFilename_KeyboardHtml, "", true); - - // For versions of Chrome with full ES6 support, we use the ES6 artifact. - copyAsset(context, KMFilename_JSEngine, "", true); - } + copyAsset(context, KMFilename_KeyboardHtml, "", true); - // Is still built targeting ES5. + copyAsset(context, KMFilename_JSEngine, "", true); copyAsset(context, KMFilename_JSSentry, "", true); copyAsset(context, KMFilename_JSSentryInit, "", true); copyAsset(context, KMFilename_AndroidHost, "", true); @@ -887,12 +871,6 @@ private static void copyAssets(Context context) { // Copy default keyboard font copyAsset(context, KMDefault_KeyboardFont, "", true); - if(legacyMode) { - copyAsset(context, KMFilename_JSPolyfill, "", true); - copyAsset(context, KMFilename_JSPolyfill2, "", true); - copyAsset(context, KMFilename_JSPolyfill3, "", true); - } - // Keyboard packages directory File packagesDir = new File(getPackagesDir()); if (!packagesDir.exists()) { @@ -1638,7 +1616,7 @@ public static boolean removeKeyboard(Context context, int position) { public static boolean isDefaultKey(String key) { return ( - key != null && + key != null && key.equals(KMString.format("%s_%s", KMDefault_LanguageID, KMDefault_KeyboardID))); } @@ -2084,11 +2062,11 @@ public static Point getWindowSize(Context context) { wm.getDefaultDisplay().getSize(size); return size; } - + WindowMetrics windowMetrics = wm.getCurrentWindowMetrics(); return new Point( windowMetrics.getBounds().width(), - windowMetrics.getBounds().height()); + windowMetrics.getBounds().height()); } public static float getWindowDensity(Context context) { diff --git a/android/KMEA/app/src/main/res/values-b+el/strings.xml b/android/KMEA/app/src/main/res/values-b+el/strings.xml new file mode 100644 index 00000000000..871171d63f7 --- /dev/null +++ b/android/KMEA/app/src/main/res/values-b+el/strings.xml @@ -0,0 +1,172 @@ + + + + + + + Πληκτρολόγιο + Πληκτρολόγια + + + + Ἄλλη μέθοδος εἰσαγωγῆς + Ἄλλες Μέθοδοι Εἰσαγωγῆς + + + Προσθέστε νέο Πληκτρολόγιο + + Ἐγκατεστημένες γλῶσσες + + Ρυθμίσεις %1$s + + Προσθέστε + + Πίσω + + Ἀκυρῶστε + + Κλεῖστε + + Κλεῖστε τὸ Κῆμαν + + Προχωρῆστε + + Ἑπόμενη Μέθοδος Εἰσαγωγῆς + + Λήψη + + Ἐγκαταστῆστε + + Ἀργότερα + + Ἑπόμενο + + OK + + Ἐνημερῶστε + + Δὲν ὑπάρχει σύνδεση μὲ τὸ Διαδίκτυο + + Ἀδυναμία συνδέσεως μὲ τὸν διακομιστὴ τοῦ Κῆμαν! + + Θέλετε νὰ διαγράψετε αὐτὸ τὸ πληκτρολόγιο; + + Θὰ θέλατε νὰ κατεβάσετε τὴν τελευταία ἔκδοση αὐτοῦ τοῦ πληκτρολογίου; + + Θά θέλατε νὰ ἐνημερώσετε τώρα πληκτρολόγια καὶ λεξικά; + + Ἐνημερώσεις Πόρων + + Διαθέσιμες Ἐνημερώσεις Πόρων + + %1$s (Διαθέσιμη Ἐνημέρωση) + + Διαθέσιμες ἐνημερώσεις γιὰ τὸ πληκτρολόγιο %1$s: %2$s + + Διαθέσιμες ἐνημερώσεις γιὰ τὸ λεξικὸ %1$s: %2$s + + Ἔκδοση πληκτρολογίου + + Σύνδεσμος βοηθείας + + Ἀπεγκαταστῆστε πληκτρολόγιο + + [νέο] %1$s + + Σαρῶστε αὐτὸν τὸν κωδικό γιὰ νὰ φορτώσετε\nαὐτὸ τὸ πληκτρολόγιο σὲ ἄλλη συσκευή + + Καλωσορίσατε στὸ %1$s + + Ἀπαιτεῖται βιβλιοθήκη FileProvider γιὰ νὰ δεῖτε ἀρχεῖο βοηθείας: %1$s + + Μοιραῖο σφάλμα πληκτρολογίου στὸ %1$s:%2$s γιὰ τὴν %3$s γλῶσσα. Φορτώνεται προεπιλεγμένο πληκτρολόγιο. + + Error in keyboard %1$s:%2$s for %3$s language. + + Ἔλεγχος συσχετισμένου λεξικοῦ πρὸς λῆψιν + Ἀδυναμία συνδέσεως μὲ τὸν διακομιστὴ Κῆμαν γιὰ τὸν ἔλεγχο συσχετισμένου λεξικοῦ πρὸς λῆψιν + + Θὰ θέλατε νὰ κατεβάσετε τὴν τελευταία ἔκδοση αὐτοῦ τοῦ λεξικοῦ; + + Δὲν ὑπάρχει λεξικὸ πρὸς λῆψιν + + Μὴ διαθέσιμος κατάλογος πόρων + + Ἔχει ξεκινήσει ἐνημέρωση καταλόγου στὸ παρασκήνιο + + Ἡ λήψη τοῦ καταλόγου συνεχίζεται· παρακαλοῦμε ξαναδοκιμάστε σὲ λίγο! + + Ἔλεγχος πόρου σὲ ἐξέλιξη + + Ἡ λήψη τοῦ πληκτρολογίου ἔχει ξεκινήσει στὸ παρασκήνιο + + Ἡ λήψη τοῦ ἐπιλεγέντος πληκτρολογίου βρίσκεται σὲ ἐξέλιξη· παρακαλοῦμε ξαναδοκιμάστε σὲ λίγο! + + Ἡ λήψη τοῦ πληκτρολογίου ὁλοκληρώθηκε! + + Ἡ λήψη τοῦ λεξικοῦ ἔχει ξεκινήσει στὸ παρασκήνιο + + Ἡ λήψη τοῦ ἐπιλεγέντος λεξικοῦ βρίσκεται σὲ ἐξέλιξη· παρακαλοῦμε ξαναδοκιμάστε σὲ λίγο! + + Ἡ λήψη τοῦ λεξικοῦ ὁλοκληρώθηκε. + + Ἡ λήψη ἀπέτυχε + + Ἀποτυχία ἀνακτήσεως ληφθέντος ἀρχείου + + Ἀποτυχία προσβάσεως στὸν διακομιστή! + + "Ὅλοι οἱ πόροι ἔχουν ἐνημερωθεῖ!" + + Ἕνας ἢ περισσότεροι πόροι ἀπέτυχαν νὰ ἐνημερωθοῦν! + + Οἱ πόροι ἐνημερώθηκαν ἐπιτυχῶς! + + Ἔκδοση λεξικοῦ + + Ἀπεγκαταστῆτε λεξικό + + Θὰ θέλατε νὰ διαγράψετε αὐτὸ τὸ λεξικό; + + Τὸ λεξικὸ διεγράφη + + Τὸ πληκτρολόγιο %1$s ἐγκατεστάθη + + Τὸ πληκτρολόγιο διεγράφη + + Ἐνεργοποιῆστε τὶς διορθώσεις + + Ἐνεργοποιῆστε προβλέψεις + + Λεξικά + + Λεξικό + Λεξικά + + + Ἔλεγχος διαθεσίμου λεξικοῦ + Ἔλεγχος λεξικῶν ὀνλάϊν + + Λεξικό: %1$s + + %1$s λεξικά + + + Τὸ λεξικὸ ἐγκατεστάθη + + + (%1$d πληκτρολόγιο) + (%1$d πληκτρολόγια) + + + Προεπιλεγμένη Γλῶσσα + + + + Διαγράψτε + + + Κτυπῆστε ἐδῶ γιὰ νὰ ἀλλάξετε πληκτρολόγιο + + Ἀδυναμία ἐκκινήσεως φυλλομετρητῆ + diff --git a/android/KMEA/app/src/main/res/values-pt-rPT/strings.xml b/android/KMEA/app/src/main/res/values-pt-rPT/strings.xml index f66d5fa4443..f58f48dd2dc 100644 --- a/android/KMEA/app/src/main/res/values-pt-rPT/strings.xml +++ b/android/KMEA/app/src/main/res/values-pt-rPT/strings.xml @@ -45,7 +45,7 @@ Atualizar - No internet connection + Sem ligação à Internet Não foi possível conectar ao servidor Keyman! @@ -84,7 +84,7 @@ Erro no teclado %1$s:%2$s para %3$s idioma. Verificando o download do dicionário associado - Cannot connect to Keyman server to check for associated dictionary to download + A ligação ao servidor do Keyman para verificar dicionários para descarregar falhou Gostaria de baixar a versão mais recente deste dicionário? @@ -107,7 +107,7 @@ Download do dicionário iniciado em segundo plano O dicionário selecionado já está baixando; por favor, tente novamente mais tarde! - + O download do dicionário terminou. Download falhou @@ -119,7 +119,7 @@ "Todos os recursos estão atualizados!" Um ou mais recursos falharam ao atualizar! - + Recursos atualizados com sucesso! Versão do dicionário @@ -128,11 +128,11 @@ Você gostaria de excluir este dicionário? - Dictionary deleted + Dicionário eliminado Teclado %1$s instalado - Keyboard deleted + Teclado eliminado Habilitar correções @@ -140,12 +140,12 @@ Dicionário - Dictionary - Dictionaries + Dicionário + Dicionários Verificar dicionário disponível - Check for dictionaries online + Verifique dicionários online Dicionário: %1$s @@ -168,5 +168,5 @@ Toque aqui para alterar o teclado - Unable to launch web browser + Não foi possível carregar o navegador diff --git a/android/KMEA/build.sh b/android/KMEA/build.sh index ccf7c92887b..878b54797dc 100755 --- a/android/KMEA/build.sh +++ b/android/KMEA/build.sh @@ -77,8 +77,6 @@ if builder_start_action build:engine; then echo "Copying Keyman Web artifacts" cp "$KEYMAN_WEB_ROOT/build/app/webview/$CONFIG/keymanweb-webview.js" "$ENGINE_ASSETS/keymanweb-webview.js" cp "$KEYMAN_WEB_ROOT/build/app/webview/$CONFIG/keymanweb-webview.js.map" "$ENGINE_ASSETS/keymanweb-webview.js.map" - cp "$KEYMAN_WEB_ROOT/build/app/webview/$CONFIG/keymanweb-webview.es5.js" "$ENGINE_ASSETS/keymanweb-webview.es5.js" - cp "$KEYMAN_WEB_ROOT/build/app/webview/$CONFIG/keymanweb-webview.es5.js.map" "$ENGINE_ASSETS/keymanweb-webview.es5.js.map" cp "$KEYMAN_WEB_ROOT/build/app/webview/$CONFIG/map-polyfill.js" "$ENGINE_ASSETS/map-polyfill.js" cp "$KEYMAN_WEB_ROOT/build/app/resources/osk/ajax-loader.gif" "$ENGINE_ASSETS/ajax-loader.gif" cp "$KEYMAN_WEB_ROOT/build/app/resources/osk/kmwosk.css" "$ENGINE_ASSETS/kmwosk.css" diff --git a/common/models/templates/src/common.ts b/common/models/templates/src/common.ts index 9f3eae1dc1a..853bc7c18dd 100644 --- a/common/models/templates/src/common.ts +++ b/common/models/templates/src/common.ts @@ -115,10 +115,13 @@ export function transformToSuggestion(transform: Transform, p: number): WithOutc export function transformToSuggestion(transform: Transform, p?: number): Outcome { let suggestion: Outcome = { transform: transform, - transformId: transform.id, displayAs: transform.insert }; + if(transform.id !== undefined) { + suggestion.transformId = transform.id; + } + if(p === 0 || p) { suggestion.p = p; } diff --git a/common/models/templates/src/index.ts b/common/models/templates/src/index.ts index 52650ba9bb9..8563004fefa 100644 --- a/common/models/templates/src/index.ts +++ b/common/models/templates/src/index.ts @@ -2,7 +2,6 @@ export { SENTINEL_CODE_UNIT, applyTransform, buildMergedTransform, isHighSurrogate, isLowSurrogate, isSentinel, transformToSuggestion, defaultApplyCasing } from "./common.js"; -export { default as PriorityQueue, Comparator } from "./priority-queue.js"; export { default as QuoteBehavior } from "./quote-behavior.js"; export { Tokenization, tokenize, getLastPreCaretToken, wordbreak } from "./tokenization.js"; export { default as TrieModel, TrieModelOptions } from "./trie-model.js"; \ No newline at end of file diff --git a/common/models/templates/src/trie-model.ts b/common/models/templates/src/trie-model.ts index 0a571517c7f..9de2fb9050e 100644 --- a/common/models/templates/src/trie-model.ts +++ b/common/models/templates/src/trie-model.ts @@ -26,12 +26,11 @@ // Should probably make a 'lm-utils' submodule. // Allows the kmwstring bindings to resolve. -import { extendString } from "@keymanapp/web-utils"; +import { extendString, PriorityQueue } from "@keymanapp/web-utils"; import { default as defaultWordBreaker } from "@keymanapp/models-wordbreakers"; import { applyTransform, isHighSurrogate, isSentinel, SENTINEL_CODE_UNIT, transformToSuggestion } from "./common.js"; import { getLastPreCaretToken } from "./tokenization.js"; -import PriorityQueue from "./priority-queue.js"; extendString(); @@ -74,15 +73,6 @@ export interface TrieModelOptions { punctuation?: LexicalModelPunctuation; } -/** - * Used to determine the probability of an entry from the trie. - */ -type TextWithProbability = { - text: string; - // TODO: use negative-log scaling instead? - p: number; // real-number weight, from 0 to 1 -} - class Traversal implements LexiconTraversal { /** * The lexical prefix corresponding to the current traversal state. @@ -95,14 +85,75 @@ class Traversal implements LexiconTraversal { */ root: Node; - constructor(root: Node, prefix: string) { + /** + * The max weight for the Trie being 'traversed'. Needed for probability + * calculations. + */ + totalWeight: number; + + constructor(root: Node, prefix: string, totalWeight: number) { this.root = root; this.prefix = prefix; + this.totalWeight = totalWeight; + } + + child(char: USVString): LexiconTraversal | undefined { + /* + Note: would otherwise return the current instance if `char == ''`. If + such a call is happening, it's probably indicative of an implementation + issue elsewhere - let's signal now in order to catch such stuff early. + */ + if(char == '') { + return undefined; + } + + // Split into individual code units. + let steps = char.split(''); + let traversal: Traversal | undefined = this; + + while(steps.length > 0 && traversal) { + const step: string = steps.shift()!; + traversal = traversal._child(step); + } + + return traversal; + } + + // Handles one code unit at a time. + private _child(char: USVString): Traversal | undefined { + const root = this.root; + const totalWeight = this.totalWeight; + const nextPrefix = this.prefix + char; + + if(root.type == 'internal') { + let childNode = root.children[char]; + if(!childNode) { + return undefined; + } + + return new Traversal(childNode, nextPrefix, totalWeight); + } else { + // root.type == 'leaf'; + const legalChildren = root.entries.filter(function(entry) { + return entry.key.indexOf(nextPrefix) == 0; + }); + + if(!legalChildren.length) { + return undefined; + } + + return new Traversal(root, nextPrefix, totalWeight); + } } - *children(): Generator<{char: string, traversal: () => LexiconTraversal}> { + *children(): Generator<{char: USVString, traversal: () => LexiconTraversal}> { let root = this.root; + // We refer to the field multiple times in this method, and it doesn't change. + // This also assists minification a bit, since we can't minify when re-accessing + // through `this.`. + const totalWeight = this.totalWeight; + if(root.type == 'internal') { for(let entry of root.values) { let entryNode = root.children[entry]; @@ -120,7 +171,7 @@ class Traversal implements LexiconTraversal { let prefix = this.prefix + entry + lowSurrogate; yield { char: entry + lowSurrogate, - traversal: function() { return new Traversal(internalNode.children[lowSurrogate], prefix) } + traversal: function() { return new Traversal(internalNode.children[lowSurrogate], prefix, totalWeight) } } } } else { @@ -131,7 +182,7 @@ class Traversal implements LexiconTraversal { yield { char: entry, - traversal: function () {return new Traversal(entryNode, prefix)} + traversal: function () {return new Traversal(entryNode, prefix, totalWeight)} } } } else if(isSentinel(entry)) { @@ -143,7 +194,7 @@ class Traversal implements LexiconTraversal { let prefix = this.prefix + entry; yield { char: entry, - traversal: function() { return new Traversal(entryNode, prefix)} + traversal: function() { return new Traversal(entryNode, prefix, totalWeight)} } } } @@ -165,30 +216,41 @@ class Traversal implements LexiconTraversal { } yield { char: nodeKey, - traversal: function() { return new Traversal(root, prefix + nodeKey)} + traversal: function() { return new Traversal(root, prefix + nodeKey, totalWeight)} } }; return; } } - get entries(): string[] { + get entries() { + const entryMapper = (value: Entry) => { + return { + text: value.content, + p: value.weight / this.totalWeight + } + } + if(this.root.type == 'leaf') { let prefix = this.prefix; let matches = this.root.entries.filter(function(entry) { return entry.key == prefix; }); - return matches.map(function(value) { return value.content }); + return matches.map(entryMapper); } else { let matchingLeaf = this.root.children[SENTINEL_CODE_UNIT]; if(matchingLeaf && matchingLeaf.type == 'leaf') { - return matchingLeaf.entries.map(function(value) { return value.content }); + return matchingLeaf.entries.map(entryMapper); } else { return []; } } } + + get p(): number { + return this.root.weight / this.totalWeight; + } } /** @@ -286,7 +348,7 @@ export default class TrieModel implements LexicalModel { } public traverseFromRoot(): LexiconTraversal { - return new Traversal(this._trie['root'], ''); + return this._trie.traverseFromRoot(); } }; @@ -307,11 +369,10 @@ export default class TrieModel implements LexicalModel { type SearchKey = string & { _: 'SearchKey'}; /** - * The priority queue will always pop the most weighted item. There can only - * be two kinds of items right now: nodes, and entries; both having a weight - * attribute. + * The priority queue will always pop the most probable item - be it a Traversal + * state or a lexical entry reached via Traversal. */ -type Weighted = Node | Entry; +type TraversableWithProb = TextWithProbability | LexiconTraversal; /** * A function that converts a string (word form or query) into a search key @@ -367,9 +428,9 @@ interface Entry { * Wrapper class for the trie and its nodes. */ class Trie { - private root: Node; + public readonly root: Node; /** The total weight of the entire trie. */ - private totalWeight: number; + readonly totalWeight: number; /** * Converts arbitrary strings to a search key. The trie is built up of * search keys; not each entry's word form! @@ -382,6 +443,10 @@ class Trie { this.totalWeight = totalWeight; } + public traverseFromRoot(): LexiconTraversal { + return new Traversal(this.root, '', this.totalWeight); + } + /** * Lookups an arbitrary prefix (a query) in the trie. Returns the top 3 * results in sorted order. @@ -389,13 +454,26 @@ class Trie { * @param prefix */ lookup(prefix: string): TextWithProbability[] { - let searchKey = this.toKey(prefix); - let lowestCommonNode = findPrefix(this.root, searchKey); - if (lowestCommonNode === null) { + const searchKey = this.toKey(prefix); + const rootTraversal = this.traverseFromRoot().child(searchKey); + + if(!rootTraversal) { return []; } - return getSortedResults(lowestCommonNode, searchKey, this.totalWeight); + const directEntries = rootTraversal.entries; + // `Set` requires Chrome 38+, which is more recent than Chrome 35. + const directSet: Record = {}; + for(const entry of directEntries) { + directSet[entry.text] = entry.text; + } + + const bestEntries = getSortedResults(rootTraversal); + const deduplicated = bestEntries.filter((entry) => !directSet[entry.text]); + + // Any entries directly hosted on the current node should get full display + // priority over anything from its descendants. + return directEntries.concat(deduplicated); } /** @@ -403,36 +481,10 @@ class Trie { * @param n How many suggestions, maximum, to return. */ firstN(n: number): TextWithProbability[] { - return getSortedResults(this.root, '' as SearchKey, this.totalWeight, n); + return getSortedResults(this.traverseFromRoot(), n); } } -/** - * Finds the deepest descendent in the trie with the given prefix key. - * - * This means that a search in the trie for a given prefix has a best-case - * complexity of O(m) where m is the length of the prefix. - * - * @param key The prefix to search for. - * @param index The index in the prefix. Initially 0. - */ -function findPrefix(node: Node, key: SearchKey, index: number = 0): Node | null { - // An important note - the Trie itself is built on a per-JS-character basis, - // not on a UTF-8 character-code basis. - if (node.type === 'leaf' || index === key.length) { - return node; - } - - // So, for SMP models, we need to match each char of the supplementary pair - // in sequence. Each has its own node in the Trie. - let char = key[index]; - if (node.children[char]) { - return findPrefix(node.children[char], key, index + 1); - } - - return null; -} - /** * Returns all entries matching the given prefix, in descending order of * weight. @@ -441,72 +493,36 @@ function findPrefix(node: Node, key: SearchKey, index: number = 0): Node | null * @param results the current results * @param queue */ -function getSortedResults(node: Node, prefix: SearchKey, N: number, limit = MAX_SUGGESTIONS): TextWithProbability[] { - let queue = new PriorityQueue(function(a: Weighted, b: Weighted) { +function getSortedResults(traversal: LexiconTraversal, limit = MAX_SUGGESTIONS): TextWithProbability[] { + let queue = new PriorityQueue(function(a: TraversableWithProb, b: TraversableWithProb) { // In case of Trie compilation issues that emit `null` or `undefined` - return (b ? b.weight : 0) - (a ? a.weight : 0); + return (b ? b.p : 0) - (a ? a.p : 0); }); let results: TextWithProbability[] = []; - if (node.type === 'leaf') { - // Assuming the values are sorted, we can just add all of the values in the - // leaf, until we reach the limit. - for (let item of node.entries) { - // String.startsWith is not supported on certain Android (5.0) devices we wish to support. - // Requires a minimum of Chrome 36, as opposed to 5.0's default of 35. - if (item.key.indexOf(prefix) == 0) { - let { content, weight } = item; - results.push({ - text: content, - p: weight / N - }); - - if (results.length >= limit) { - return results; - } + queue.enqueue(traversal); + + while(queue.count > 0) { + const entry = queue.dequeue(); + + if((entry as TextWithProbability)!.text !== undefined) { + const lexicalEntry = entry as TextWithProbability; + results.push(lexicalEntry); + if(results.length >= limit) { + return results; } - } - } else { - queue.enqueue(node); - let next: Weighted | undefined; - - while (next = queue.dequeue()) { - if (isNode(next)) { - // When a node is next up in the queue, that means that next least - // likely suggestion is among its decsendants. - // So we search all of its descendants! - if (next.type === 'leaf') { - queue.enqueueAll(next.entries); - } else { - // XXX: alias `next` so that TypeScript can be SURE that internal is - // in fact an internal node. Because of the callback binding to the - // original definition of node (i.e., a Node | Entry), this will not - // type-check otherwise. - let internal = next; - queue.enqueueAll(next.values.map(char => { - return internal.children[char]; - })); - } - } else { - // When an entry is up next in the queue, we just add its contents to - // the results! - results.push({ - text: next.content, - p: next.weight / N - }); - if (results.length >= limit) { - return results; - } + } else { + const traversal = entry as LexiconTraversal; + queue.enqueueAll(traversal.entries); + let children: LexiconTraversal[] = [] + for(let child of traversal.children()) { + children.push(child.traversal()); } + queue.enqueueAll(children); } } - return results; -} - -/** TypeScript type guard that returns whether the thing is a Node. */ -function isNode(x: Entry | Node): x is Node { - return 'type' in x; + return results; } /** diff --git a/common/models/templates/test/test-trie-traversal.js b/common/models/templates/test/test-trie-traversal.js index d3d04963165..09237ee9a0e 100644 --- a/common/models/templates/test/test-trie-traversal.js +++ b/common/models/templates/test/test-trie-traversal.js @@ -13,6 +13,12 @@ var smpForUnicode = function(code){ return String.fromCharCode(H, L); } +// Prob: entry weight / total weight +// "the" is the highest-weighted word in the fixture. +const PROB_OF_THE = 1000 / 500500; +const PROB_OF_TRUE = 607 / 500500; +const PROB_OF_TROUBLE = 267 / 500500; + describe('Trie traversal abstractions', function() { it('root-level iteration over child nodes', function() { var model = new TrieModel(jsonFixture('tries/english-1000')); @@ -21,7 +27,11 @@ describe('Trie traversal abstractions', function() { assert.isDefined(rootTraversal); let rootKeys = ['t', 'o', 'a', 'i', 'w', 'h', 'f', 'b', 'n', 'y', 's', 'm', - 'u', 'c', 'd', 'l', 'e', 'j', 'p', 'g', 'v', 'k', 'r', 'q'] + 'u', 'c', 'd', 'l', 'e', 'j', 'p', 'g', 'v', 'k', 'r', 'q']; + + rootKeys.forEach((entry) => assert.isOk(rootTraversal.child(entry))); + assert.isNotOk(rootTraversal.child('x')); + assert.isNotOk(rootTraversal.child('z')); for(let child of rootTraversal.children()) { let keyIndex = rootKeys.indexOf(child.char); @@ -50,6 +60,7 @@ describe('Trie traversal abstractions', function() { assert.isDefined(traversalInner1); assert.isArray(child.traversal().entries); assert.isEmpty(child.traversal().entries); + assert.equal(traversalInner1.p, PROB_OF_THE); for(let tChild of traversalInner1.children()) { if(tChild.char == 'h') { @@ -58,19 +69,28 @@ describe('Trie traversal abstractions', function() { assert.isDefined(traversalInner2); assert.isEmpty(tChild.traversal().entries); assert.isArray(tChild.traversal().entries); + assert.equal(traversalInner2.p, PROB_OF_THE); for(let hChild of traversalInner2.children()) { if(hChild.char == 'e') { eSuccess = true; let traversalInner3 = hChild.traversal(); assert.isDefined(traversalInner3); - assert.isDefined(traversalInner3.entries); - assert.equal(traversalInner3.entries[0], "the"); + assert.deepEqual(traversalInner3.entries, [ + { + text: "the", + p: PROB_OF_THE + } + ]); + assert.equal(traversalInner3.p, PROB_OF_THE); for(let eChild of traversalInner3.children()) { let keyIndex = eKeys.indexOf(eChild.char); assert.notEqual(keyIndex, -1, "Did not find char '" + eChild.char + "' in array!"); + + // THE is not accessible if any of the sub-tries of our 'e' node (traversalInner3). + assert.isBelow(eChild.traversal().p, PROB_OF_THE); eKeys.splice(keyIndex, 1); } } @@ -87,6 +107,38 @@ describe('Trie traversal abstractions', function() { assert.isEmpty(eKeys); }); + it('direct traversal with simple internal nodes', function() { + var model = new TrieModel(jsonFixture('tries/english-1000')); + + let rootTraversal = model.traverseFromRoot(); + assert.isDefined(rootTraversal); + + let eKeys = ['y', 'r', 'i', 'm', 's', 'n', 'o']; + + const tNode = rootTraversal.child('t'); + assert.isOk(tNode); + assert.isDefined(tNode); + assert.isArray(tNode.entries); + assert.isEmpty(tNode.entries); + + const hNode = tNode.child('h'); + assert.isOk(hNode); + assert.isDefined(hNode); + assert.isArray(hNode.entries); + assert.isEmpty(hNode.entries); + + const eNode = hNode.child('e'); + assert.isOk(eNode); + assert.isDefined(eNode); + assert.isArray(eNode.entries); + assert.isNotEmpty(eNode.entries); + assert.equal(eNode.entries[0].text, "the"); + + for(let key of eKeys) { + assert.isOk(eNode.child(key)); + } + }); + it('traversal over compact leaf node', function() { var model = new TrieModel(jsonFixture('tries/english-1000')); @@ -102,6 +154,7 @@ describe('Trie traversal abstractions', function() { assert.isDefined(traversalInner1); assert.isArray(child.traversal().entries); assert.isEmpty(child.traversal().entries); + assert.equal(traversalInner1.p, PROB_OF_THE); for(let tChild of traversalInner1.children()) { if(tChild.char == 'r') { @@ -109,6 +162,7 @@ describe('Trie traversal abstractions', function() { assert.isDefined(traversalInner2); assert.isArray(tChild.traversal().entries); assert.isEmpty(tChild.traversal().entries); + assert.equal(traversalInner2.p, PROB_OF_TRUE); for(let rChild of traversalInner2.children()) { if(rChild.char == 'o') { @@ -137,10 +191,17 @@ describe('Trie traversal abstractions', function() { if(leafChildSequence.length > 0) { assert.isArray(curChild.traversal().entries); assert.isEmpty(curChild.traversal().entries); + assert.equal(curChild.traversal().p, PROB_OF_TROUBLE); } else { let finalTraversal = curChild.traversal(); + assert.equal(finalTraversal.p, PROB_OF_TROUBLE); assert.isDefined(finalTraversal.entries); - assert.equal(finalTraversal.entries[0], 'trouble'); + assert.deepEqual(finalTraversal.entries, [ + { + text: 'trouble', + p: PROB_OF_TROUBLE + } + ]); eSuccess = true; } } while (leafChildSequence.length > 0); @@ -154,7 +215,6 @@ describe('Trie traversal abstractions', function() { assert.isTrue(eSuccess); }); - it('traversal with SMP entries', function() { // Two entries, both of which read "apple" to native English speakers. // One solely uses SMP characters, the other of which uses a mix of SMP and standard. @@ -179,18 +239,20 @@ describe('Trie traversal abstractions', function() { for(let child of rootTraversal.children()) { if(child.char == smpA) { aSuccess = true; - let traversalInner1 = child.traversal(); + const traversalInner1 = child.traversal(); assert.isDefined(traversalInner1); - assert.isArray(child.traversal().entries); - assert.isEmpty(child.traversal().entries); + assert.isArray(traversalInner1.entries); + assert.isEmpty(traversalInner1.entries); + assert.equal(traversalInner1.p, 0.5); // The two entries are equally weighted. for(let aChild of traversalInner1.children()) { if(aChild.char == smpP) { pSuccess = true; - let traversalInner2 = aChild.traversal(); + const traversalInner2 = aChild.traversal(); assert.isDefined(traversalInner2); - assert.isArray(aChild.traversal().entries); - assert.isEmpty(aChild.traversal().entries); + assert.isArray(traversalInner2.entries); + assert.isEmpty(traversalInner2.entries); + assert.equal(traversalInner2.p, 0.5); for(let pChild of traversalInner2.children()) { let keyIndex = pKeys.indexOf(pChild.char); @@ -198,10 +260,11 @@ describe('Trie traversal abstractions', function() { pKeys.splice(keyIndex, 1); if(pChild.char == 'p') { // We'll test traversal with the 'mixed' entry from here. - let traversalInner3 = pChild.traversal(); + const traversalInner3 = pChild.traversal(); assert.isDefined(traversalInner3); - assert.isArray(pChild.traversal().entries); - assert.isEmpty(pChild.traversal().entries); + assert.isArray(traversalInner3.entries); + assert.isEmpty(traversalInner3.entries); + assert.equal(traversalInner3.p, 0.5); // Now to handle the rest, knowing it's backed by a leaf node. let curChild = pChild; @@ -227,12 +290,20 @@ describe('Trie traversal abstractions', function() { // Conditional test - if that was not the final character, entries should be undefined. if(leafChildSequence.length > 0) { - assert.isArray(curChild.traversal().entries); - assert.isEmpty(curChild.traversal().entries); + const nextTraversal = curChild.traversal() + assert.isArray(nextTraversal.entries); + assert.isEmpty(nextTraversal.entries); + assert.equal(nextTraversal.p, 0.5); } else { let finalTraversal = curChild.traversal(); assert.isDefined(finalTraversal.entries); - assert.equal(finalTraversal.entries[0], smpA + smpP + 'pl' + smpE); + assert.deepEqual(finalTraversal.entries, [ + { + text: smpA + smpP + 'pl' + smpE, + p: 1/2 + } + ]); + assert.equal(finalTraversal.p, 0.5); eSuccess = true; } } while (leafChildSequence.length > 0); @@ -249,4 +320,52 @@ describe('Trie traversal abstractions', function() { assert.isEmpty(pKeys); }); + + it('direct traversal with SMP entries', function() { + // Two entries, both of which read "apple" to native English speakers. + // One solely uses SMP characters, the other of which uses a mix of SMP and standard. + var model = new TrieModel(jsonFixture('tries/smp-apple')); + + let rootTraversal = model.traverseFromRoot(); + assert.isDefined(rootTraversal); + + let smpA = smpForUnicode(0x1d5ba); + let smpP = smpForUnicode(0x1d5c9); + let smpL = smpForUnicode(0x1d5c5); + let smpE = smpForUnicode(0x1d5be); + + // Just to be sure our utility function is working right. + assert.equal(smpA + smpP + 'pl' + smpE, "𝖺𝗉pl𝖾"); + + let pKeys = ['p', smpP]; + let leafChildSequence = ['l', smpE]; + + const aNode = rootTraversal.child(smpA); + assert.isOk(aNode); + assert.isNotOk(rootTraversal.child('a')); + + const pNode1 = aNode.child(smpP); + assert.isOk(pNode1); + assert.isNotOk(aNode.child('p')); + + const pNode2 = pNode1.child('p'); + assert.isOk(pNode2); + assert.isOk(pNode1.child(smpP)); // Both exist for this step. + + const lNode = pNode2.child('l'); + assert.isOk(lNode); + assert.isNotOk(pNode2.child(smpL)); + + const eNode = lNode.child(smpE); + assert.isOk(eNode); + assert.isNotOk(lNode.child('e')); + + assert.deepEqual(eNode.entries, [ + { + text: smpA + smpP + 'pl' + smpE, + p: 1/2 + } + ]); + assert.equal(eNode.p, 0.5); + }); }); diff --git a/common/models/types/index.d.ts b/common/models/types/index.d.ts index 30aba3ba3d4..0d6223bc64a 100644 --- a/common/models/types/index.d.ts +++ b/common/models/types/index.d.ts @@ -19,13 +19,32 @@ declare type USVString = string; declare type CasingForm = 'lower' | 'initial' | 'upper'; +/** + * Represents one lexical entry and its probability.. + */ +type TextWithProbability = { + /** + * A lexical entry (word) offered by the model. + * + * Note: not the search-term keyed part. This will match the actual, unkeyed form. + */ + text: string; + + /** + * The probability of the lexical entry, directly based upon its frequency. + * + * A real-number weight, from 0 to 1. + */ + p: number; +} + /** * Used to facilitate edit-distance calculations by allowing the LMLayer to * efficiently search the model's lexicon in a Trie-like manner. */ declare interface LexiconTraversal { /** - * Provides an iterable pattern used to search for words with a prefix matching + * Provides an iterable pattern used to search for words with a 'keyed' prefix matching * the current traversal state's prefix when a new character is appended. Iterating * across `children` provides 'breadth' to a lexical search. * @@ -50,6 +69,20 @@ declare interface LexiconTraversal { */ children(): Generator<{char: USVString, traversal: () => LexiconTraversal}>; + /** + * Allows direct access to the traversal state that results when appending one + * or more codepoints encoded in UTF-16 to the current traversal state's prefix. + * This allows bypassing iteration among all legal child Traversals. + * + * If such a traversal state is not supported, returns `undefined`. + * + * Note: traversals navigate and represent the lexicon in its "keyed" state, + * as produced by use of the search-term keying function defined for the model. + * That is, if a model "keys" `è` to `e`, there will be no `è` child. + * @param char + */ + child(char: USVString): LexiconTraversal | undefined; + /** * Any entries directly keyed by the currently-represented lookup prefix. Entries and * children may exist simultaneously, but `entries` must always exist when no children are @@ -70,7 +103,14 @@ declare interface LexiconTraversal { * - prefix of 'crepe': ['crêpe', 'crêpé'] * - other examples: https://www.thoughtco.com/french-accent-homographs-1371072 */ - entries: USVString[]; + entries: TextWithProbability[]; + + // Note: `p`, not `maxP` - we want to see the same name for `this.entries.p` and `this.p` + /** + * Gives the probability of the highest-frequency lexical entry that is either a member or + * descendent of the represented trie `Node`. + */ + p: number; } /** @@ -294,6 +334,11 @@ declare interface Suggestion { * to the input text. Ex: 'keep', 'emoji', 'correction', etc. */ tag?: SuggestionTag; + + /** + * Set to true if this suggestion is a valid auto-accept target. + */ + autoAccept?: boolean } interface Reversion extends Suggestion { diff --git a/common/models/wordbreakers/src/default/index.ts b/common/models/wordbreakers/src/default/index.ts index 20e159ed198..ef50ab4b31c 100644 --- a/common/models/wordbreakers/src/default/index.ts +++ b/common/models/wordbreakers/src/default/index.ts @@ -274,7 +274,7 @@ export class BreakerContext { * @param chunk a chunk of text. Starts and ends at word boundaries. */ function isNonSpace(chunk: string, options?: DefaultWordBreakerOptions): boolean { - return !Array.from(chunk).map((char) => property(char, options)).every(wb => ( + return !chunk.split('').map((char) => property(char, options)).every(wb => ( wb === WordBreakProperty.CR || wb === WordBreakProperty.LF || wb === WordBreakProperty.Newline || diff --git a/common/predictive-text/unit_tests/headless/worker-trie-integration.js b/common/predictive-text/unit_tests/headless/worker-trie-integration.js index 8cd257878d1..70583a978ee 100644 --- a/common/predictive-text/unit_tests/headless/worker-trie-integration.js +++ b/common/predictive-text/unit_tests/headless/worker-trie-integration.js @@ -15,7 +15,7 @@ describe('LMLayer using the trie model', function () { beforeEach(function() { worker = Worker.constructInstance(); - lmLayer = new LMLayer(capabilities(), worker); + lmLayer = new LMLayer(capabilities(), worker, true); }); afterEach(function () { @@ -82,7 +82,8 @@ describe('LMLayer using the trie model', function () { var suggestions = rawSuggestions.filter(function skimKeepSuggestions(s) { return s.tag !== 'keep' }) - assert.isAtLeast(suggestions.length, 1) + assert.isAtLeast(rawSuggestions.length, 1); + assert.isAtLeast(suggestions.length, 1); // We SHOULD get 'naïve' suggested var topSuggestion = suggestions[0]; diff --git a/common/predictive-text/unit_tests/in_browser/cases/top-level-lmlayer.spec.ts b/common/predictive-text/unit_tests/in_browser/cases/top-level-lmlayer.spec.ts index bd55123bec6..2eb5c4d4275 100644 --- a/common/predictive-text/unit_tests/in_browser/cases/top-level-lmlayer.spec.ts +++ b/common/predictive-text/unit_tests/in_browser/cases/top-level-lmlayer.spec.ts @@ -1,10 +1,12 @@ import { assert } from 'chai'; import { LMLayer, Worker as WorkerBuilder } from "@keymanapp/lexical-model-layer/web"; + +import { DEFAULT_BROWSER_TIMEOUT } from '@keymanapp/common-test-resources/test-timeouts.mjs'; import { defaultCapabilities } from '../helpers.mjs'; describe('LMLayer', function () { - this.timeout(5000); + this.timeout(DEFAULT_BROWSER_TIMEOUT); describe('[[constructor]]', function () { it('should construct with a single argument', function () { diff --git a/common/predictive-text/unit_tests/in_browser/cases/worker-dummy-integration.spec.ts b/common/predictive-text/unit_tests/in_browser/cases/worker-dummy-integration.spec.ts index bc2ebb551ef..2ac5f62d004 100644 --- a/common/predictive-text/unit_tests/in_browser/cases/worker-dummy-integration.spec.ts +++ b/common/predictive-text/unit_tests/in_browser/cases/worker-dummy-integration.spec.ts @@ -1,6 +1,8 @@ import { assert } from 'chai'; import { LMLayer, Worker } from "@keymanapp/lexical-model-layer/web"; + +import { DEFAULT_BROWSER_TIMEOUT } from '@keymanapp/common-test-resources/test-timeouts.mjs'; import { defaultCapabilities } from '../helpers.mjs'; // Import assertions, even using 'with', aren't yet supported in Firefox's engine. @@ -20,7 +22,7 @@ let hazelModel; * of suggestions when loaded and return them sequentially. */ describe('LMLayer using dummy model', function () { - this.timeout(5000); + this.timeout(DEFAULT_BROWSER_TIMEOUT); before(async () => { let loc = document.location; @@ -34,6 +36,18 @@ describe('LMLayer using dummy model', function () { // Since Firefox can't do JSON imports quite yet. const hazelFixture = await fetch(new URL(`${domain}/resources/json/models/future_suggestions/i_got_distracted_by_hazel.json`)); hazelModel = await hazelFixture.json(); + hazelModel = hazelModel.map((set) => set.map((entry) => { + return { + ...entry, + // Dummy-model predictions all claim probability 1; there's no actual probability stuff + // used here. + 'lexical-p': 1, + // We're predicting from a single transform, not a distribution, so probability 1. + 'correction-p': 1, + // Multiply 'em together. + p: 1, + } + })); }); describe('Prediction', function () { diff --git a/common/predictive-text/unit_tests/in_browser/cases/worker-trie-integration.spec.ts b/common/predictive-text/unit_tests/in_browser/cases/worker-trie-integration.spec.ts index 00a50434db8..d551814dd96 100644 --- a/common/predictive-text/unit_tests/in_browser/cases/worker-trie-integration.spec.ts +++ b/common/predictive-text/unit_tests/in_browser/cases/worker-trie-integration.spec.ts @@ -1,6 +1,7 @@ import { assert } from 'chai'; import { LMLayer, Worker } from "@keymanapp/lexical-model-layer/web"; +import { DEFAULT_BROWSER_TIMEOUT } from '@keymanapp/common-test-resources/test-timeouts.mjs'; import { defaultCapabilities } from '../helpers.mjs'; // Import assertions, even using 'with', aren't yet supported in Firefox's engine. @@ -12,7 +13,7 @@ let domain: string; * How to run the worlist */ describe('LMLayer using the trie model', function () { - this.timeout(5000); + this.timeout(DEFAULT_BROWSER_TIMEOUT); before(async () => { let loc = document.location; diff --git a/common/test/resources/json/models/future_suggestions/i_got_distracted_by_hazel.json b/common/test/resources/json/models/future_suggestions/i_got_distracted_by_hazel.json index a9aa49753b4..d01c7f5c5af 100644 --- a/common/test/resources/json/models/future_suggestions/i_got_distracted_by_hazel.json +++ b/common/test/resources/json/models/future_suggestions/i_got_distracted_by_hazel.json @@ -48,7 +48,7 @@ [ { "transform": { - "insert": "distracted ", + "insert": "distracted by ", "deleteLeft": 0 }, "displayAs": "distracted by" diff --git a/common/test/resources/model-helpers.mjs b/common/test/resources/model-helpers.mjs index 1de10bdb15e..b2ad083f96f 100644 --- a/common/test/resources/model-helpers.mjs +++ b/common/test/resources/model-helpers.mjs @@ -113,7 +113,18 @@ export function randomToken() { } export function iGotDistractedByHazel() { - return jsonFixture('models/future_suggestions/i_got_distracted_by_hazel'); + return jsonFixture('models/future_suggestions/i_got_distracted_by_hazel').map((set) => set.map((entry) => { + return { + ...entry, + // Dummy-model predictions all claim probability 1; there's no actual probability stuff + // used here. + 'lexical-p': 1, + // We're predicting from a single transform, not a distribution, so probability 1. + 'correction-p': 1, + // Multiply 'em together. + p: 1, + } + })); } export function jsonFixture(name, root, import_root) { diff --git a/common/test/resources/models/simple-dummy.js b/common/test/resources/models/simple-dummy.js index 97180729bd6..86da6494b0f 100644 --- a/common/test/resources/models/simple-dummy.js +++ b/common/test/resources/models/simple-dummy.js @@ -9,9 +9,7 @@ Model.punctuation = { quotesForKeepSuggestion: { open: '“', close: '”'}, - // Important! Set this, or else the model compositor will - // insert something for us! - insertAfterWord: "", + insertAfterWord: " ", }; // A direct import/copy from i_got_distracted_by_hazel.json. @@ -19,21 +17,21 @@ [ { "transform": { - "insert": "I ", + "insert": "I", "deleteLeft": 0 }, "displayAs": "I" }, { "transform": { - "insert": "I'm ", + "insert": "I'm", "deleteLeft": 0 }, "displayAs": "I'm" }, { "transform": { - "insert": "Oh ", + "insert": "Oh", "deleteLeft": 0 }, "displayAs": "Oh" @@ -42,21 +40,21 @@ [ { "transform": { - "insert": "love ", + "insert": "love", "deleteLeft": 0 }, "displayAs": "love" }, { "transform": { - "insert": "am ", + "insert": "am", "deleteLeft": 0 }, "displayAs": "am" }, { "transform": { - "insert": "got ", + "insert": "got", "deleteLeft": 0 }, "displayAs": "got" @@ -65,21 +63,21 @@ [ { "transform": { - "insert": "distracted ", + "insert": "distracted by", "deleteLeft": 0 }, "displayAs": "distracted by" }, { "transform": { - "insert": "distracted ", + "insert": "distracted", "deleteLeft": 0 }, "displayAs": "distracted" }, { "transform": { - "insert": "a ", + "insert": "a", "deleteLeft": 0 }, "displayAs": "a" @@ -88,27 +86,27 @@ [ { "transform": { - "insert": "Hazel ", + "insert": "Hazel", "deleteLeft": 0 }, "displayAs": "Hazel" }, { "transform": { - "insert": "the ", + "insert": "the", "deleteLeft": 0 }, "displayAs": "the" }, { "transform": { - "insert": "a ", + "insert": "a", "deleteLeft": 0 }, "displayAs": "a" } ] - ]; + ]; return Model; }()); diff --git a/common/test/resources/test-timeouts.mjs b/common/test/resources/test-timeouts.mjs new file mode 100644 index 00000000000..2c5bfd5f2cb --- /dev/null +++ b/common/test/resources/test-timeouts.mjs @@ -0,0 +1 @@ +export const DEFAULT_BROWSER_TIMEOUT = 5000; //ms \ No newline at end of file diff --git a/common/test/resources/timeout-adapter.js b/common/test/resources/timeout-adapter.js deleted file mode 100644 index 0e93ec94a95..00000000000 --- a/common/test/resources/timeout-adapter.js +++ /dev/null @@ -1,43 +0,0 @@ -// Preprocessing of the Karma configuration's client.args parameter. - -var com = com || {}; -com.keyman = com.keyman || {}; -com.keyman.karma = com.keyman.karma || {}; - -(function() { - const testconfig = window['testconfig'] = {}; - - // Default value. - let mobile = false; - - // If we've set things up to support Device dection without loading KMW... - if(com.keyman.Device) { - try { - let device = new com.keyman.Device(); - device.detect(); - - mobile = (device.formFactor != 'desktop'); - } finally { - // no-op; silent failure's fine here. - } - } - - let configArgs = window['__karma__'].config.args; // Where Karma gives us our custom args. - for(var i = 0; i < configArgs.length; configArgs++) { - switch(configArgs[i].type) { - case 'timeouts': - var timeouts = JSON.parse(JSON.stringify(configArgs[i])); - delete timeouts.type; - - if(mobile) { - for(var key in timeouts) { - if(key != 'mobileFactor') { - timeouts[key] = timeouts[key] * timeouts['mobileFactor']; - } - } - } - testconfig['timeouts'] = timeouts; - break; - } - } -})(); diff --git a/common/tools/sourcemap-path-remapper/tsconfig.json b/common/tools/sourcemap-path-remapper/tsconfig.json index e207097f614..35744ee9887 100644 --- a/common/tools/sourcemap-path-remapper/tsconfig.json +++ b/common/tools/sourcemap-path-remapper/tsconfig.json @@ -10,7 +10,7 @@ "inlineSources": true, "sourceRoot": "/common/tools/sourcemap-path-remapper/src", "lib": ["dom", "es6"], - "target": "es5", + "target": "es6", "types": ["node"], "downlevelIteration": true, "baseUrl": "./", diff --git a/common/web/es-bundling/build.sh b/common/web/es-bundling/build.sh index cd6b975255a..768f41fa2fd 100755 --- a/common/web/es-bundling/build.sh +++ b/common/web/es-bundling/build.sh @@ -11,7 +11,6 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ################################ Main script ################################ builder_describe "Builds KMW's esbuild-oriented common configuration & tooling" \ - "@/common/web/tslib" \ "clean" \ "configure" \ "build" @@ -24,4 +23,4 @@ builder_parse "$@" builder_run_action configure verify_npm_setup builder_run_action clean rm -rf build/ -builder_run_action build tsc -b tsconfig.json \ No newline at end of file +builder_run_action build tsc -b tsconfig.json diff --git a/common/web/es-bundling/src/common-bundle.mts b/common/web/es-bundling/src/common-bundle.mts index d99a7bfc644..fb3dfb95a44 100644 --- a/common/web/es-bundling/src/common-bundle.mts +++ b/common/web/es-bundling/src/common-bundle.mts @@ -12,7 +12,7 @@ let profilePath; let sourceRoot; let platform; -let jsVersionTarget='es5'; +let jsVersionTarget='es6'; function doHelp(errCode?: number) { console.log(` @@ -141,4 +141,4 @@ const results = await esbuild.build(config); if(results.metafile) { let filesizeProfile = await esbuild.analyzeMetafile(results.metafile, { verbose: true }); fs.writeFileSync(profilePath, filesizeProfile); -} \ No newline at end of file +} diff --git a/common/web/es-bundling/src/configuration.mts b/common/web/es-bundling/src/configuration.mts index 4978aa73c77..0c7a724a416 100644 --- a/common/web/es-bundling/src/configuration.mts +++ b/common/web/es-bundling/src/configuration.mts @@ -2,16 +2,13 @@ import type * as esbuild from 'esbuild'; import { pluginForDowncompiledClassTreeshaking } from './classTreeshaker.mjs'; export const esmConfiguration: esbuild.BuildOptions = { - alias: { - 'tslib': '@keymanapp/tslib' - }, bundle: true, format: "esm", outExtension: { '.js': '.mjs'}, plugins: [ pluginForDowncompiledClassTreeshaking ], sourcemap: true, sourcesContent: true, - target: "es5" + target: "es6" }; export const iifeConfiguration: esbuild.BuildOptions = { @@ -70,4 +67,4 @@ export function bundleObjEntryPoints(configFolder: 'lib' | 'debug' | 'release', entryPoints: path, outdir: mappedRoot }; -} \ No newline at end of file +} diff --git a/common/web/gesture-recognizer/src/test/auto/browser/cases/canary.def.ts b/common/web/gesture-recognizer/src/test/auto/browser/cases/canary.def.ts index a17311bec82..241977b3221 100644 --- a/common/web/gesture-recognizer/src/test/auto/browser/cases/canary.def.ts +++ b/common/web/gesture-recognizer/src/test/auto/browser/cases/canary.def.ts @@ -7,8 +7,10 @@ import { InputSequenceSimulator } from '#tools'; +import { DEFAULT_BROWSER_TIMEOUT } from '@keymanapp/common-test-resources/test-timeouts.mjs'; + describe("'Canary' checks", function() { - this.timeout(5000); + this.timeout(DEFAULT_BROWSER_TIMEOUT); let domain: string; diff --git a/common/web/gesture-recognizer/src/test/auto/browser/cases/ignoredInputs.def.ts b/common/web/gesture-recognizer/src/test/auto/browser/cases/ignoredInputs.def.ts index e6fb6ad4309..b107fdea5d2 100644 --- a/common/web/gesture-recognizer/src/test/auto/browser/cases/ignoredInputs.def.ts +++ b/common/web/gesture-recognizer/src/test/auto/browser/cases/ignoredInputs.def.ts @@ -8,8 +8,10 @@ import { SequenceRecorder } from '#tools'; +import { DEFAULT_BROWSER_TIMEOUT } from '@keymanapp/common-test-resources/test-timeouts.mjs'; + describe("Layer one - DOM -> InputSequence", function() { - this.timeout(5000); + this.timeout(DEFAULT_BROWSER_TIMEOUT); let controller: HostFixtureLayoutController; diff --git a/common/web/input-processor/src/corrections.ts b/common/web/input-processor/src/corrections.ts index fa9b77036fe..39c5ae2ef37 100644 --- a/common/web/input-processor/src/corrections.ts +++ b/common/web/input-processor/src/corrections.ts @@ -80,7 +80,12 @@ export function distributionFromDistanceMaps(squaredDistMaps: Map any; /** * Covers 'tryaccept' events. */ -export type TryUIHandler = (source: string) => boolean; +export type TryUIHandler = (source: string, returnObj: {shouldSwallow: boolean}) => boolean; export type InvalidateSourceEnum = 'new'|'context'; @@ -168,9 +168,9 @@ export default class LanguageProcessor extends EventEmitter { - let result = new ReadySuggestions(suggestions, transform.id); - this.emit("suggestionsready", result); - this.currentPromise = null; + let promise = this.currentPromise = this.lmEngine.revertSuggestion(reversion, new ContextWindow(original.preInput, this.configuration, null)) + // If the "current Promise" is as set above, clear it. + // If another one has been triggered since... don't. + promise.then(() => this.currentPromise = (this.currentPromise == promise) ? null : this.currentPromise); - return suggestions; - }); + return promise; } public predictFromTarget(outputTarget: OutputTarget, layerId: string): Promise { @@ -453,12 +450,13 @@ export default class LanguageProcessor extends EventEmitter; private swallowPrediction: boolean = false; @@ -85,9 +86,12 @@ export default class PredictionContext extends EventEmitter { + this.suggestionReverter = async (reversion) => { if(validSuggestionState()) { - langProcessor.applyReversion(reversion, this.currentTarget); + let suggestions = await langProcessor.applyReversion(reversion, this.currentTarget); + // We want to avoid altering flags that indicate our post-reversion state. + this.swallowPrediction = true; + this.updateSuggestions(new ReadySuggestions(suggestions, reversion.id ? -reversion.id : undefined)); } } @@ -107,10 +111,6 @@ export default class PredictionContext extends EventEmitter { - //let keyman = com.keyman.singleton; + private doTryAccept = (source: string, returnObj: {shouldSwallow: boolean}): void => { + const recentAcceptCause = this.recentAcceptCause; - if(!this.recentAccept && this.selected) { + if(!recentAcceptCause && this.selected) { this.accept(this.selected); - // returnObj.shouldSwallow = true; - } else if(this.recentAccept && source == 'space') { - this.recentAccept = false; - // // If the model doesn't insert wordbreaks, don't swallow the space. If it does, - // // we consider that insertion to be the results of the first post-accept space. - // returnObj.shouldSwallow = !!keyman.core.languageProcessor.wordbreaksAfterSuggestions; // can be handed outside + returnObj.shouldSwallow = true; + + // doTryAccept is the path for keystroke-based auto-acceptance. + // Overwrite the cause to reflect this. + this.recentAcceptCause = 'key'; + } else if(recentAcceptCause && source == 'space') { + this.recentAcceptCause = null; + if(recentAcceptCause == 'key') { + // No need to swallow the keystroke's whitespace; we triggered the prior acceptance + // FROM a space, so we've already aliased the suggestion's built-in space. + returnObj.shouldSwallow = false; + return; + } + + // Standard whitespace applications from the banner, those we DO want to + // swallow the first time. + // + // If the model doesn't insert wordbreaks, there's no space to alias, so + // don't swallow the space. If it does, we consider that insertion to be + // the results of the first post-accept space. + returnObj.shouldSwallow = !!this.langProcessor.wordbreaksAfterSuggestions; // can be handed outside } else { - // returnObj.shouldSwallow = false; + returnObj.shouldSwallow = false; } } @@ -250,9 +272,9 @@ export default class PredictionContext extends EventEmitter { // By default, we assume that the context is the same until we notice otherwise. this.initNewContext = false; + this.selected = null; if(!this.swallowPrediction || source == 'context') { - this.recentAccept = false; + this.recentAcceptCause = null; this.doRevert = false; this.recentRevert = false; @@ -299,7 +322,7 @@ export default class PredictionContext extends EventEmitter