diff --git a/.github/workflows/deb-packaging.yml b/.github/workflows/deb-packaging.yml index 2d72e01764..8cc431f178 100644 --- a/.github/workflows/deb-packaging.yml +++ b/.github/workflows/deb-packaging.yml @@ -105,10 +105,7 @@ jobs: strategy: fail-fast: true matrix: - # Currently not building mantic until ibus version on mantic stabilizied - # and we can provide a patched version - # dist: [focal, jammy, lunar, mantic] - dist: [focal, jammy, lunar] + dist: [focal, jammy, lunar, mantic] arch: [amd64] runs-on: ubuntu-latest @@ -222,7 +219,7 @@ jobs: echo "::endgroup::" api_verification: - name: Verify API for libkmnkbp0.so + name: Verify API for libkeymancore.so needs: [sourcepackage, binary_packages] runs-on: ubuntu-latest @@ -245,16 +242,17 @@ jobs: - name: Verify API run: | cd linux + PKG_NAME=libkeymancore SRC_PKG="${GITHUB_WORKSPACE}/artifacts/keyman-srcpkg/keyman_${{ needs.sourcepackage.outputs.VERSION }}-1.debian.tar.xz" \ - BIN_PKG="${GITHUB_WORKSPACE}/artifacts/keyman-binarypkgs/libkmnkbp0-0_${{ needs.sourcepackage.outputs.VERSION }}-1${{ needs.sourcepackage.outputs.PRERELEASE_TAG }}+jammy1_amd64.deb" \ + BIN_PKG="${GITHUB_WORKSPACE}/artifacts/keyman-binarypkgs/${PKG_NAME}_${{ needs.sourcepackage.outputs.VERSION }}-1${{ needs.sourcepackage.outputs.PRERELEASE_TAG }}+jammy1_amd64.deb" \ PKG_VERSION="${{ needs.sourcepackage.outputs.VERSION }}" \ ./scripts/deb-packaging.sh --gha verify 2>> $GITHUB_STEP_SUMMARY - name: Archive .symbols file uses: actions/upload-artifact@0b7f8abb1508181956e8e162db84b466c27e18ce # v3.1.2 with: - name: libkmnkbp0-0.symbols - path: linux/debian/libkmnkbp0-0.symbols + name: libkeymancore.symbols + path: linux/debian/libkeymancore.symbols if: always() set_status: diff --git a/HISTORY.md b/HISTORY.md index 7b2d722074..55a6f985e6 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,69 @@ # Keyman Version History +## 17.0.198 alpha 2023-10-25 + +* chore(common): Add entries from 16.0 HISTORY.md (#9826) +* feat(core): new actions APIs (#9828) + +## 17.0.197 alpha 2023-10-24 + +* chore(linux): Rename (lib)kmnkbp to (lib)keymancore ️ (#9793) +* chore(linux): Rename `namespace kbp` to `core` ️ (#9792) +* chore(linux): rename keyboardprocessor_ldml.* to keyman_core_ldml ️ (#9791) +* chore(linux): Rename libkmnkbp0-0 package ️ (#9795) +* fix(web): proper disabling of prediction timeout for prediction unit tests (#9835) + +## 17.0.196 alpha 2023-10-20 + +* fix(mac): move keyboard menu items to main Input Menu from submenu (#9777) +* (#9728) +* (#9759) +* docs(common): macos build update (#9809) +* fix(web): enhances integrated test stability (#9718) +* chore(linux): Update packaging GHA ️ (#9812) +* chore(linux): Rename KBP to CORE ️ (#9794) +* chore(linux): Fix build by adding one character (#9817) + +## 17.0.195 alpha 2023-10-19 + +* chore(linux): Allow to collect coverage on TC (#9790) +* fix(common): don't use URL in common/web/types (#9798) +* chore: update kmp.schema.json and docs for kps schema (#9800) +* docs(windows): update text and images for windows 11 (#9689) + +## 17.0.194 alpha 2023-10-18 + +* chore(linux): Re-enable building for Ubuntu 23.10 Mantic (#9780) +* chore(linux): Add missing tests (#9783) +* fix(web): fixes touch form-factor default kbd on cookieless keymanweb.com page load (#9786) +* fix(developer): three kmc .keyboard_info generation bugs (#9784) +* fix(developer): handle invalid project folders cleanly (#9785) +* chore(linux): Fix build scripts (#9781) + +## 17.0.193 alpha 2023-10-17 + +* fix(developer): kmc crash on start (#9771) +* fix(core): don't use double newlines in debuglog (#9258) +* chore(linux): Add code coverage index page (#9758) +* feat(linux): Allow installing keyboards with arbitrary language ️ (#9756) +* fix(linux): Fix crash initializing Sentry with Python < 3.10 (#9774) +* chore(linux): Rename `kbp_state_get_intermediate_context` to `km_core…` ️ (#9775) + +## 17.0.192 alpha 2023-10-16 + +* fix(ios): missing backslash in build script (#9765) +* chore(linux): Speed up ibus-util tests (#9754) +* feat(linux): Allow loading of keyboards with arbitrary language ️ (#9735) + +## 17.0.191 alpha 2023-10-15 + +* chore: update readme for keyboard_info schema (#9746) + +## 17.0.190 alpha 2023-10-12 + +* chore(web): web build streamlining (#9743) +* chore(linux): Add code coverage reports for keyman-config and keyman-system-service (#9753) + ## 17.0.189 alpha 2023-10-11 * refactor(linux): Use auto cleanup (and fix some memory leaks) (#9648) @@ -1099,6 +1163,22 @@ * chore: move to 17.0-alpha (#7577) * chore: Move to 17.0 alpha +## 16.0.142 stable 2023-10-20 + +* chore(linux): Update debian changelog (#9326) +* chore(linux): Remove obsolete dist from uploading to launchpad (#9340) +* chore(linux): Stop building Kinetic on Jenkins (#9354) +* chore(linux): Work around build failure on Debian mips64el (#9342) +* chore(linux): Update debian changelog (#9359) +* chore(android): Update targetSDKVersion to 33 (#9494) +* chore(linux): Remove workaround for mips64el (#9519) +* chore(linux): Workaround: Don't run Wayland tests (#9530) +* chore(linux): Add `clean` target to `rules` (#9532) +* fix(linux): Prevent exception if neither USER, LOGNAME nor SUDO_USER set (#9544) +* chore(linux): Ignore tests that fail on ba-jammy-64-ta (#9556) +* chore(ios): renew certificate (#9811) +* fix(mac): move keyboard menu items to main Input Menu from submenu (#9810) + ## 16.0.141 stable 2023-07-25 * chore(mac): fix corrupt installer (#9332) @@ -1486,6 +1566,7 @@ * fix(linux): Fix upload script (#7494) * docs(linux): Update sample vscode settings (#7491) * fix(android/engine): Dismiss key preview and subkeys on globe action (#7472) +* refactor(linux): Cleanup whitespace (#7500) * chore(linux): Improve debug output (#7499) ## 16.0.84 alpha 2022-10-19 @@ -1773,7 +1854,7 @@ ## 16.0.34 alpha 2022-07-15 * fix(developer): set contextDevice in web debugger ️ (#6906) -* fix(web): postkeystroke processing should ignore key-event source ️ (#6901) +* fix(web): postkeystroke processing should ignore key-event source (#6901) * fix(web): layer-setting ops should not trigger for hardware keystroke processing ️ (#6902) * fix(developer): kmdecomp virtual character key output (#6940) * fix(developer): crash on exit when checking for updates (#6941) diff --git a/VERSION.md b/VERSION.md index c92d133b7a..a2ed5131d5 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -17.0.190 \ No newline at end of file +17.0.199 \ No newline at end of file diff --git a/common/include/kmx_file.h b/common/include/kmx_file.h index 243e5e0d63..7dbc2261d0 100644 --- a/common/include/kmx_file.h +++ b/common/include/kmx_file.h @@ -25,10 +25,10 @@ typedef KMX_WORD __attribute__((aligned(1))) KMX_WORD_unaligned; #define KMX_WORD_unaligned KMX_WORD #endif -#ifdef KMN_KBP +#ifdef KM_CORE_LIBRARY // TODO: move this to a common namespace keyman::common::kmx_file or similar in the future namespace km { -namespace kbp { +namespace core { namespace kmx { #endif @@ -402,8 +402,8 @@ static_assert(sizeof(COMP_KEY) == KEYBOARDFILEKEY_SIZE, "COMP_KEY must be KEYBOA static_assert(sizeof(COMP_GROUP) == KEYBOARDFILEGROUP_SIZE, "COMP_GROUP must be KEYBOARDFILEGROUP_SIZE bytes"); static_assert(sizeof(COMP_KEYBOARD) == KEYBOARDFILEHEADER_SIZE, "COMP_KEYBOARD must be KEYBOARDFILEHEADER_SIZE bytes"); -#ifdef KMN_KBP +#ifdef KM_CORE_LIBRARY } // namespace kmx -} // namespace kbp +} // namespace core } // namespace km #endif diff --git a/common/schemas/keyboard_info/README.md b/common/schemas/keyboard_info/README.md index 1c318ff593..f41457f93e 100644 --- a/common/schemas/keyboard_info/README.md +++ b/common/schemas/keyboard_info/README.md @@ -12,7 +12,19 @@ Documentation at https://help.keyman.com/developer/cloud/keyboard_info # .keyboard_info version history ## 2023-08-11 2.0 stable -* Removed legacyId, documentationFilename, documentationFileSize. Source vs distribution keyboard_info distinction is removed. Example key sequences are simplified. +* Removed: + - `.documentationFilename` + - `.documentationFileSize` + - `.legacyId` + `.links` + `.related[].note` + `.languages[].example` + Added: + - `.languages[].examples[]` + Modified: + - `.languages[].font`, `.languages[].oskFont`: `.source` is `[string]` + - Source .keyboard_info files are no longer needed, so source vs distribution + keyboard_info distinction is removed ## 2019-09-06 1.0.6 stable * No changes (see api.keyman.com#36 and api.keyman.com#59. Reverted in 2020-06-10.). diff --git a/common/schemas/kmp/README.md b/common/schemas/kmp/README.md new file mode 100644 index 0000000000..6c8372b779 --- /dev/null +++ b/common/schemas/kmp/README.md @@ -0,0 +1,24 @@ +# kmp.schema.json + +* kmp.json file format, metadata included in Keyman .kmp package files + +Documentation at https://help.keyman.com/developer/current-version/reference/file-types/metadata + +# kmp.schema.json version history + +## 2023-10-19 2.0 +* Add relatedPackages, options.licenseFile, options.welcomeFile, + keyboard.examples, keyboard.webOskFonts, keyboard.webDisplayFonts, + info.description (all of these formerly were stored in .keyboard_info) + +## 2019-01-31 1.1.0 +* Add lexicalModels properties (note: `version` is optional and currently unused) + +## 2018-02-13 1.0.2 +* Add rtl property for keyboard layouts + +## 2018-01-22 1.0.1 +* Remove id field as it is derived from the filename anyway + +## 2017-11-30 1.0 beta +* Initial version diff --git a/common/schemas/kmp/kmp.schema.json b/common/schemas/kmp/kmp.schema.json new file mode 100644 index 0000000000..a28aa43195 --- /dev/null +++ b/common/schemas/kmp/kmp.schema.json @@ -0,0 +1,353 @@ +{ + "$schema": "http://json-schema.org/schema#", + "$ref": "#/definitions/package", + "definitions": { + "package": { + "type": "object", + "properties": { + "system": { + "$ref": "#/definitions/system" + }, + "options": { + "$ref": "#/definitions/options" + }, + "startMenu": { + "$ref": "#/definitions/startMenu" + }, + "strings": { + "$ref": "#/definitions/strings" + }, + "files": { + "$ref": "#/definitions/files" + }, + "keyboards": { + "$ref": "#/definitions/keyboards" + }, + "lexicalModels": { + "$ref": "#/definitions/lexicalModels" + }, + "info": { + "$ref": "#/definitions/info" + }, + "relatedPackages": { + "type": "array", + "items": { + "$ref": "#/definitions/relatedPackage" + } + } + }, + "additionalProperties": false, + "required": [ + "options", + "system" + ] + }, + "system": { + "type": "object", + "properties": { + "keymanDeveloperVersion": { + "type": "string" + }, + "fileVersion": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "fileVersion" + ] + }, + "options": { + "type": "object", + "properties": { + "readmeFile": { + "type": "string" + }, + "graphicFile": { + "type": "string" + }, + "licenseFile": { + "type": "string" + }, + "welcomeFile": { + "type": "string" + }, + "executeProgram": { + "type": "string" + }, + "msiFilename": { + "type": "string" + }, + "msiOptions": { + "type": "string" + } + }, + "additionalProperties": false + }, + "startMenu": { + "type": "object", + "properties": { + "folder": { + "type": "string" + }, + "addUninstallEntry": { + "type": "boolean" + }, + "items": { + "type": "array", + "items": { + "$ref": "#/definitions/startMenuItem" + } + } + }, + "additionalProperties": false + }, + "startMenuItem": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "filename": { + "type": "string" + }, + "arguments": { + "type": "string" + }, + "icon": { + "type": "string" + }, + "location": { + "type": "number" + } + }, + "additionalProperties": false, + "required": [ + "name", + "filename" + ] + }, + "strings": { + "type": "object", + "patternProperties": { + ".": { + "type": "string" + } + }, + "additionalProperties": false + }, + "files": { + "type": "array", + "items": { + "$ref": "#/definitions/file" + } + }, + "file": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "copyLocation": { + "type": "number" + } + }, + "additionalProperties": false, + "required": [ + "name", + "description" + ] + }, + "keyboards": { + "type": "array", + "items": { + "$ref": "#/definitions/keyboard" + } + }, + "keyboard": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "version": { + "type": "string" + }, + "oskFont": { + "type": "string" + }, + "displayFont": { + "type": "string" + }, + "rtl": { + "type": "boolean" + }, + "languages": { + "type": "array", + "items": { + "$ref": "#/definitions/keyboardLanguage" + } + }, + "examples": { + "type": "array", + "items": { + "$ref": "#/definitions/keyboardExample" + } + }, + "webOskFonts": { + "type": "array", + "items": { + "type": "string" + } + }, + "webDisplayFonts": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false, + "required": [ + "name", + "id", + "version" + ] + }, + "keyboardLanguage": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "id", + "name" + ] + }, + "info": { + "type": "object", + "properties": { + "website": { + "$ref": "#/definitions/infoItem" + }, + "version": { + "$ref": "#/definitions/infoItem" + }, + "name": { + "$ref": "#/definitions/infoItem" + }, + "copyright": { + "$ref": "#/definitions/infoItem" + }, + "author": { + "$ref": "#/definitions/infoItem" + }, + "description": { + "$ref": "#/definitions/infoItem" + } + }, + "additionalProperties": false + }, + "infoItem": { + "type": "object", + "properties": { + "description": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "description" + ] + }, + "lexicalModels": { + "type": "array", + "items": { + "$ref": "#/definitions/lexicalModel" + } + }, + "lexicalModel": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "id": { + "type": "string" + }, + "version": { + "type": "string" + }, + "rtl": { + "type": "boolean" + }, + "languages": { + "type": "array", + "items": { + "$ref": "#/definitions/keyboardLanguage" + } + } + }, + "additionalProperties": false, + "required": [ + "name", + "id", + "languages" + ] + }, + "keyboardExample": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "keys": { + "type": "string" + }, + "text": { + "type": "string" + }, + "note": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "id", + "keys" + ] + }, + "relatedPackage": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "relationship": { + "type": "string", + "enum": ["deprecates", "related"] + } + }, + "additionalProperties": false, + "required": [ + "id", + "relationship" + ] + } + } +} \ No newline at end of file diff --git a/common/schemas/kps/README.md b/common/schemas/kps/README.md index e4aacf97d8..aea03f2a5b 100644 --- a/common/schemas/kps/README.md +++ b/common/schemas/kps/README.md @@ -1,9 +1,22 @@ # kps.xsd -Master version: https://github.com/keymanapp/api.keyman.com/blob/master/schemas/kps/7.0/kps.xsd +Master version: https://github.com/keymanapp/api.keyman.com/blob/master/schemas/kps/17.0/kps.xsd + +## 2023-10-19 17.0 +* Version 17.0 adds: + - LicenseFile - a .md file, usually named LICENSE.md + - WelcomeFile - a .htm file, usually named welcome.htm (later versions will support .md) + - Info/Description - a short Markdown description of the content of the package, e.g. shown in search results on keyman.com + - RelatedPackages - a list of other packages which relate to this one, or are deprecated by it + - Keyboards/Keyboard/Examples - a list of typing examples for the keyboard + - Keyboarsd/Keyboard/WebOSKFonts - a list of font filenames (not necessarily in package) suitable for rendering the on screen keyboard + - Keyboarsd/Keyboard/WebDisplayFonts - a list of font filenames (not necessarily in package) suitable for use with the keyboard +* Version 17.0 removes: + - LexicalModels/LexicalModel/Version - version information is not stored in the models, but only in the package metadata (was unused) + +## 2023-04-21 7.0.1 +* Removes LexicalModel.Version, as it was never read or written ## 2021-07-19 7.0 * Initial version 7.0 -## 2023-04-21 7.0.1 -* Removes LexicalModel.Version, as it was never read or written diff --git a/common/web/lm-worker/src/test/cases/worker-custom-punctuation.js b/common/web/lm-worker/src/test/cases/worker-custom-punctuation.js index e0ff73d1c9..8114ac530a 100644 --- a/common/web/lm-worker/src/test/cases/worker-custom-punctuation.js +++ b/common/web/lm-worker/src/test/cases/worker-custom-punctuation.js @@ -36,7 +36,7 @@ describe('Custom Punctuation', function () { // The model compositor is responsible for adding this to the display as // string. - var composite = new ModelCompositor(model); + var composite = new ModelCompositor(model, true); var suggestions = composite.predict([{ sample: { insert: 'o', deleteLeft: 0 }, p: 1.00 }], { left: 'Hrll', startOfBuffer: false, endOfBuffer: true }); @@ -81,7 +81,7 @@ describe('Custom Punctuation', function () { // The model compositor is responsible for adding this to the display as // string. - var composite = new ModelCompositor(model); + var composite = new ModelCompositor(model, true); var suggestions = composite.predict([{ sample: { insert: 'ᚋ', deleteLeft: 0 }, p: 1.00 }], { left: '᚛ᚈᚑ', startOfBuffer: false, endOfBuffer: true }); diff --git a/common/web/lm-worker/src/test/cases/worker-model-compositor.js b/common/web/lm-worker/src/test/cases/worker-model-compositor.js index 3c2622be1f..7cbaa1e947 100644 --- a/common/web/lm-worker/src/test/cases/worker-model-compositor.js +++ b/common/web/lm-worker/src/test/cases/worker-model-compositor.js @@ -20,7 +20,7 @@ describe('ModelCompositor', function() { ); it('generates suggestions with expected properties', function() { - let compositor = new ModelCompositor(plainModel); + let compositor = new ModelCompositor(plainModel, true); let context = { left: 'th', startOfBuffer: true, endOfBuffer: true, }; @@ -56,7 +56,7 @@ describe('ModelCompositor', function() { }); it('strongly avoids corrections for single-character roots', function() { - let compositor = new ModelCompositor(plainModel); + let compositor = new ModelCompositor(plainModel, true); let context = { left: '', startOfBuffer: true, endOfBuffer: true, }; @@ -95,7 +95,7 @@ describe('ModelCompositor', function() { }); it('properly handles suggestions after a backspace', function() { - let compositor = new ModelCompositor(plainModel); + let compositor = new ModelCompositor(plainModel, true); let context = { left: 'the ', startOfBuffer: true, endOfBuffer: true, }; @@ -118,7 +118,7 @@ describe('ModelCompositor', function() { }); it('properly handles suggestions for the first letter after a ` `', function() { - let compositor = new ModelCompositor(plainModel); + let compositor = new ModelCompositor(plainModel, true); let context = { left: 'the', startOfBuffer: true, endOfBuffer: true, }; @@ -137,7 +137,7 @@ describe('ModelCompositor', function() { }); it('properly handles suggestions for the first letter after a `\'`', function() { - let compositor = new ModelCompositor(plainModel); + let compositor = new ModelCompositor(plainModel, true); let context = { left: "the '", startOfBuffer: true, endOfBuffer: true, }; @@ -186,7 +186,7 @@ describe('ModelCompositor', function() { ); it('properly cases suggestions with no suggestion root', function() { - var compositor = new ModelCompositor(plainCasedModel); + var compositor = new ModelCompositor(plainCasedModel, true); let suggestion = { transform: { @@ -226,7 +226,7 @@ describe('ModelCompositor', function() { }); it('properly cases suggestions that fully replace the suggestion root', function() { - var compositor = new ModelCompositor(plainCasedModel); + var compositor = new ModelCompositor(plainCasedModel, true); let suggestion = { transform: { @@ -266,7 +266,7 @@ describe('ModelCompositor', function() { }); it('properly cases suggestions that do not fully replace the suggestion root', function() { - var compositor = new ModelCompositor(plainCasedModel); + var compositor = new ModelCompositor(plainCasedModel, true); let suggestion = { transform: { @@ -322,7 +322,7 @@ describe('ModelCompositor', function() { it('should produce suggestions from uncased input', function() { let model = uncasedModel; - var composite = new ModelCompositor(model); + var composite = new ModelCompositor(model, true); // Initialize context let context = { @@ -350,7 +350,7 @@ describe('ModelCompositor', function() { it('should not produce suggestions from cased input', function() { let model = uncasedModel; - var composite = new ModelCompositor(model); + var composite = new ModelCompositor(model, true); // Initialize context let context = { @@ -397,7 +397,7 @@ describe('ModelCompositor', function() { it('should produce suggestions from uncased input', function() { let model = casedModel; - var composite = new ModelCompositor(model); + var composite = new ModelCompositor(model, true); // Initialize context let context = { @@ -425,7 +425,7 @@ describe('ModelCompositor', function() { it('should produce capitalized suggestions from fully-uppercased input', function() { let model = casedModel; - var composite = new ModelCompositor(model); + var composite = new ModelCompositor(model, true); // Initialize context let context = { @@ -453,7 +453,7 @@ describe('ModelCompositor', function() { it('should produce "initial-case" suggestions from input with an initial capital', function() { let model = casedModel; - var composite = new ModelCompositor(model); + var composite = new ModelCompositor(model, true); // Initialize context let context = { @@ -481,7 +481,7 @@ describe('ModelCompositor', function() { it('also from input with partial capitalization when including an initial capital', function() { let model = casedModel; - var composite = new ModelCompositor(model); + var composite = new ModelCompositor(model, true); // Initialize context let context = { @@ -512,7 +512,7 @@ describe('ModelCompositor', function() { var model = new TrieModel( jsonFixture('models/tries/english-1000') ); - var composite = new ModelCompositor(model); + var composite = new ModelCompositor(model, true); // Initialize context let context = { @@ -542,7 +542,7 @@ describe('ModelCompositor', function() { var model = new TrieModel( jsonFixture('models/tries/english-1000') ); - var composite = new ModelCompositor(model); + var composite = new ModelCompositor(model, true); // Initialize context let context = { @@ -602,7 +602,7 @@ describe('ModelCompositor', function() { }; let model = new models.DummyModel(options); - let compositor = new ModelCompositor(model); + let compositor = new ModelCompositor(model, true); var keep; if(quoteStyle) { @@ -646,7 +646,7 @@ describe('ModelCompositor', function() { }; let model = new models.DummyModel(options); - let compositor = new ModelCompositor(model); + let compositor = new ModelCompositor(model, true); return compositor.acceptSuggestion(suggestion, context, postTransform); } @@ -829,7 +829,7 @@ describe('ModelCompositor', function() { describe('acceptReversion', function() { let executeAcceptance = function(model, suggestion, context, postTransform) { - let compositor = new ModelCompositor(model); + let compositor = new ModelCompositor(model, true); return {compositor: compositor, reversion: compositor.acceptSuggestion(suggestion, context, postTransform)}; } @@ -869,7 +869,7 @@ describe('ModelCompositor', function() { } let model = new models.DummyModel({punctuation: englishPunctuation}); - let compositor = new ModelCompositor(model); + let compositor = new ModelCompositor(model, true); let reversion = compositor.acceptSuggestion(baseSuggestion, baseContext, postTransform); assert.equal(reversion.transformId, baseSuggestion.transformId); @@ -909,7 +909,7 @@ describe('ModelCompositor', function() { } let model = new models.TrieModel(jsonFixture('models/tries/english-1000'), {punctuation: englishPunctuation}); - let compositor = new ModelCompositor(model); + let compositor = new ModelCompositor(model, true); let initialSuggestions = compositor.predict(postTransform, baseContext); let keepSuggestion = initialSuggestions[0]; @@ -944,7 +944,7 @@ describe('ModelCompositor', function() { } let model = new models.TrieModel(jsonFixture('models/tries/english-1000'), {punctuation: englishPunctuation}); - let compositor = new ModelCompositor(model); + let compositor = new ModelCompositor(model, true); let initialSuggestions = compositor.predict(postTransform, baseContext); let keepSuggestion = initialSuggestions[0]; diff --git a/common/web/types/src/kpj/keyman-developer-project.ts b/common/web/types/src/kpj/keyman-developer-project.ts index a6814a5302..27ea6ddf16 100644 --- a/common/web/types/src/kpj/keyman-developer-project.ts +++ b/common/web/types/src/kpj/keyman-developer-project.ts @@ -30,6 +30,9 @@ export class KeymanDeveloperProject { throw new Error('populateFiles can only be called on a v2.0 project'); } let 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); @@ -44,6 +47,7 @@ export class KeymanDeveloperProject { this.files.push(file); } } + return true; } public isKeyboardProject() { diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-testdata-xml.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-testdata-xml.ts index 86a05e3893..acbcf0ead4 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-testdata-xml.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-testdata-xml.ts @@ -41,34 +41,41 @@ export interface LKTTests { export interface LKTTest { name?: string; startContext?: LKTStartContext; - actions?: LKTAction[]; // differs from XML, to represent order of actions + actions?: LKTAnyAction[]; // differs from XML, to represent order of actions }; export interface LKTStartContext { to?: string; }; -export interface LKTCheck { - result?: string; -}; - -export interface LKTEmit { - to?: string; -}; - -export interface LKTKeystroke { - key?: string; - flick?: string; - longPress?: string; - tapCount?: string; -}; - /** * Test Actions. * The expectation is that each LKTAction object will have exactly one non-falsy field. */ export interface LKTAction { - check?: LKTCheck; - emit?: LKTEmit; - keystroke?: LKTKeystroke; + type?: "check" | "emit" | "keystroke" | "backspace"; }; + +export interface LKTCheck extends LKTAction { + type: "check"; + result?: string; +}; + +export interface LKTEmit extends LKTAction { + type: "emit"; + to?: string; +}; + +export interface LKTKeystroke extends LKTAction { + type: "keystroke"; + key?: string; + flick?: string; + longPress?: string; + tapCount?: string; +}; + +export interface LKTBackspace extends LKTAction { + type: "backspace"; +} + +export type LKTAnyAction = LKTCheck | LKTEmit | LKTKeystroke | LKTBackspace; diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts index 5e7c1ab2d3..3d0b2c7131 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -21,8 +21,8 @@ export class LDMLKeyboardXMLSourceFileReader { constructor(private options: LDMLKeyboardXMLSourceFileReaderOptions, private callbacks : CompilerCallbacks) { } - static get defaultImportsURL() { - return new URL(`../import/`, import.meta.url); + static get defaultImportsURL(): [string,string] { + return ['../import/', import.meta.url]; } readImportFile(version: string, subpath: string): Uint8Array { @@ -393,15 +393,12 @@ export class LDMLKeyboardXMLSourceFileReader { r.stuffBoxes(test, $$, 'startContext'); // singleton // now the actions test.actions = $$.map(v => { - const subtag = v['#name']; - const subv = LDMLKeyboardXMLSourceFileReader.defaultMapper(v, r); - switch(subtag) { - case 'keystroke': return { keystroke: subv }; - case 'check': return { check: subv }; - case 'emit': return { emit: subv }; - case 'startContext': return null; // handled above - default: this.callbacks.reportMessage(CommonTypesMessages.Error_TestDataUnexpectedAction({ subtag })); return null; + const type = v['#name']; // element name + if (type === 'startContext') { + return null; // handled above } + const subv = LDMLKeyboardXMLSourceFileReader.defaultMapper(v, r); + return Object.assign({ type }, subv); }).filter(v => v !== null); return test; }); diff --git a/common/web/types/src/main.ts b/common/web/types/src/main.ts index 108a2d57c2..06671a13d7 100644 --- a/common/web/types/src/main.ts +++ b/common/web/types/src/main.ts @@ -36,7 +36,7 @@ export { TouchLayoutFileWriter, TouchLayoutFileWriterOptions } from './keyman-to export * as KPJ from './kpj/kpj-file.js'; export { KPJFileReader } from './kpj/kpj-file-reader.js'; -export { KeymanDeveloperProject, KeymanDeveloperProjectFile, } from './kpj/keyman-developer-project.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/common/web/types/src/util/common-events.ts b/common/web/types/src/util/common-events.ts index b8b3300550..65743d686d 100644 --- a/common/web/types/src/util/common-events.ts +++ b/common/web/types/src/util/common-events.ts @@ -43,8 +43,4 @@ export class CommonTypesMessages { m(this.ERROR_TestDataUnexpectedArray, `Problem reading test data: expected single ${o.subtag} element, found multiple`); static ERROR_TestDataUnexpectedArray = SevError | 0x0007; - static Error_TestDataUnexpectedAction = (o: {subtag: string}) => - m(this.ERROR_TestDataUnexpectedAction, - `Problem reading test data: unexpected action element ${o.subtag}`); - static ERROR_TestDataUnexpectedAction = SevError | 0x0008; }; diff --git a/common/web/types/test/fixtures/test-fr.xml b/common/web/types/test/fixtures/test-fr.xml index cfd63c4f75..c705605521 100644 --- a/common/web/types/test/fixtures/test-fr.xml +++ b/common/web/types/test/fixtures/test-fr.xml @@ -1,6 +1,12 @@ + @@ -17,6 +23,8 @@ + + diff --git a/common/web/types/test/helpers/reader-callback-test.ts b/common/web/types/test/helpers/reader-callback-test.ts index 86758ad413..907793cdf7 100644 --- a/common/web/types/test/helpers/reader-callback-test.ts +++ b/common/web/types/test/helpers/reader-callback-test.ts @@ -9,7 +9,7 @@ import { TestCompilerCallbacks } from './TestCompilerCallbacks.js'; import { fileURLToPath } from 'url'; const readerOptions: LDMLKeyboardXMLSourceFileReaderOptions = { - importsPath: fileURLToPath(LDMLKeyboardXMLSourceFileReader.defaultImportsURL) + importsPath: fileURLToPath(new URL(...LDMLKeyboardXMLSourceFileReader.defaultImportsURL)) }; export interface CompilationCase { diff --git a/common/web/types/test/ldml-keyboard/test-ldml-keyboard-testdata-reader.ts b/common/web/types/test/ldml-keyboard/test-ldml-keyboard-testdata-reader.ts index 80a78a1187..a0e1202f85 100644 --- a/common/web/types/test/ldml-keyboard/test-ldml-keyboard-testdata-reader.ts +++ b/common/web/types/test/ldml-keyboard/test-ldml-keyboard-testdata-reader.ts @@ -2,12 +2,14 @@ import { constants } from '@keymanapp/ldml-keyboard-constants'; import { assert } from 'chai'; import 'mocha'; import { testTestdataReaderCases } from '../helpers/reader-callback-test.js'; +import { LKTAnyAction } from './ldml-keyboard-testdata-xml.js'; describe('ldml keyboard xml reader tests', function () { this.slow(500); // 0.5 sec -- json schema validation takes a while testTestdataReaderCases([ { + // Note! There's another test case against similar data, in developer/src/kmc-ldml/test/test-testdata-e2e.ts using test-fr.json subpath: 'test-fr.xml', callback: (data, source) => { assert.ok(source); @@ -35,16 +37,19 @@ describe('ldml keyboard xml reader tests', function () { const test0 = source.keyboardTest3.tests[0].test[0]; assert.equal('key-test', test0.name); assert.equal('abc\\u0022...', test0.startContext?.to); - assert.sameDeepOrderedMembers([ - { keystroke: { key: 's' } }, - { check: { result: 'abc\\u0022...s' } }, - { keystroke: { key: 't' } }, - { check: { result: 'abc\\u0022...st' } }, - { keystroke: { key: 'u' } }, - { check: { result: 'abc\\u0022...stu' } }, - { emit: { to: 'v' } }, - { check: { result: 'abc\\u0022...stuv' } }, - ], test0.actions); + const expectedActions : LKTAnyAction[] = [ + { type: "keystroke", key: 's' }, + { type: "check", result: 'abc\\u0022...s' }, + { type: "keystroke", key: 't' }, + { type: "check", result: 'abc\\u0022...st' }, + { type: "keystroke", key: 'u' }, + { type: "check", result: 'abc\\u0022...stu' }, + { type: "emit", to: 'v' }, + { type: "check", result: 'abc\\u0022...stuv' }, + { type: "backspace" }, + { type: "check", result: 'abc\\u0022...stu' }, + ]; + assert.sameDeepOrderedMembers(expectedActions, test0.actions, 'Static data in .ts file should match parsed test-fr.xml'); }, } ]); diff --git a/core/build.sh b/core/build.sh index 9779419202..646a3731c2 100755 --- a/core/build.sh +++ b/core/build.sh @@ -107,13 +107,13 @@ builder_describe_outputs \ configure:mac-arm64 /core/build/mac-arm64/$BUILDER_CONFIGURATION/build.ninja \ configure:arch /core/build/arch/$BUILDER_CONFIGURATION/build.ninja \ configure:wasm /core/build/wasm/$BUILDER_CONFIGURATION/build.ninja \ - build:x86 /core/build/x86/$BUILDER_CONFIGURATION/src/libkmnkbp0.a \ - build:x64 /core/build/x64/$BUILDER_CONFIGURATION/src/libkmnkbp0.a \ - build:mac /core/build/mac/$BUILDER_CONFIGURATION/libkmnkbp0.a \ - build:mac-x86_64 /core/build/mac-x86_64/$BUILDER_CONFIGURATION/src/libkmnkbp0.a \ - build:mac-arm64 /core/build/mac-arm64/$BUILDER_CONFIGURATION/src/libkmnkbp0.a \ - build:arch /core/build/arch/$BUILDER_CONFIGURATION/src/libkmnkbp0.a \ - build:wasm /core/build/wasm/$BUILDER_CONFIGURATION/src/libkmnkbp0.a + build:x86 /core/build/x86/$BUILDER_CONFIGURATION/src/libkeymancore.a \ + build:x64 /core/build/x64/$BUILDER_CONFIGURATION/src/libkeymancore.a \ + build:mac /core/build/mac/$BUILDER_CONFIGURATION/libkeymancore.a \ + build:mac-x86_64 /core/build/mac-x86_64/$BUILDER_CONFIGURATION/src/libkeymancore.a \ + build:mac-arm64 /core/build/mac-arm64/$BUILDER_CONFIGURATION/src/libkeymancore.a \ + build:arch /core/build/arch/$BUILDER_CONFIGURATION/src/libkeymancore.a \ + build:wasm /core/build/wasm/$BUILDER_CONFIGURATION/src/libkeymancore.a # Import our standard compiler defines; this is copied from # /resources/build/meson/standard.meson.build by build.sh, because meson doesn't @@ -157,9 +157,9 @@ do_action build if builder_start_action build:mac; then lipo -create \ - "$KEYMAN_ROOT/core/build/mac-x86_64/$BUILDER_CONFIGURATION/src/libkmnkbp0.a" \ - "$KEYMAN_ROOT/core/build/mac-arm64/$BUILDER_CONFIGURATION/src/libkmnkbp0.a" \ - -output "$KEYMAN_ROOT/core/build/mac/$BUILDER_CONFIGURATION/libkmnkbp0.a" + "$KEYMAN_ROOT/core/build/mac-x86_64/$BUILDER_CONFIGURATION/src/libkeymancore.a" \ + "$KEYMAN_ROOT/core/build/mac-arm64/$BUILDER_CONFIGURATION/src/libkeymancore.a" \ + -output "$KEYMAN_ROOT/core/build/mac/$BUILDER_CONFIGURATION/libkeymancore.a" builder_finish_action success build:mac fi diff --git a/core/include/keyman/keyman_core_api.h b/core/include/keyman/keyman_core_api.h index 86358edeeb..309fdb20e1 100644 --- a/core/include/keyman/keyman_core_api.h +++ b/core/include/keyman/keyman_core_api.h @@ -1,6 +1,6 @@ /* Copyright: © 2018 SIL International. - Description: Cross platform API C/C++ declarations for libkmnkbp keyboard + Description: Cross platform API C/C++ declarations for libkeymancore keyboard processor. Create Date: 2 Oct 2018 Authors: Tim Eves (TSE) @@ -253,8 +253,8 @@ km_core_context_items_from_utf8(char const *text, Convert a context item array into a UTF-16 encoded string placing it into the supplied buffer of specified size, and return the number of code units actually used in the conversion. If null is passed as the buffer the -number codeunits required is returned. This will strip markers from the -context during the conversion. +number of codeunits required is returned. Any markers in the context will +not be included in the output buffer. ##### Return status: - `KM_CORE_STATUS_OK`: On success. - `KM_CORE_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. @@ -285,8 +285,8 @@ km_core_context_items_to_utf16(km_core_context_item const *item, Convert a context item array into a UTF-8 encoded string placing it into the supplied buffer of specified size, and return the number of code units actually used in the conversion. If null is passed as the buffer the -number codeunits required is returned. This will strip markers from the -context during the conversion. +number of codeunits required is returned. Any markers in the context will +not be included in the output buffer. ##### Return status: - `KM_CORE_STATUS_OK`: On success. - `KM_CORE_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. @@ -310,6 +310,38 @@ km_core_context_items_to_utf8(km_core_context_item const *item, char *buf, size_t *buf_size); +/* +``` +### `km_core_context_items_to_utf32` +##### Description: +Convert a context item array into a UTF-32 encoded string placing it into +the supplied buffer of specified size, and return the number of codepoints +actually used in the conversion. If null is passed as the buffer the +number of codepoints required is returned. Any markers in the context will +not be included in the output buffer. +##### Return status: +- `KM_CORE_STATUS_OK`: On success. +- `KM_CORE_STATUS_INVALID_ARGUMENT`: If non-optional parameters are null. +- `KM_CORE_STATUS_INSUFFICENT_BUFFER`: If the buffer is not large enough. + `buf_size` will contain the space required. The contents of the buffer are + undefined. +##### Parameters: +- __context_items__: A pointer to the start of an array `km_core_context_item`. + Must be terminated with a type of `KM_CORE_CT_END`. +- __buf__: A pointer to the buffer to place the UTF-32 string into. + May be null to request size calculation. +- __buf_size__: a pointer to the result variable: + The size of the supplied buffer in codepoints if `buf` is given. + On return will be the size required if `buf` is null. + +```c +*/ +KMN_API +km_core_status +km_core_context_items_to_utf32(km_core_context_item const *item, + km_core_usv *buf, + size_t *buf_size); + /* ``` ### `km_core_context_items_dispose` @@ -501,10 +533,10 @@ typedef struct { uint8_t type; uint8_t _reserved[sizeof(void*)-sizeof(uint8_t)]; union { - uintptr_t marker; // MARKER type + uint32_t marker; // MARKER type km_core_option_item const * option; // OPT types km_core_usv character; // CHAR type - uint8_t capsLock; // CAPSLOCK type, 1 to turn on, 0 to turn off + uint8_t capsLock; // CAPSLOCK type, 1 to turn on, 0 to turn off; re name see #9833 km_core_backspace_item backspace; // BACKSPACE type }; } km_core_action_item; @@ -526,6 +558,161 @@ enum km_core_action_type { KM_CORE_IT_MAX_TYPE_ID }; +/* +``` +### Actions +This structure provides the results of processing a key event to the Platform layer and +should be processed by the Platform layer to issue commands to the os text +services framework to transform the text store in the Client Application, among +other actions. + +This API replaces the Action items APIs, which is now deprecated and will be +removed in the future. +```c +*/ + +typedef enum { KM_CORE_FALSE = 0, KM_CORE_TRUE = 1 } km_core_bool; +typedef enum { KM_CORE_CAPS_UNCHANGED = -1, KM_CORE_CAPS_OFF = 0, KM_CORE_CAPS_ON = 1 } km_core_caps_state; + +typedef struct { + // number of codepoints (not codeunits!) to delete from app context. + unsigned int code_points_to_delete; + + // null-term string of characters to insert into document + km_core_usv* output; + + // list of options to persist, terminated with KM_CORE_OPTIONS_END + km_core_option_item* persist_options; + + // issue a beep, 0 = no, 1 = yes + km_core_bool do_alert; + + // emit the (unmodified) input keystroke to the application, 0 = no, 1 = yes + km_core_bool emit_keystroke; + + // -1=unchanged, 0=off, 1=on + km_core_caps_state new_caps_lock_state; +} km_core_actions; + +/* +``` +### `km_core_state_get_actions` +##### Description: +Returns a pointer to an actions object which details all the actions +that the Platform layer must take after a keystroke. The `code_points_to_delete` +action must be performed before the `output` action, but the other +actions may be performed in any order. +##### Return: +A pointer to a `km_core_actions` object, which must be freed with +`km_core_actions_dispose`. +##### Parameters: +- __state__: An opaque pointer to a state object. + +```c +*/ +KMN_API +km_core_actions* +km_core_state_get_actions( + km_core_state const *state +); + +/* +``` +### `km_core_actions_dispose` +##### Description: +Free the allocated memory belonging to an actions object previously +returned by `km_core_state_get_actions`. +##### Parameters: +- __actions__: A pointer to the actions object to be disposed of. + +```c +*/ +KMN_API +km_core_status +km_core_actions_dispose( + km_core_actions* actions +); + +/* +``` +### `km_core_context_status` +##### Description: +Return values for `km_core_state_context_set_if_needed`. + +```c +*/ + +typedef enum { + KM_CORE_CONTEXT_STATUS_UNCHANGED = 0, // Cached context change was not needed + KM_CORE_CONTEXT_STATUS_UPDATED = 1, // Cached context was set to application context + KM_CORE_CONTEXT_STATUS_CLEARED = 2, // Application context was invalid, context was cleared + KM_CORE_CONTEXT_STATUS_ERROR = 3, // Internal error + KM_CORE_CONTEXT_STATUS_INVALID_ARGUMENT = 4, // Invalid arguments +} km_core_context_status; + +/* +``` +### `km_core_state_context_set_if_needed` +##### Description: +Sets the internal cached context for the state object, to the passed-in +application context string, if it differs from the codepoints in the +cached context. For the purposes of comparison, (1) cached markers are +ignored, (2) if the cached context is shorter than the application +context, it is considered identical, but (3) if the cached context is +longer, then it is considered different. + +If a difference is found, then the cached context will be set to the +application context, and thus any cached markers will be cleared. + +`km_core_state_context_set_if_needed` and `km_core_state_context_clear` +will replace most uses of the existing Core context APIs. + +##### Parameters: +- __state__: An opaque pointer to a state object. +- __application_context__: A pointer to an null-terminated `km_core_cp` + string representing the current context from the application. +##### Return status: +- `KM_CORE_CONTEXT_STATUS_UNCHANGED`: Cached context change was not needed +- `KM_CORE_CONTEXT_STATUS_UPDATED`: Cached context was set to application + context +- `KM_CORE_CONTEXT_STATUS_CLEARED`: Application context was invalid, perhaps + had unpaired surrogates, and so cached context was cleared instead +- `KM_CORE_CONTEXT_STATUS_ERROR`: Internal error +- `KM_CORE_CONTEXT_STATUS_INVALID_ARGUMENT`: One or more parameters was null + +```c +*/ + +KMN_API +km_core_context_status +km_core_state_context_set_if_needed( + km_core_state *state, + km_core_cp const *application_context +); + +/* +``` +### `km_core_state_context_clear` +##### Description: +Clears the internal cached context for the state. This is the same as +`km_core_context_clear(km_core_state_context(&state))`. + +`km_core_state_context_set_if_needed` and `km_core_state_context_clear` +will replace most uses of the existing Core context APIs. + +##### Parameters: +- __state__: An opaque pointer to a state object. +##### Return status: +- `KM_CORE_STATUS_OK`: On success. +- `KM_CORE_STATUS_INVALID_ARGUMENT`: If any parameters are null. + +```c +*/ +KMN_API +km_core_status +km_core_state_context_clear( + km_core_state *state +); /* ``` @@ -936,7 +1123,7 @@ km_core_state_context(km_core_state *state); /* ``` -### `kbp_state_get_intermediate_context` +### `km_core_state_get_intermediate_context` ##### Description: Get access to the state object's keyboard processor's intermediate context. This context is used during an IMX callback, part way through processing a keystroke. @@ -950,7 +1137,7 @@ to `km_core_context_items_dispose`. */ KMN_API km_core_status -kbp_state_get_intermediate_context(km_core_state *state, km_core_context_item ** context_items); +km_core_state_get_intermediate_context(km_core_state *state, km_core_context_item ** context_items); /* ``` diff --git a/core/include/keyman/keyman_core_api_bits.h b/core/include/keyman/keyman_core_api_bits.h index be2886d058..e00f4698a8 100644 --- a/core/include/keyman/keyman_core_api_bits.h +++ b/core/include/keyman/keyman_core_api_bits.h @@ -28,8 +28,8 @@ #if defined _WIN32 || defined __CYGWIN__ typedef wchar_t const * km_core_path_name; - #define _KM_KBP_PATH_SEPARATOR (L'\\') - #define _KM_KBP_EXT_SEPARATOR (L'.') + #define _KM_CORE_PATH_SEPARATOR (L'\\') + #define _KM_CORE_EXT_SEPARATOR (L'.') #if defined __GNUC__ // These three will be redefined for Windows #undef _kmn_export_flag #undef _kmn_import_flag @@ -44,14 +44,14 @@ #define _kmn_static_flag #else typedef char const * km_core_path_name; - #define _KM_KBP_PATH_SEPARATOR ('/') - #define _KM_KBP_EXT_SEPARATOR ('.') + #define _KM_CORE_PATH_SEPARATOR ('/') + #define _KM_CORE_EXT_SEPARATOR ('.') #endif -#if defined KMN_KBP_STATIC +#if defined KM_CORE_LIBRARY_STATIC #define KMN_API _kmn_tag_fn(_kmn_static_flag) #define KMN_DEPRECATED_API _kmn_tag_fn(_kmn_deprecated_flag _kmn_and _kmn_static_flag) -#elif defined KMN_KBP_EXPORTING +#elif defined KM_CORE_LIBRARY_EXPORTING #define KMN_API _kmn_tag_fn(_kmn_export_flag) #define KMN_DEPRECATED_API _kmn_tag_fn(_kmn_deprecated_flag _kmn_and _kmn_export_flag) #else @@ -59,8 +59,8 @@ #define KMN_DEPRECATED_API _kmn_tag_fn(_kmn_deprecated_flag _kmn_and _kmn_import_flag) #endif -#ifndef KMN_KBP -#define KMN_KBP +#ifndef KM_CORE_LIBRARY +#define KM_CORE_LIBRARY #endif #ifndef USE_CHAR16_T #define USE_CHAR16_T diff --git a/core/include/ldml/build.sh b/core/include/ldml/build.sh index 2f4b76ab8e..8787ba0e1c 100755 --- a/core/include/ldml/build.sh +++ b/core/include/ldml/build.sh @@ -28,7 +28,7 @@ builder_describe "Build Keyman ldml-keyboard-constants package" \ builder_describe_outputs \ configure /node_modules \ - build /core/include/ldml/build/keyboardprocessor_ldml.js + build /core/include/ldml/build/keyman_core_ldml.js builder_parse "$@" diff --git a/core/include/ldml/keyboardprocessor_ldml.h b/core/include/ldml/keyman_core_ldml.h similarity index 98% rename from core/include/ldml/keyboardprocessor_ldml.h rename to core/include/ldml/keyman_core_ldml.h index 45837b6385..41f87a0d50 100644 --- a/core/include/ldml/keyboardprocessor_ldml.h +++ b/core/include/ldml/keyman_core_ldml.h @@ -10,7 +10,7 @@ // Generated File - do not edit // // This file is generated by core/tools/ldml-const-builder/build.sh -// based on core/include/ldml/keyboardprocessor_ldml.ts +// based on core/include/ldml/keyman_core_ldml.ts // #pragma once diff --git a/core/include/ldml/keyboardprocessor_ldml.ts b/core/include/ldml/keyman_core_ldml.ts similarity index 99% rename from core/include/ldml/keyboardprocessor_ldml.ts rename to core/include/ldml/keyman_core_ldml.ts index 4e9aaedbb1..b404a05bcc 100644 --- a/core/include/ldml/keyboardprocessor_ldml.ts +++ b/core/include/ldml/keyman_core_ldml.ts @@ -12,7 +12,7 @@ // // core/tools/ldml-const-builder/build.sh clean build run // -// To update keyboardprocessor_ldml.h, and commit the result. +// To update keyman_core_ldml.h, and commit the result. // // It is not updated automatically. diff --git a/core/include/ldml/ldml-const-builder.ts b/core/include/ldml/ldml-const-builder.ts index c08e74dddc..eced81b85a 100644 --- a/core/include/ldml/ldml-const-builder.ts +++ b/core/include/ldml/ldml-const-builder.ts @@ -1,10 +1,10 @@ /* Copyright: Copyright (C) 2022 SIL International. Authors: srl295 - This tool generates a .h version of the keyboardprocessor_ldml.ts file + This tool generates a .h version of the keyman_core_ldml.ts file */ -import { constants } from './keyboardprocessor_ldml.js'; +import { constants } from './keyman_core_ldml.js'; const keys = Object.keys(constants); keys.sort(); @@ -20,7 +20,7 @@ console.log(` // Generated File - do not edit // // This file is generated by core/tools/ldml-const-builder/build.sh -// based on core/include/ldml/keyboardprocessor_ldml.ts +// based on core/include/ldml/keyman_core_ldml.ts // #pragma once diff --git a/core/include/ldml/package.json b/core/include/ldml/package.json index 76da479930..1e9a26c1c0 100644 --- a/core/include/ldml/package.json +++ b/core/include/ldml/package.json @@ -8,11 +8,11 @@ "unicode" ], "files": [ - "/build/keyboardprocessor_ldml.*" + "/build/keyman_core_ldml.*" ], "license": "MIT", "type": "module", - "main": "build/keyboardprocessor_ldml.js", + "main": "build/keyman_core_ldml.js", "repository": { "type": "git", "url": "git+https://github.com/keymanapp/keyman.git" diff --git a/core/include/ldml/tsconfig.build.json b/core/include/ldml/tsconfig.build.json index fd0f003272..60565eda3a 100644 --- a/core/include/ldml/tsconfig.build.json +++ b/core/include/ldml/tsconfig.build.json @@ -10,7 +10,7 @@ "node_modules" ], "files": [ - "keyboardprocessor_ldml.ts", + "keyman_core_ldml.ts", "ldml-const-builder.ts" ] } diff --git a/core/include/ldml/tsconfig.json b/core/include/ldml/tsconfig.json index fa4135b021..e73b5777da 100644 --- a/core/include/ldml/tsconfig.json +++ b/core/include/ldml/tsconfig.json @@ -10,6 +10,6 @@ "node_modules" ], "files": [ - "keyboardprocessor_ldml.ts" + "keyman_core_ldml.ts" ] } diff --git a/core/meson.build b/core/meson.build index 81d994124c..2061942d61 100644 --- a/core/meson.build +++ b/core/meson.build @@ -1,11 +1,11 @@ # Copyright: © 2018-2022 SIL International. -# Description: Cross platform build script to compile libkmnkbp, documentation +# Description: Cross platform build script to compile libkeymancore, documentation # and tests. # Create Date: 2 Oct 2018 # Authors: Tim Eves (TSE) # -project('keyboardprocessor', 'cpp', 'c', +project('keyman_core', 'cpp', 'c', version: run_command(find_program('getversion.bat', 'getversion.sh'), check:true).stdout().strip(), license: 'MIT', default_options : ['buildtype=release', @@ -19,14 +19,14 @@ project('keyboardprocessor', 'cpp', 'c', # meson doesn't allow us to reference a file outside its root subdir('resources') -lib_version = '0.0.0' +lib_version = '1.0.0' py = import('python') python = py.find_installation() # TODO: Shared includes may use namespaces, etc which need future tidyup. -# For now, we use KMN_KBP to inject the km::kbp::kmx namespace -defns += ['-DKMN_KBP'] +# For now, we use KM_CORE_LIBRARY to inject the km::core::kmx namespace +defns += ['-DKM_CORE_LIBRARY'] # #define DEBUG when we are on a debug build if get_option('buildtype') == 'debug' @@ -36,4 +36,4 @@ endif subdir('doc') subdir('include') subdir('src') -subdir('tests') \ No newline at end of file +subdir('tests') diff --git a/core/src/action.cpp b/core/src/action.cpp new file mode 100644 index 0000000000..3387d8f85d --- /dev/null +++ b/core/src/action.cpp @@ -0,0 +1,138 @@ +/* + Copyright: © 2023 SIL International. + Description: Implementation of the action API functions using internal + data structures and functions. + Create Date: 23 Oct 2023 + Authors: Marc Durdin (MCD) + History: 23 Oct 2023 - MCD - Initial implementation from #9720 +*/ +#include +#include +#include +#include + +#include + +#include "action.hpp" +#include "state.hpp" +#include "option.hpp" + +km_core_actions * km::core::action_item_list_to_actions_object( + km_core_action_item const *action_items +) { + assert(action_items != nullptr); + if(action_items == nullptr) { + return nullptr; + } + + km_core_status status = KM_CORE_STATUS_OK; + + std::unique_ptr actions(new km_core_actions); + + // Set actions default values + std::vector output; + std::vector options; + actions->code_points_to_delete = 0; + actions->do_alert = KM_CORE_FALSE; + actions->emit_keystroke = KM_CORE_FALSE; + actions->new_caps_lock_state = KM_CORE_CAPS_UNCHANGED; + + // Clear output pointers, will be set later once we have sizes + actions->output = nullptr; + actions->persist_options = nullptr; + + for (; action_items->type != KM_CORE_IT_END; ++action_items) { + assert(action_items->type < KM_CORE_IT_MAX_TYPE_ID); + + switch(action_items->type) { + case KM_CORE_IT_ALERT: + actions->do_alert = KM_CORE_TRUE; + break; + case KM_CORE_IT_BACK: + switch(action_items->backspace.expected_type) { + case KM_CORE_BT_UNKNOWN: + // this is equivalent to emit_keystroke, because the only time we + // are allowed to do an unknown bksp is when a bksp is passed in + actions->emit_keystroke = KM_CORE_TRUE; + break; + case KM_CORE_BT_CHAR: + if(output.empty()) { + actions->code_points_to_delete++; + } else { + auto last_context_item = output.back(); + output.pop_back(); + assert(last_context_item.type == KM_CORE_CT_CHAR); + assert(last_context_item.character == action_items->backspace.expected_value); + } + break; + case KM_CORE_BT_MARKER: + if(output.empty()) { + // deleting a marker has no effect on the application + } else { + auto last_context_item = output.back(); + output.pop_back(); + assert(last_context_item.type == KM_CORE_CT_MARKER); + assert(last_context_item.marker == action_items->backspace.expected_value); + } + break; + default: + assert(false); + } + break; + case KM_CORE_IT_CAPSLOCK: + actions->new_caps_lock_state = action_items->capsLock ? KM_CORE_CAPS_ON : KM_CORE_CAPS_OFF; + break; + case KM_CORE_IT_CHAR: + output.push_back({KM_CORE_CT_CHAR,{0},{action_items->character}}); + break; + case KM_CORE_IT_EMIT_KEYSTROKE: + actions->emit_keystroke = KM_CORE_TRUE; + break; + case KM_CORE_IT_INVALIDATE_CONTEXT: + // no-op + break; + case KM_CORE_IT_MARKER: + output.push_back({KM_CORE_CT_MARKER,{0},{action_items->marker}}); + break; + case KM_CORE_IT_PERSIST_OPT: + // TODO: lowpri: replace existing item if already present in options vector? + options.push_back(km::core::option( + static_cast(action_items->option->scope), + action_items->option->key, + action_items->option->value + )); + break; + default: + assert(false); + } + } + + + // Strip the markers from the output, and convert to an string of UTF-32 + + output.push_back(KM_CORE_CONTEXT_ITEM_END); + + size_t buf_size; + + if((status = km_core_context_items_to_utf32(output.data(), nullptr, &buf_size)) != KM_CORE_STATUS_OK) { + return nullptr; + } + + std::unique_ptr output_usv(new km_core_usv[buf_size]); + + if((status = km_core_context_items_to_utf32(output.data(), output_usv.get(), &buf_size)) != KM_CORE_STATUS_OK) { + return nullptr; + } + + actions->output = output_usv.release(); + + // Create an array of the persisted options + + options.push_back(KM_CORE_OPTIONS_END); + actions->persist_options = new km_core_option_item[options.size()]; + std::copy(options.begin(), options.end(), actions->persist_options); + + // We now have a complete set of actions + + return actions.release(); +} diff --git a/core/src/action.hpp b/core/src/action.hpp new file mode 100644 index 0000000000..4cc0aabdf2 --- /dev/null +++ b/core/src/action.hpp @@ -0,0 +1,20 @@ +/* + Copyright: © 2023 SIL International. + Description: Internal actions methods for Keyman Core + Create Date: 23 Oct 2023 + Authors: Marc Durdin (MCD) + History: 23 Oct 2023 - MCD - Initial implementation +*/ + +#pragma once + +#include + +namespace km { +namespace core +{ + km_core_actions* action_item_list_to_actions_object( + km_core_action_item const *action_items + ); +} // namespace core +} // namespace km diff --git a/core/src/context.hpp b/core/src/context.hpp index 08edc86163..dba7bec060 100644 --- a/core/src/context.hpp +++ b/core/src/context.hpp @@ -15,7 +15,7 @@ class json; namespace km { -namespace kbp +namespace core { // This will likely be replaced with a class implementing a more space @@ -39,13 +39,13 @@ void context::push_marker(uint32_t marker) { emplace_back(km_core_context_item { KM_CORE_CT_MARKER, {0,}, {marker} }); } -} // namespace kbp +} // namespace core } // namespace km -json & operator << (json &, km::kbp::context const &); +json & operator << (json &, km::core::context const &); json & operator << (json &, km_core_context_item const &); -struct km_core_context : public km::kbp::context +struct km_core_context : public km::core::context { }; diff --git a/core/src/debug.hpp b/core/src/debug.hpp index 935fad155c..a68d9f1e57 100644 --- a/core/src/debug.hpp +++ b/core/src/debug.hpp @@ -13,7 +13,7 @@ #include namespace km { -namespace kbp +namespace core { class debug_items : public std::vector @@ -66,6 +66,6 @@ void debug_items::set_enabled(bool value) noexcept { _is_enabled = value; } -} // namespace kbp +} // namespace core } // namespace km diff --git a/core/src/debuglog.cpp b/core/src/debuglog.cpp index dc3dd95cd5..a4f8847892 100644 --- a/core/src/debuglog.cpp +++ b/core/src/debuglog.cpp @@ -9,7 +9,7 @@ #include "debuglog.h" namespace km { -namespace kbp { +namespace core { namespace kmx { #define TAB "\t" @@ -358,7 +358,7 @@ int DebugLog_1(const char *file, int line, const char *function, const char *fmt "%ld" TAB //"TickCount" TAB "%s:%d" TAB //"SourceFile" TAB "%s" TAB //"Function" - "%s" NL, //"Message" + "%s", //"Message" GetTickCount(), //"TickCount" TAB file, line, //"SourceFile" TAB diff --git a/core/src/debuglog.h b/core/src/debuglog.h index 1ecbaca612..67e6ce601f 100644 --- a/core/src/debuglog.h +++ b/core/src/debuglog.h @@ -1,9 +1,11 @@ /* Debugging */ +#pragma once + #include namespace km { -namespace kbp { +namespace core { namespace kmx { extern KMX_BOOL g_debug_ToConsole, g_debug_KeymanLog, g_silent; @@ -17,11 +19,13 @@ extern const struct modifier_names s_modifier_names[]; extern const char *s_key_names[]; #ifdef _MSC_VER -#define DebugLog(msg,...) (km::kbp::kmx::ShouldDebug() ? km::kbp::kmx::DebugLog_1(__FILE__, __LINE__, __FUNCTION__, (msg),__VA_ARGS__) : 0) +#define DebugLog(msg,...) (km::core::kmx::ShouldDebug() ? km::core::kmx::DebugLog_1(__FILE__, __LINE__, __FUNCTION__, (msg),__VA_ARGS__) : 0) +#define DebugLog2(file,line,function,msg,...) (km::core::kmx::ShouldDebug() ? km::core::kmx::DebugLog_1(file, line, function, (msg),__VA_ARGS__) : 0) #define console_error(msg,...) write_console(TRUE, (msg), __VA_ARGS__) #define console_log(msg,...) write_console(FALSE, (msg), __VA_ARGS__) #else -#define DebugLog(msg,...) (km::kbp::kmx::ShouldDebug() ? km::kbp::kmx::DebugLog_1(__FILE__, __LINE__, __FUNCTION__, (msg), ##__VA_ARGS__) : 0) +#define DebugLog(msg,...) (km::core::kmx::ShouldDebug() ? km::core::kmx::DebugLog_1(__FILE__, __LINE__, __FUNCTION__, (msg), ##__VA_ARGS__) : 0) +#define DebugLog2(file,line,function,msg,...) (km::core::kmx::ShouldDebug() ? km::core::kmx::DebugLog_1(file, line, function, (msg), ##__VA_ARGS__) : 0) #define console_error(msg,...) write_console(TRUE, (msg), ##__VA_ARGS__) #define console_log(msg,...) write_console(FALSE, (msg), ##__VA_ARGS__) #endif diff --git a/core/src/keyboard.cpp b/core/src/keyboard.cpp index 47693b9b66..2c3a0c11b2 100644 --- a/core/src/keyboard.cpp +++ b/core/src/keyboard.cpp @@ -8,7 +8,7 @@ #include "keyboard.hpp" #include "jsonpp.hpp" -using namespace km::kbp; +using namespace km::core; inline @@ -54,7 +54,7 @@ keyboard_attributes & keyboard_attributes::operator = (keyboard_attributes &&rhs } -json & km::kbp::operator << (json & j, km::kbp::keyboard_attributes const & kb) +json & km::core::operator << (json & j, km::core::keyboard_attributes const & kb) { j << json::object << "id" << kb.id diff --git a/core/src/keyboard.hpp b/core/src/keyboard.hpp index 97329b7e76..6dd8517ce1 100644 --- a/core/src/keyboard.hpp +++ b/core/src/keyboard.hpp @@ -20,13 +20,13 @@ class json; namespace km { -namespace kbp +namespace core { class keyboard_attributes : public km_core_keyboard_attrs { std::u16string _keyboard_id; std::u16string _version_string; - kbp::path _folder_path; + core::path _folder_path; std::vector