chore(web): Merge branch 'refactor/web/transcription-cache' into feat/web/test-kbd-prototyping

This commit is contained in:
Joshua A. Horton 2023-11-01 08:42:51 +07:00
commit bfb31a6918
216 changed files with 4590 additions and 1307 deletions

View file

@ -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:

View file

@ -1,5 +1,76 @@
# Keyman Version History
## 17.0.199 alpha 2023-10-26
* fix(developer): handle xml errors in package compiler (#9821)
* fix(developer): server download Keyman link (#9822)
* chore(common): handle invalid XML in kpj-file-reader (#9824)
* fix(developer): reduce confusion in Unicode fields in touch layout editor (#9839)
## 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 +1170,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 +1573,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 +1861,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)

View file

@ -1 +1 @@
17.0.190
17.0.200

View file

@ -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

View file

@ -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.).

View file

@ -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

View file

@ -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"
]
}
}
}

View file

@ -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

View file

@ -96,6 +96,7 @@ export class GestureSequence<Type, StateToken = any> extends EventEmitter<EventM
private pushedSelector?: MatcherSelector<Type, StateToken>;
private gestureConfig: GestureModelDefs<Type, StateToken>;
private markedComplete: boolean = false;
// Note: the first stage will be available under `stageReports` after awaiting a simple Promise.resolve().
constructor(
@ -110,6 +111,16 @@ export class GestureSequence<Type, StateToken = any> extends EventEmitter<EventM
this.selector = selector;
this.selector.on('rejectionwithaction', this.modelResetHandler);
this.once('complete', () => {
if(this.pushedSelector) {
// The `popSelector` method is responsible for triggering cascading cancellations if
// there are nested GestureSequences.
//
// As this tends to affect which gestures are permitted, it's important this is done
// any time the GestureSequence is cancelled or completed, for any reason.
this.touchpointCoordinator?.popSelector(this.pushedSelector);
this.pushedSelector = null;
}
this.selector.off('rejectionwithaction', this.modelResetHandler);
this.selector.dropSourcesWithIds(this.allSourceIds);
@ -193,13 +204,10 @@ export class GestureSequence<Type, StateToken = any> extends EventEmitter<EventM
});
if(!selection.result.matched) {
if(this.pushedSelector) {
// The `popSelector` method is responsible for triggering cascading cancellations if
// there are nested GestureSequences.
this.touchpointCoordinator?.popSelector(this.pushedSelector);
if(!this.markedComplete) {
this.markedComplete = true;
this.emit('complete');
}
this.emit('complete');
return;
}
}
@ -276,13 +284,11 @@ export class GestureSequence<Type, StateToken = any> extends EventEmitter<EventM
}
}
} else {
if(this.pushedSelector) {
this.touchpointCoordinator?.popSelector(this.pushedSelector);
this.pushedSelector = null;
}
// Any extra finalization stuff should go here, before the event, if needed.
this.emit('complete');
if(!this.markedComplete) {
this.markedComplete = true;
this.emit('complete');
}
}
}
@ -310,7 +316,11 @@ export class GestureSequence<Type, StateToken = any> extends EventEmitter<EventM
public cancel() {
const sources = this.stageReports[this.stageReports.length - 1].sources;
sources.forEach((src) => src.terminate(true));
sources.forEach((src) => src.baseSource.isPathComplete || src.baseSource.terminate(true));
if(!this.markedComplete) {
this.markedComplete = true;
this.emit('complete');
}
}
}

View file

@ -428,22 +428,10 @@ export class MatcherSelector<Type, StateToken = any> extends EventEmitter<EventM
return async (result: MatchResult<Type>) => {
// Note: is only called by GestureMatcher Promises that are resolving.
/*
* If we already had a gesture stage match, this will have already been fulfilled;
* bypass all match-handling. Capturing `matchSynchronization` in a closure in this
* manner is important to ensure that the returned handler is "locked" to the
* currently-processing gesture stage.
*/
for(let synchronizer of matchSynchronizers) {
if(synchronizer.isFulfilled) {
return;
}
}
// Do not bypass match handling just because a synchronization promise is fulfilled.
// If a source was force-cancelled, cascading to a call of this handler, we still
// need to perform internal state cleanup.
/* If cancellation was requested but not pre-filtered by the synchronizer setup, replace
* the result object. The matcher's Promise may have resolved simultaneously with the
* winner but 'lost', a scenario that may require careful handling to clean up.
*/
if(matcher.isCancelled) {
result = {
matched: false,
@ -474,11 +462,6 @@ export class MatcherSelector<Type, StateToken = any> extends EventEmitter<EventM
return;
}
if(matcher.isCancelled) {
// Fortunately, the rest of the code will help us recover from the state.
console.warn("Unexpected state: a cancelled GestureMatcher was still listed as a possibility");
}
this.potentialMatchers.splice(matcherIndex, 1);
/*
@ -583,14 +566,6 @@ export class MatcherSelector<Type, StateToken = any> extends EventEmitter<EventM
/*
* Fulfills the contract set by `matchGesture`.
*
* Also, fulfilling the ManagedPromise acts as a synchronizer, partially facilitating the
* guarantee at the start of this closure. It's set synchronously, so other gesture-matchers
* that call into this method will know that a match has already fulfilled for the matched
* source(s). Any further matchers will be silently ignored, effectively cancelling them.
* However, this fails to handle the case where two separate calls to matcherSelectionFilter
* occur for the same matcher due to one source being added at a later point in time;
* this is what the `cancel`
*/
tracker.matchPromise.resolve({matcher, result});
}

View file

@ -112,7 +112,12 @@ export class TouchpointCoordinator<HoveredItemType, StateToken=any> extends Even
touchpoint.setGestureMatchInspector(buildGestureMatchInspector(selector));
// We wait for the source to fully pass through the gesture-model spin-up phase; there's a chanc
/* We wait for the source to fully pass through the gesture-model spin-up phase; there's
* a chance that the new source will complete an existing gesture instantly without being
* locked to it, resulting in activation of a different `stateToken`.
*
* This, in turn, can affect what the initial 'item' for the new gesture will be.
*/
const modelingSpinupPromise = selector.matchGesture(touchpoint, getGestureModelSet(modelDefs, selector.baseGestureSetId));
modelingSpinupPromise.then(async (selectionPromiseHost) => {
this.emit('inputstart', touchpoint);
@ -181,6 +186,15 @@ export class TouchpointCoordinator<HoveredItemType, StateToken=any> extends Even
touchpoint.path.on('invalidated', () => {
// GestureSequence _should_ handle any other cleanup internally as fallout
// from the path being cancelled.
//
// That said, it's handled asynchronously... but we can give a synchronous signal
// through the next block of code, allowing cleanup to occur earlier during
// recovery states.
const owningSequence = this.activeGestures.find((entry) => entry.allSourceIds.includes(touchpoint.identifier));
if(owningSequence) {
owningSequence.cancel();
}
// To consider: should it specially mark if it 'completed' due to cancellation,
// or is that safe to infer from the tracked GestureSource(s)?
@ -191,8 +205,6 @@ export class TouchpointCoordinator<HoveredItemType, StateToken=any> extends Even
this._activeSources = this._activeSources.splice(i, 1);
});
touchpoint.path.on('complete', () => {
// TODO: on cancellation, is there any other cleanup to be done?
// Also mark the touchpoint as no longer active.
let i = this._activeSources.indexOf(touchpoint);
this._activeSources = this._activeSources.splice(i, 1);

View file

@ -105,7 +105,7 @@ export class TouchEventEngine<HoveredItemType, StateToken = any> extends InputEv
const newTouches = touchListToArray(event.changedTouches);
// Maintain all touches in the `.touches` array that are NOT marked as `.changedTouches` (and therefore, new)
this.maintainTouchpointsWithIds(allTouches
.filter((touch) => (newTouches.indexOf(touch) == -1))
.filter((touch1) => newTouches.findIndex(touch2 => touch1.identifier == touch2.identifier) == -1)
.map((touch) => touch.identifier)
);

View file

@ -1,7 +1,8 @@
import Codes from "../text/codes.js";
import KeyEvent, { KeyEventSpec } from "../text/keyEvent.js";
import KeyMapping from "../text/keyMapping.js";
import { Layouts } from "./defaultLayouts.js";
import type { KeyDistribution } from "../text/keyEvent.js";
import { ButtonClasses, Layouts } from "./defaultLayouts.js";
import type { LayoutKey, LayoutSubKey, LayoutRow, LayoutLayer, LayoutFormFactor, ButtonClass } from "./defaultLayouts.js";
import type Keyboard from "./keyboard.js";
@ -69,7 +70,7 @@ export class ActiveKeyBase {
static readonly DEFAULT_KEY = {
text: '',
width: ActiveKeyBase.DEFAULT_KEY_WIDTH,
sp: Layouts.buttonClasses.DEFAULT,
sp: ButtonClasses.normal,
pad: ActiveKeyBase.DEFAULT_PAD
};
@ -139,7 +140,7 @@ export class ActiveKeyBase {
public get isPadding(): boolean {
// Does not include 9 (class: blank) as that may be an intentional 'catch' for misplaced
// keystrokes.
return this.sp == Layouts.buttonClasses.HIDDEN; // Button class: hidden.
return this.sp == ButtonClasses.spacer; // Button class: hidden.
}
/**
@ -544,17 +545,17 @@ export class ActiveRow implements LayoutRow {
}
// Modify the key type for special keys with non-standard labels
// to allow the keyboard font to ovveride the SpecialOSK font.
// to allow the keyboard font to overide the SpecialOSK font.
// Blank keys are no longer reclassed - can use before/after CSS to add text
switch(key['sp']) {
case Layouts.buttonClasses.SHIFT:
case ButtonClasses.special:
if(!ActiveRow.SPECIAL_LABEL.test(key['text']) && key['text'] != '') {
key.sp=Layouts.buttonClasses.SPECIAL;
key.sp=ButtonClasses.customSpecial;
}
break;
case Layouts.buttonClasses['SHIFT-ON']:
case ButtonClasses.specialActive:
if(!ActiveRow.SPECIAL_LABEL.test(key['text']) && key['text'] != '') {
key.sp=Layouts.buttonClasses['SPECIAL-ON'];
key.sp=ButtonClasses.customSpecialActive;
}
break;
}

View file

@ -12,6 +12,9 @@ export type LayoutRow = TouchLayout.TouchLayoutRow;
export type LayoutKey = TouchLayout.TouchLayoutKey;
export type LayoutSubKey = TouchLayout.TouchLayoutSubKey;
import ButtonClasses = TouchLayout.TouchLayoutKeySp;
export { ButtonClasses };
import Codes from "../text/codes.js";
import type Keyboard from "./keyboard.js";
@ -20,7 +23,6 @@ export type KLS = {[layerName: string]: string[]};
// The following types provide type definitions for the full JSON format we use for visual keyboard definitions.
export type ButtonClass = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
export type ButtonClassString = "0"|"1"|"2"|"3"|"4"|"5"|"6"|"7"|"8"|"9"|"10";
export interface LayoutLayer extends LayoutLayerBase {
// Post-processing elements.
@ -61,19 +63,7 @@ export class Layouts {
static readonly DEFAULT_RAW_SPEC = {'F':'Tahoma', 'BK': Layouts.dfltText} as const;
// Cross-reference with the ids in osk.setButtonClass.
static readonly buttonClasses: {[name: string]: ButtonClass} = {
'DEFAULT':0,
'SHIFT':1, // special-key / frame key styling: uses our custom, PUA OSK font
'SHIFT-ON':2,
'SPECIAL':3, // special-key / frame key styling: uses the keyboard's font
'SPECIAL-ON':4,
'DEADKEY':8,
'BLANK':9,
'HIDDEN':10
};
static readonly modifierSpecials = {
static modifierSpecials = {
'leftalt': '*LAlt*',
'rightalt': '*RAlt*',
'alt': '*Alt*',
@ -323,7 +313,7 @@ export class Layouts {
// Set modifier key appearance and behaviour for non-desktop devices using the default layout
if(formFactor != 'desktop') {
if(n > 0 && shiftKey != null) {
shiftKey['sp']=Layouts.buttonClasses['SHIFT-ON'];
shiftKey['sp']=ButtonClasses.specialActive;
shiftKey['sk']=null;
shiftKey['text'] = Layouts.modifierSpecials[layers[n].id] ? Layouts.modifierSpecials[layers[n].id] : "*Shift*";
}
@ -407,7 +397,6 @@ export class Layouts {
*/
static formatDefaultLayer(layer: LayoutLayer, chiral: boolean, formFactor: string, key102: boolean) {
var layerId = layer['id'];
let buttonClasses = Layouts.buttonClasses;
// Correct appearance of state-dependent modifier keys according to group
for(var i=0; i<layer['row'].length; i++) {
@ -420,7 +409,7 @@ export class Layouts {
case 'K_LSHIFT':
case 'K_RSHIFT':
if(layerId.indexOf('shift') != -1) {
key['sp'] = buttonClasses['SHIFT-ON'];
key['sp'] = ButtonClasses.specialActive;
}
if(formFactor != 'desktop') {
if(layerId != 'default') {
@ -434,7 +423,7 @@ export class Layouts {
case 'K_LCONTROL':
if(chiral) {
if(layerId.indexOf('leftctrl') != -1) {
key['sp'] = buttonClasses['SHIFT-ON'];
key['sp'] = ButtonClasses.specialActive;
}
break;
}
@ -442,35 +431,35 @@ export class Layouts {
case 'K_RCONTROL':
if(chiral) {
if(layerId.indexOf('rightctrl') != -1) {
key['sp'] = buttonClasses['SHIFT-ON'];
key['sp'] = ButtonClasses.specialActive;
}
break;
}
case 'K_CONTROL':
if(layerId.indexOf('ctrl') != -1) {
if(!chiral || (layerId.indexOf('leftctrl') != -1 && layerId.indexOf('rightctrl') != -1)) {
key['sp'] = buttonClasses['SHIFT-ON'];
key['sp'] = ButtonClasses.specialActive;
}
}
break;
case 'K_LALT':
if(chiral) {
if(layerId.indexOf('leftalt') != -1) {
key['sp'] = buttonClasses['SHIFT-ON'];
key['sp'] = ButtonClasses.specialActive;
}
break;
}
case 'K_RALT':
if(chiral) {
if(layerId.indexOf('rightalt') != -1) {
key['sp'] = buttonClasses['SHIFT-ON'];
key['sp'] = ButtonClasses.specialActive;
}
break;
}
case 'K_ALT':
if(layerId.indexOf('alt') != -1) {
if(!chiral || (layerId.indexOf('leftalt') != -1 && layerId.indexOf('rightalt') != -1)) {
key['sp'] = buttonClasses['SHIFT-ON'];
key['sp'] = ButtonClasses.specialActive;
}
}
break;
@ -480,7 +469,7 @@ export class Layouts {
keys.splice(j--, 1);
keys[0]['width']=KEY_102_WIDTH;
} else {
keys[j]['sp']=buttonClasses['HIDDEN'];
keys[j]['sp']=ButtonClasses.spacer;
}
}
break;

View file

@ -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
});

View file

@ -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];

View file

@ -64,7 +64,30 @@ export interface TouchLayoutKey {
hint?: string;
};
export enum TouchLayoutKeySp { normal=0, special=1, specialActive=2, deadkey=8, blank=9, spacer=10 };
export const enum TouchLayoutKeySp {
normal=0,
/** A 'frame' key, such as Shift or Enter, which is styled accordingly; uses
* the 'KeymanwebOsk' font on KeymanWeb */
special=1,
/** A 'frame' key, such as Shift or Enter, which is styled accordingly and is
* highlighted to indicate it is active, such as the shift key on a shift
* layer; uses the 'KeymanwebOsk' font on KeymanWeb */
specialActive=2,
/** **KeymanWeb runtime private use:** a variant of `special` with the
* keyboard font rather than 'KeymanwebOsk' font */
customSpecial=3,
/** **KeymanWeb runtime private use:** a variant of `specialActive` with the
* keyboard font rather than 'KeymanwebOsk' font. */
customSpecialActive=4,
/** A styling signal to indicate that the key may have 'deadkey' type
* behaviour. */
deadkey=8,
/** A key which is rendered as a blank keycap, blocks any interaction */
blank=9,
/** Renders the key only as a gap or spacer, blocks any interaction */
spacer=10
};
export type TouchLayoutKeyPad = number; // 0-100000
export type TouchLayoutKeyWidth = number; // 0-100000

View file

@ -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() {

View file

@ -21,7 +21,12 @@ export class KPJFileReader {
emptyTag: ''
});
parser.parseString(file, (e: unknown, r: unknown) => { data = r as KPJFile });
parser.parseString(file, (e: unknown, r: unknown) => {
if(e) {
throw e;
}
data = r as KPJFile;
});
data = this.boxArrays(data);
for(let file of data.KeymanDeveloperProject?.Files?.File) {
// xml2js imports <Details/> as '' so we will just delete the empty string

View file

@ -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;

View file

@ -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;
});

View file

@ -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';

View file

@ -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;
};

View file

@ -1,6 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE keyboardTest3 SYSTEM "../../../../../resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest3.dtd">
<keyboardTest3 conformsTo="techpreview">
<!--
Read by:
- common/web/types/test/ldml-keyboard/test-ldml-keyboard-testdata-reader.ts
Also see the other test-fr.xml in developer
-->
<info keyboard="fr-t-k0-azerty.xml" author="Team Keyboard" name="fr-test" />
<repertoire name="simple-repertoire" chars="[a b c d e \u{22}]" type="simple" /> <!-- verify that these outputs are all available from simple keys on any layer, for all form factors -->
<repertoire name="chars-repertoire" chars="[á é ó]" type="gesture" /> <!-- verify that these outputs are all available from simple or gesture keys on any layer, for touch -->
@ -17,6 +23,8 @@
<!-- tests by specifying 'to' output char -->
<emit to="v"/>
<check result="abc\u0022...stuv" />
<backspace />
<check result="abc\u0022...stu" />
</test>
</tests>
</keyboardTest3>

View file

@ -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');
},
}
]);

View file

@ -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

View file

@ -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);
/*
```

View file

@ -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

View file

@ -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 "$@"

View file

@ -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

View file

@ -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.

View file

@ -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

View file

@ -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"

View file

@ -10,7 +10,7 @@
"node_modules"
],
"files": [
"keyboardprocessor_ldml.ts",
"keyman_core_ldml.ts",
"ldml-const-builder.ts"
]
}

View file

@ -10,6 +10,6 @@
"node_modules"
],
"files": [
"keyboardprocessor_ldml.ts"
"keyman_core_ldml.ts"
]
}

View file

@ -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')
subdir('tests')

138
core/src/action.cpp Normal file
View file

@ -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 <cassert>
#include <algorithm>
#include <sstream>
#include <memory>
#include <keyman/keyman_core_api.h>
#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<km_core_actions> actions(new km_core_actions);
// Set actions default values
std::vector<km_core_context_item> output;
std::vector<km_core_option_item> 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<km_core_option_scope>(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<km_core_usv[]> 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();
}

20
core/src/action.hpp Normal file
View file

@ -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 <keyman/keyman_core_api.h>
namespace km {
namespace core
{
km_core_actions* action_item_list_to_actions_object(
km_core_action_item const *action_items
);
} // namespace core
} // namespace km

View file

@ -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
{
};

View file

@ -13,7 +13,7 @@
#include <keyman/keyman_core_api_debug.h>
namespace km {
namespace kbp
namespace core
{
class debug_items : public std::vector<km_core_state_debug_item>
@ -66,6 +66,6 @@ void debug_items::set_enabled(bool value) noexcept {
_is_enabled = value;
}
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -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

View file

@ -1,9 +1,11 @@
/* Debugging */
#pragma once
#include <keyman/keyman_core_api_bits.h>
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

View file

@ -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

View file

@ -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<option> _default_opts;
void render();
@ -48,7 +48,7 @@ namespace kbp
keyboard_attributes & operator = (keyboard_attributes const &) = delete;
keyboard_attributes & operator = (keyboard_attributes &&);
friend json & operator << (json &, km::kbp::keyboard_attributes const &);
friend json & operator << (json &, km::core::keyboard_attributes const &);
options_store const & default_opts_store() const noexcept { return _default_opts; }
options_store & default_opts_store() noexcept { return _default_opts; }
@ -56,7 +56,7 @@ namespace kbp
path_type const & path() const noexcept { return _folder_path; }
};
json & operator << (json &, km::kbp::keyboard_attributes const &);
json & operator << (json &, km::core::keyboard_attributes const &);
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -0,0 +1,64 @@
/*
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.
*/
#include <cassert>
#include <algorithm>
#include <sstream>
#include <keyman/keyman_core_api.h>
#include "jsonpp.hpp"
#include "processor.hpp"
#include "state.hpp"
#include "action.hpp"
using namespace km::core;
km_core_actions* km_core_state_get_actions(
km_core_state const *state
) {
assert(state);
if(!state) {
return nullptr;
}
km_core_actions* actions = nullptr;
auto action_items = km_core_state_action_items(state, nullptr);
if(!action_items) {
return nullptr;
}
actions = action_item_list_to_actions_object(action_items);
return actions;
}
km_core_status km_core_actions_dispose(
km_core_actions* actions
) {
if(actions == nullptr) {
return KM_CORE_STATUS_OK;
}
if(actions->output) {
delete[] actions->output;
}
if(actions->persist_options) {
for(auto option = actions->persist_options; option->scope; option++) {
delete[] option->key;
delete[] option->value;
}
delete[] actions->persist_options;
}
delete actions;
return KM_CORE_STATUS_OK;
}

View file

@ -140,6 +140,13 @@ km_core_status km_core_context_items_to_utf16(km_core_context_item const *ci,
sz_ptr);
}
km_core_status km_core_context_items_to_utf32(km_core_context_item const *ci,
km_core_usv *buf, size_t * sz_ptr)
{
return _context_items_to<utf32>(ci,
reinterpret_cast<utf32::codeunit_t *>(buf),
sz_ptr);
}
void km_core_context_items_dispose(km_core_context_item *ci)
{
@ -252,7 +259,7 @@ km_core_context_item_list_size(km_core_context_item const *context_items)
return n;
}
json & operator << (json & j, km::kbp::context const & ctxt) {
json & operator << (json & j, km::core::context const & ctxt) {
j << json::array;
for (auto & i: ctxt) j << i;
return j << json::close;

View file

@ -13,7 +13,7 @@
#include "processor.hpp"
#include "state.hpp"
using namespace km::kbp;
using namespace km::core;
km_core_status
km_core_state_debug_set(

View file

@ -18,7 +18,7 @@
#include "ldml/ldml_processor.hpp"
#include "mock/mock_processor.hpp"
using namespace km::kbp;
using namespace km::core;
namespace
{

View file

@ -11,6 +11,7 @@
#include <cassert>
#include <algorithm>
#include <sstream>
#include <memory>
#include <keyman/keyman_core_api.h>
#include "jsonpp.hpp"
@ -18,7 +19,7 @@
#include "processor.hpp"
#include "state.hpp"
using namespace km::kbp;
using namespace km::core;
// Forward declarations
class context;
@ -69,7 +70,7 @@ km_core_context *km_core_state_context(km_core_state *state)
return static_cast<km_core_context *>(&state->context());
}
km_core_status kbp_state_get_intermediate_context(
km_core_status km_core_state_get_intermediate_context(
km_core_state *state,
km_core_context_item ** context_items
) {
@ -259,3 +260,99 @@ void km_core_state_imx_deregister_callback(km_core_state *state)
}
state->imx_deregister_callback();
}
bool is_context_valid(km_core_cp const * context, km_core_cp const * cached_context) {
km_core_cp const* context_p = context;
while(*context_p) {
context_p++;
}
km_core_cp const* cached_context_p = cached_context;
while(*cached_context_p) {
cached_context_p++;
}
// we need to compare from the end of the cached context
for(; context_p >= context && cached_context_p >= cached_context; context_p--, cached_context_p--) {
if(*context_p != *cached_context_p) {
// The cached context doesn't match the application context, so it is
// invalid
return false;
}
}
if(cached_context_p > cached_context) {
// if the cached context is longer than the application context, then we also
// assume that it is invalid
return false;
}
// It's acceptable for the application context to be longer than the cached
// context, so if we match the whole cached context, we can safely return true
return true;
}
km_core_context_status km_core_state_context_set_if_needed(
km_core_state *state,
km_core_cp const *application_context
) {
assert(state != nullptr);
assert(application_context != nullptr);
if(state == nullptr || application_context == nullptr) {
return KM_CORE_CONTEXT_STATUS_INVALID_ARGUMENT;
}
size_t buf_size;
km_core_context_item* context_items = nullptr;
auto context = km_core_state_context(state);
if(km_core_context_get(context, &context_items) != KM_CORE_STATUS_OK) {
return KM_CORE_CONTEXT_STATUS_ERROR;
}
if(km_core_context_items_to_utf16(context_items, nullptr, &buf_size) != KM_CORE_STATUS_OK) {
km_core_context_items_dispose(context_items);
return KM_CORE_CONTEXT_STATUS_ERROR;
}
std::unique_ptr<km_core_cp[]> cached_context(new km_core_cp[buf_size]);
km_core_status status = km_core_context_items_to_utf16(context_items, cached_context.get(), &buf_size);
km_core_context_items_dispose(context_items);
if(status != KM_CORE_STATUS_OK) {
return KM_CORE_CONTEXT_STATUS_ERROR;
}
bool is_valid = is_context_valid(application_context, cached_context.get());
if(is_valid) {
// We keep the context as is
return KM_CORE_CONTEXT_STATUS_UNCHANGED;
}
km_core_context_item* new_context_items = nullptr;
// We replace the cached context with the current application context
status = km_core_context_items_from_utf16(application_context, &new_context_items);
if (status != KM_CORE_STATUS_OK) {
km_core_context_clear(context);
return KM_CORE_CONTEXT_STATUS_CLEARED;
}
km_core_context_set(context, new_context_items);
km_core_context_items_dispose(new_context_items);
return KM_CORE_CONTEXT_STATUS_UPDATED;
}
km_core_status km_core_state_context_clear(
km_core_state *state
) {
assert(state != nullptr);
if(state == nullptr) {
return KM_CORE_STATUS_INVALID_ARGUMENT;
}
km_core_context_clear(km_core_state_context(state));
return KM_CORE_STATUS_OK;
}

View file

@ -4,7 +4,7 @@
*/
#include <kmx/kmx_processevent.h>
using namespace km::kbp;
using namespace km::core;
using namespace kmx;
void KMX_Actions::ResetQueue()

View file

@ -8,7 +8,7 @@
#include "kmx_base.h"
namespace km {
namespace kbp {
namespace core {
namespace kmx {
#define MAXACTIONQUEUE 1024
@ -59,6 +59,6 @@ public:
};
} // namespace kmx
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -11,7 +11,7 @@
#endif
namespace km {
namespace kbp {
namespace core {
namespace kmx {
typedef struct tagSTORE
@ -95,5 +95,5 @@ typedef struct tagKMSTATE
enum ProcessStringReturn { psrPostMessages, psrCheckMatches };
} // namespace kmx
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -5,7 +5,7 @@
#include <keyman/keyman_core_api_consts.h>
#include <kmx/kmx_processevent.h>
using namespace km::kbp;
using namespace km::core;
using namespace kmx;
/**

View file

@ -2,7 +2,7 @@
#include "kmx_processevent.h"
namespace km {
namespace kbp {
namespace core {
namespace kmx {
const struct char_to_vkey s_char_to_vkey[] = {
@ -105,5 +105,5 @@ const struct char_to_vkey s_char_to_vkey[] = {
};
} // namespace kmx
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -4,7 +4,7 @@
*/
#include <kmx/kmx_processevent.h>
using namespace km::kbp;
using namespace km::core;
using namespace kmx;
/* KMX_Context */

View file

@ -10,7 +10,7 @@
#define MAXCONTEXT 64
namespace km {
namespace kbp {
namespace core {
namespace kmx {
class KMX_Context
@ -118,5 +118,5 @@ public:
};
} // namespace kmx
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -3,7 +3,7 @@
#include "kmx_processevent.h"
using namespace km::kbp;
using namespace km::core;
using namespace kmx;
/* This array is lifted from preservedkeymap.cpp */
@ -116,7 +116,7 @@ const struct
};
KMX_BOOL km::kbp::kmx::MapUSCharToVK(KMX_WORD ch, PKMX_WORD puKey, PKMX_DWORD puShiftFlags) {
KMX_BOOL km::core::kmx::MapUSCharToVK(KMX_WORD ch, PKMX_WORD puKey, PKMX_DWORD puShiftFlags) {
assert(puKey != NULL);
assert(puShiftFlags != NULL);
if (ch >= 0x20 && ch < 0x7F) {

View file

@ -3,7 +3,7 @@
#include "kmx_base.h"
namespace km {
namespace kbp {
namespace core {
namespace kmx {
#define VK_SPACE 0x20
@ -32,5 +32,5 @@ namespace kmx {
KMX_BOOL MapUSCharToVK(KMX_WORD ch, PKMX_WORD puKey, PKMX_DWORD puShiftFlags);
} // namespace kmx
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -8,7 +8,7 @@
#include <kmx/kmx_processevent.h>
using namespace km::kbp;
using namespace km::core;
using namespace kmx;
void KMX_DebugItems::push_item(
@ -93,4 +93,4 @@ void KMX_DebugItems::fill_store_offsets(km_core_state_debug_kmx_info *info, PKMX
}
info->store_offsets[n] = 0xFFFF;
}
}

View file

@ -14,7 +14,7 @@
#include <debug.hpp>
namespace km {
namespace kbp {
namespace core {
namespace kmx {
class KMX_DebugItems
@ -143,5 +143,5 @@ KMX_DebugItems::push_rule_exit(
}
} // namespace kmx
} // namespace kbp
} // namespace km
} // namespace core
} // namespace km

View file

@ -7,7 +7,7 @@
#include <state.hpp>
#include <option.hpp>
using namespace km::kbp;
using namespace km::core;
using namespace kmx;
namespace {

View file

@ -5,7 +5,7 @@
#include "kmx_base.h"
namespace km {
namespace kbp {
namespace core {
namespace kmx {
class KMX_Environment {
@ -30,5 +30,5 @@ public:
};
} // namespace kmx
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -6,7 +6,7 @@
#include <assert.h>
#include "kmx_file_validator.hpp"
using namespace km::kbp;
using namespace km::core;
using namespace kmx;
#if defined(_WIN32) || defined(_WIN64)
@ -20,7 +20,7 @@ KMX_BOOL KMX_ProcessEvent::Load(km_core_path_name KeyboardName)
return TRUE;
}
const int km::kbp::kmx::CODE__SIZE[] = {
const int km::core::kmx::CODE__SIZE[] = {
-1, // undefined 0x00
1, // CODE_ANY 0x01
2, // CODE_INDEX 0x02

View file

@ -6,10 +6,10 @@
#include <kmx_file.h>
#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
@ -27,8 +27,8 @@ public:
};
#ifdef KMN_KBP
#ifdef KM_CORE_LIBRARY
} // namespace kmx
} // namespace kbp
} // namespace core
} // namespace km
#endif

View file

@ -4,7 +4,7 @@
*/
#include "kmx_processevent.h"
using namespace km::kbp;
using namespace km::core;
using namespace kmx;
#define MAX_RSHIFT 24
@ -22,7 +22,7 @@ static KMX_DWORD legalRuleStates[MAX_RSHIFT] = {
};
// Simulate RALT -> LCtrl+RAlt -> columns marked with *
//
//
// Truth table: matching key states
// 1 = yes match always
// 2 = RAlt mapped as LCtrl+RAlt by keyboard driver
@ -109,14 +109,14 @@ KMX_BOOL KMX_ProcessEvent::IsEquivalentShift(KMX_UINT rshift, KMX_UINT kshift) {
for(i = 0; i < MAX_RSHIFT; i++) {
if(rshift == legalRuleStates[i]) {
rshift = i;
rshift = i;
break;
}
}
if(i == MAX_RSHIFT) return FALSE;
for(i = 0; i < MAX_KSHIFT; i++) {
if(kshift == legalKeyStates[i]) {
if(kshift == legalKeyStates[i]) {
kshift = i;
break;
}

View file

@ -6,7 +6,7 @@
#include "kmx_processevent.h"
#include <option.hpp>
using namespace km::kbp;
using namespace km::core;
using namespace kmx;
int KMX_Options::_GetIndex(std::u16string const &key) const {

View file

@ -11,7 +11,7 @@
#include "kmx_debugger.h"
namespace km {
namespace kbp {
namespace core {
class abstract_processor;
@ -68,5 +68,5 @@ STORE const * KMX_Options::end() const {
}
} // namespace kmx
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -10,13 +10,13 @@
#include <kmx/kmx_xstring.h>
#include "kmx_processevent.h" // for debug functions
#include "ldml/keyboardprocessor_ldml.h"
#include "ldml/keyman_core_ldml.h"
#include <assert.h>
#include "kmx_plus.h"
namespace km {
namespace kbp {
namespace core {
namespace kmx {
/**
@ -381,7 +381,7 @@ COMP_KMXPLUS_ELEM_ELEMENT::loadAsStringList(KMX_DWORD length, const COMP_KMXPLUS
if (o.type() == LDML_ELEM_FLAGS_TYPE_STR) {
// fetch the string
const auto str16 = strs.get(o.element);
str = km::kbp::kmx::u16string_to_u32string(str16);
str = km::core::kmx::u16string_to_u32string(str16);
} else {
// single char
str = std::u32string(1, (km_core_usv)o.element);
@ -1289,5 +1289,5 @@ const COMP_KMXPLUS_VARS_ITEM *COMP_KMXPLUS_VARS::findByStringId(KMX_DWORD strId)
} // namespace kmx
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -10,12 +10,12 @@
#include <km_types.h>
#include <kmx/kmx_base.h>
#include <kmx_file.h>
#include <ldml/keyboardprocessor_ldml.h>
#include <ldml/keyman_core_ldml.h>
#include <list>
#include <deque>
namespace km {
namespace kbp {
namespace core {
namespace kmx {
/**
@ -111,7 +111,7 @@ struct COMP_KMXPLUS_ELEM_ELEMENT {
* @param length number of elements, including this one
* @return the string elements as a string array
*/
std::deque<std::u32string> loadAsStringList(KMX_DWORD length, const km::kbp::kmx::COMP_KMXPLUS_STRS &strs) const;
std::deque<std::u32string> loadAsStringList(KMX_DWORD length, const km::core::kmx::COMP_KMXPLUS_STRS &strs) const;
/** @return element type */
KMX_DWORD type() const;
@ -782,5 +782,5 @@ class kmx_plus {
#endif
} // namespace kmx
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -6,14 +6,14 @@
#include "state.hpp"
#include <keyman/keyman_core_api_consts.h>
using namespace km::kbp;
using namespace km::core;
using namespace kmx;
/* Globals */
KMX_BOOL km::kbp::kmx::g_debug_ToConsole = FALSE;
KMX_BOOL km::kbp::kmx::g_debug_KeymanLog = TRUE;
KMX_BOOL km::kbp::kmx::g_silent = FALSE;
KMX_BOOL km::core::kmx::g_debug_ToConsole = FALSE;
KMX_BOOL km::core::kmx::g_debug_KeymanLog = TRUE;
KMX_BOOL km::core::kmx::g_silent = FALSE;
/*
* KMX_ProcessEvent
@ -84,7 +84,7 @@ KMX_BOOL KMX_ProcessEvent::ProcessEvent(
) {
LPKEYBOARD kbd = m_keyboard.Keyboard;
m_kbp_state = state;
m_core_state = state;
// If debugging is enabled, then ...
DeleteInternalDebugItems();
@ -106,7 +106,7 @@ KMX_BOOL KMX_ProcessEvent::ProcessEvent(
if (kbd->StartGroup[BEGIN_UNICODE] == (KMX_DWORD) -1) {
DebugLog("Non-Unicode keyboards are not supported.");
m_kbp_state = nullptr;
m_core_state = nullptr;
return FALSE;
}
@ -124,7 +124,7 @@ KMX_BOOL KMX_ProcessEvent::ProcessEvent(
}
if (!isKeyDown) {
m_kbp_state = nullptr;
m_core_state = nullptr;
return FALSE;
}
@ -149,7 +149,7 @@ KMX_BOOL KMX_ProcessEvent::ProcessEvent(
DeleteInternalDebugItems();
}
m_kbp_state = nullptr;
m_core_state = nullptr;
return !fOutputKeystroke;
}
@ -488,7 +488,7 @@ int KMX_ProcessEvent::PostString(PKMX_WCHAR str, LPKEYBOARD lpkb, PKMX_WCHAR end
case CODE_CALL:
p++;
m_kbp_state->imx_callback(*p-1);
m_core_state->imx_callback(*p-1);
FoundUse = TRUE;
break;
case CODE_USE: // use another group
@ -519,7 +519,7 @@ int KMX_ProcessEvent::PostString(PKMX_WCHAR str, LPKEYBOARD lpkb, PKMX_WCHAR end
case CODE_RESETOPT:
p++;
n1 = *p - 1;
GetOptions()->Reset(m_kbp_state->processor(), n1);
GetOptions()->Reset(m_core_state->processor(), n1);
break;
case CODE_SAVEOPT:
p++;
@ -792,8 +792,8 @@ PKMX_WCHAR KMX_ProcessEvent::GetSystemStore(LPKEYBOARD kb, KMX_DWORD SystemID)
void KMX_ProcessEvent::CreateInternalDebugItems() {
assert(m_debug_items == nullptr);
assert(m_kbp_state != nullptr);
m_debug_items = new KMX_DebugItems(&m_kbp_state->debug_items());
assert(m_core_state != nullptr);
m_debug_items = new KMX_DebugItems(&m_core_state->debug_items());
m_options.SetInternalDebugItems(m_debug_items);
}

View file

@ -1,8 +1,8 @@
#pragma once
#ifndef KMN_KBP
#define KMN_KBP
#ifndef KM_CORE_LIBRARY
#define KM_CORE_LIBRARY
#endif
#ifndef USE_CHAR16_T
#define USE_CHAR16_T
@ -27,7 +27,7 @@
/***************************************************************************/
namespace km {
namespace kbp {
namespace core {
namespace kmx {
/* Utility */
@ -40,7 +40,7 @@ private:
PKMX_WCHAR m_miniContext;
int m_miniContextIfLen; // number of if() statements excluded from start of m_miniContext
KMSTATE m_state;
km_core_state *m_kbp_state;
km_core_state *m_core_state;
kmx::KMX_Actions m_actions;
@ -127,5 +127,5 @@ struct char_to_vkey {
extern const struct char_to_vkey s_char_to_vkey[];
} // namespace kmx
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -3,7 +3,7 @@
#include "kmx/kmx_processor.hpp"
#include <map>
using namespace km::kbp;
using namespace km::core;
using namespace kmx;
// TODO consolodate with appint.cpp and put in public library.
@ -38,7 +38,7 @@ km_core_status kmx_processor::validate() const {
return _valid ? KM_CORE_STATUS_OK : KM_CORE_STATUS_INVALID_KEYBOARD;
}
kmx_processor::kmx_processor(kbp::path p) {
kmx_processor::kmx_processor(core::path p) {
p.replace_extension(".kmx");
_valid = bool(_kmx.Load(p.c_str()));
@ -283,8 +283,8 @@ kmx_processor::process_event(
switch (c->type) {
case KM_CORE_CT_CHAR:
{
km::kbp::kmx::char16_single buf;
const int len = km::kbp::kmx::Utf32CharToUtf16(c->character, buf);
km::core::kmx::char16_single buf;
const int len = km::core::kmx::Utf32CharToUtf16(c->character, buf);
ctxt.append(buf.ch, len);
}
break;

View file

@ -15,7 +15,7 @@
#include "processor.hpp"
namespace km {
namespace kbp
namespace core
{
class kmx_processor : public abstract_processor
{
@ -82,5 +82,5 @@ namespace kbp
};
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -9,10 +9,10 @@
#include "kmx_processevent.h"
#include "utfcodec.hpp"
using namespace km::kbp;
using namespace km::core;
using namespace kmx;
const km_core_cp *km::kbp::kmx::u16chr(const km_core_cp *p, km_core_cp ch) {
const km_core_cp *km::core::kmx::u16chr(const km_core_cp *p, km_core_cp ch) {
while (*p) {
if (*p == ch) return p;
p++;
@ -20,7 +20,7 @@ const km_core_cp *km::kbp::kmx::u16chr(const km_core_cp *p, km_core_cp ch) {
return ch == 0 ? p : NULL;
}
const km_core_cp *km::kbp::kmx::u16cpy(km_core_cp *dst, const km_core_cp *src) {
const km_core_cp *km::core::kmx::u16cpy(km_core_cp *dst, const km_core_cp *src) {
km_core_cp *o = dst;
while (*src) {
*dst++ = *src++;
@ -29,7 +29,7 @@ const km_core_cp *km::kbp::kmx::u16cpy(km_core_cp *dst, const km_core_cp *src) {
return o;
}
const km_core_cp *km::kbp::kmx::u16ncpy(km_core_cp *dst, const km_core_cp *src, size_t max) {
const km_core_cp *km::core::kmx::u16ncpy(km_core_cp *dst, const km_core_cp *src, size_t max) {
km_core_cp *o = dst;
while (*src && max > 0) {
*dst++ = *src++;
@ -42,7 +42,7 @@ const km_core_cp *km::kbp::kmx::u16ncpy(km_core_cp *dst, const km_core_cp *src,
return o;
}
size_t km::kbp::kmx::u16len(const km_core_cp *p) {
size_t km::core::kmx::u16len(const km_core_cp *p) {
int i = 0;
while (*p) {
p++;
@ -51,7 +51,7 @@ size_t km::kbp::kmx::u16len(const km_core_cp *p) {
return i;
}
int km::kbp::kmx::u16cmp(const km_core_cp *p, const km_core_cp *q) {
int km::core::kmx::u16cmp(const km_core_cp *p, const km_core_cp *q) {
while (*p && *q) {
if (*p != *q) return *p - *q;
p++;
@ -60,7 +60,7 @@ int km::kbp::kmx::u16cmp(const km_core_cp *p, const km_core_cp *q) {
return *p - *q;
}
int km::kbp::kmx::u16icmp(const km_core_cp *p, const km_core_cp *q) {
int km::core::kmx::u16icmp(const km_core_cp *p, const km_core_cp *q) {
while (*p && *q) {
if (toupper(*p) != toupper(*q)) return *p - *q;
p++;
@ -69,7 +69,7 @@ int km::kbp::kmx::u16icmp(const km_core_cp *p, const km_core_cp *q) {
return *p - *q;
}
int km::kbp::kmx::u16ncmp(const km_core_cp *p, const km_core_cp *q, size_t count) {
int km::core::kmx::u16ncmp(const km_core_cp *p, const km_core_cp *q, size_t count) {
while (*p && *q && count) {
if (*p != *q) return *p - *q;
p++;
@ -81,7 +81,7 @@ int km::kbp::kmx::u16ncmp(const km_core_cp *p, const km_core_cp *q, size_t count
return 0;
}
km_core_cp *km::kbp::kmx::u16tok(km_core_cp *p, km_core_cp ch, km_core_cp **ctx) {
km_core_cp *km::core::kmx::u16tok(km_core_cp *p, km_core_cp ch, km_core_cp **ctx) {
if (!p) {
p = *ctx;
if (!p) return NULL;
@ -103,7 +103,7 @@ km_core_cp *km::kbp::kmx::u16tok(km_core_cp *p, km_core_cp ch, km_core_cp **ctx)
return p;
}
km_core_cp *km::kbp::kmx::u16dup(km_core_cp *src) {
km_core_cp *km::core::kmx::u16dup(km_core_cp *src) {
km_core_cp *dup = new km_core_cp[u16len(src) + 1];
memcpy(dup, src, (u16len(src) + 1) * sizeof(km_core_cp));
return dup;
@ -120,7 +120,7 @@ km_core_cp *km::kbp::kmx::u16dup(km_core_cp *src) {
*
* xstrlen calculates the length of a string, ignoring some special chars.
*/
PKMX_WCHAR km::kbp::kmx::incxstr(PKMX_WCHAR p) {
PKMX_WCHAR km::core::kmx::incxstr(PKMX_WCHAR p) {
if (*p == 0)
return p;
@ -154,7 +154,7 @@ PKMX_WCHAR km::kbp::kmx::incxstr(PKMX_WCHAR p) {
return p;
}
PKMX_WCHAR km::kbp::kmx::decxstr(PKMX_WCHAR p, PKMX_WCHAR pStart)
PKMX_WCHAR km::core::kmx::decxstr(PKMX_WCHAR p, PKMX_WCHAR pStart)
{
PKMX_WCHAR q;
@ -197,7 +197,7 @@ PKMX_WCHAR km::kbp::kmx::decxstr(PKMX_WCHAR p, PKMX_WCHAR pStart)
return p;
}
int km::kbp::kmx::xstrlen_ignoreifopt(PKMX_WCHAR p)
int km::core::kmx::xstrlen_ignoreifopt(PKMX_WCHAR p)
{
int i;
for(i = 0; *p; i++, p=incxstr(p))
@ -207,21 +207,21 @@ int km::kbp::kmx::xstrlen_ignoreifopt(PKMX_WCHAR p)
return i;
}
int km::kbp::kmx::xstrlen(PKMX_WCHAR p)
int km::core::kmx::xstrlen(PKMX_WCHAR p)
{
int i;
for(i = 0; *p; i++, p=incxstr(p));
return i;
}
int km::kbp::kmx::xstrpos(PKMX_WCHAR p1, PKMX_WCHAR p)
int km::core::kmx::xstrpos(PKMX_WCHAR p1, PKMX_WCHAR p)
{
int i;
for(i = 0; p < p1; p = incxstr(p), i++);
return i;
}
PKMX_WCHAR km::kbp::kmx::xstrchr(PKMX_WCHAR buf, PKMX_WCHAR chr)
PKMX_WCHAR km::core::kmx::xstrchr(PKMX_WCHAR buf, PKMX_WCHAR chr)
{
for(PKMX_WCHAR q = incxstr(buf); *buf; buf = q, q = incxstr(buf))
if(!u16ncmp(buf, chr, (intptr_t)(q-buf)))
@ -229,14 +229,14 @@ PKMX_WCHAR km::kbp::kmx::xstrchr(PKMX_WCHAR buf, PKMX_WCHAR chr)
return NULL;
}
int km::kbp::kmx::xchrcmp(PKMX_WCHAR ch1, PKMX_WCHAR ch2)
int km::core::kmx::xchrcmp(PKMX_WCHAR ch1, PKMX_WCHAR ch2)
{
PKMX_WCHAR nch1 = incxstr(ch1);
if(nch1 == ch1) return *ch2 - *ch1; /* comparing *ch2 to nul */
return u16ncmp(ch1, ch2, (intptr_t)(nch1-ch1));
}
PKMX_WCHAR km::kbp::kmx::strtowstr(PKMX_CHAR in)
PKMX_WCHAR km::core::kmx::strtowstr(PKMX_CHAR in)
{
PKMX_WCHAR result;
@ -247,7 +247,7 @@ PKMX_WCHAR km::kbp::kmx::strtowstr(PKMX_CHAR in)
return result;
}
PKMX_CHAR km::kbp::kmx::wstrtostr(PKMX_WCHAR in)
PKMX_CHAR km::core::kmx::wstrtostr(PKMX_WCHAR in)
{
PKMX_CHAR result;

View file

@ -3,7 +3,7 @@
#include "kmx_base.h"
namespace km {
namespace kbp {
namespace core {
namespace kmx {
const char16_t Uni_LEAD_SURROGATE_START = 0xD800;
@ -25,12 +25,12 @@ const km_core_usv Uni_MAX_CODEPOINT = 0x10FFFF;
* @brief True if a lead surrogate
* \def Uni_IsSurrogate1
*/
#define Uni_IsSurrogate1(ch) ((ch) >= km::kbp::kmx::Uni_LEAD_SURROGATE_START && (ch) <= km::kbp::kmx::Uni_LEAD_SURROGATE_END)
#define Uni_IsSurrogate1(ch) ((ch) >= km::core::kmx::Uni_LEAD_SURROGATE_START && (ch) <= km::core::kmx::Uni_LEAD_SURROGATE_END)
/**
* @brief True if a trail surrogate
* \def Uni_IsSurrogate2
*/
#define Uni_IsSurrogate2(ch) ((ch) >= km::kbp::kmx::Uni_TRAIL_SURROGATE_START && (ch) <= km::kbp::kmx::Uni_TRAIL_SURROGATE_END)
#define Uni_IsSurrogate2(ch) ((ch) >= km::core::kmx::Uni_TRAIL_SURROGATE_START && (ch) <= km::core::kmx::Uni_TRAIL_SURROGATE_END)
/**
* @brief True if any surrogate
@ -42,7 +42,7 @@ const km_core_usv Uni_MAX_CODEPOINT = 0x10FFFF;
* @brief Returns true if BMP (Plane 0)
* \def Uni_IsBMP
*/
#define Uni_IsBMP(ch) ((ch) <= km::kbp::kmx::Uni_BMP_END)
#define Uni_IsBMP(ch) ((ch) <= km::core::kmx::Uni_BMP_END)
/**
* @brief Convert two UTF-16 surrogates into one UTF-32 codepoint
@ -50,7 +50,7 @@ const km_core_usv Uni_MAX_CODEPOINT = 0x10FFFF;
* @param cl trail surrogate - Uni_IsSurrogate2(cl) must == true
* \def Uni_SurrogateToUTF
*/
#define Uni_SurrogateToUTF32(ch, cl) (((ch) - km::kbp::kmx::Uni_LEAD_SURROGATE_START) * 0x400 + ((cl) - km::kbp::kmx::Uni_TRAIL_SURROGATE_START) + km::kbp::kmx::Uni_SMP_START)
#define Uni_SurrogateToUTF32(ch, cl) (((ch) - km::core::kmx::Uni_LEAD_SURROGATE_START) * 0x400 + ((cl) - km::core::kmx::Uni_TRAIL_SURROGATE_START) + km::core::kmx::Uni_SMP_START)
/**
* @brief Convert UTF-32 BMP to UTF-16 BMP
@ -59,8 +59,8 @@ const km_core_usv Uni_MAX_CODEPOINT = 0x10FFFF;
*/
#define Uni_UTF32BMPToUTF16(ch) ((ch) & Uni_FFFF_NONCHARACTER)
#define Uni_UTF32ToSurrogate1(ch) (char16_t)(((ch) - km::kbp::kmx::Uni_SMP_START) / 0x400 + km::kbp::kmx::Uni_LEAD_SURROGATE_START)
#define Uni_UTF32ToSurrogate2(ch) (char16_t)(((ch) - km::kbp::kmx::Uni_SMP_START) % 0x400 + km::kbp::kmx::Uni_TRAIL_SURROGATE_START)
#define Uni_UTF32ToSurrogate1(ch) (char16_t)(((ch) - km::core::kmx::Uni_SMP_START) / 0x400 + km::core::kmx::Uni_LEAD_SURROGATE_START)
#define Uni_UTF32ToSurrogate2(ch) (char16_t)(((ch) - km::core::kmx::Uni_SMP_START) % 0x400 + km::core::kmx::Uni_TRAIL_SURROGATE_START)
/**
* @returns true if the character is a noncharacter
@ -224,5 +224,5 @@ inline bool Uni_IsValid(km_core_usv start, km_core_usv end) {
} // namespace kmx
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -9,7 +9,7 @@ working on layr, using disp as a model from https://github.com/keyma
## Constants and Scaffolding
- *Edit/Commit*: `core/include/ldml/keyboardprocessor_ldml.ts`
- *Edit/Commit*: `core/include/ldml/keyman_core_ldml.ts`
- update `SectionIdent` and keep in order: `'layr' |`
- update `Constants.section` (near the end of the file) and keep in order: `'layr': 'layr',`
- add a comment block in order `layr section`
@ -18,7 +18,7 @@ working on layr, using disp as a model from https://github.com/keyma
- add parameters for each flag/bitfield
- Check indentation, check for copypasta errs!
- Run: `./core/tools/ldml-const-builder/build.sh clean build run`
- Verify/Commit: `core/include/ldml/keyboardprocessor_ldml.h`
- Verify/Commit: `core/include/ldml/keyman_core_ldml.h`
## XML changes

View file

@ -6,12 +6,13 @@
*/
#include <fstream>
#include <algorithm>
#include "ldml/ldml_processor.hpp"
#include "state.hpp"
#include "kmx_file.h"
#include "kmx/kmx_plus.h"
#include "kmx/kmx_xstring.h"
#include "ldml/keyboardprocessor_ldml.h"
#include "ldml/keyman_core_ldml.h"
#include "kmx/kmx_file_validator.hpp"
#include "debuglog.h"
#include <assert.h>
@ -27,10 +28,10 @@ namespace {
};
}
// using km::kbp::kmx::ShouldDebug; // for DebugLog
// using km::core::kmx::ShouldDebug; // for DebugLog
namespace km {
namespace kbp {
namespace core {
ldml_processor::ldml_processor(path const & kb_path, const std::vector<uint8_t> &data)
@ -88,7 +89,7 @@ ldml_processor::ldml_processor(path const & kb_path, const std::vector<uint8_t>
// load transforms
if (kplus.tran != nullptr && kplus.tran->groupCount > 0) {
transforms.reset(km::kbp::ldml::transforms::load(kplus, kplus.tran, kplus.tranHelper));
transforms.reset(km::core::ldml::transforms::load(kplus, kplus.tran, kplus.tranHelper));
if (!transforms) {
DebugLog("Failed to load tran transforms");
return; // failed to load
@ -97,7 +98,7 @@ ldml_processor::ldml_processor(path const & kb_path, const std::vector<uint8_t>
// load bksp transforms
if (kplus.bksp != nullptr && kplus.bksp->groupCount > 0) {
bksp_transforms.reset(km::kbp::ldml::transforms::load(kplus, kplus.bksp, kplus.bkspHelper));
bksp_transforms.reset(km::core::ldml::transforms::load(kplus, kplus.bksp, kplus.bkspHelper));
if (!bksp_transforms) {
DebugLog("Failed to load bksp transforms");
return; // failed to load
@ -236,88 +237,16 @@ ldml_processor::process_event(
// all other VKs
{
// Look up the key
const std::u16string str = keys.lookup(vk, modifier_state);
const std::u16string key_str = keys.lookup(vk, modifier_state);
if (str.empty()) {
if (key_str.empty()) {
// no key was found, so pass the keystroke on to the Engine
state->actions().push_invalidate_context();
state->actions().push_emit_keystroke();
break; // ----- commit and exit
}
// found a string - push it into the context and actions
// we convert it here instead of using the emit_text() overload
// so that we don't have to reconvert it inside the transform code.
const std::u32string str32 = kmx::u16string_to_u32string(str);
if (!transforms) {
// No transforms: just emit the string.
emit_text(state, str32);
} else {
// Process transforms here
/**
* a copy of the current/changed context, for transform use.
*
*/
std::u32string ctxtstr;
(void)context_to_string(state, ctxtstr);
// add the newly added key output to ctxtstr
ctxtstr.append(str32);
/** the output buffer for transforms */
std::u32string outputString;
// apply the transform, get how much matched (at the end)
const size_t matchedContext = transforms->apply(ctxtstr, outputString);
if (matchedContext == 0) {
// No match, just emit the original string
emit_text(state, str32);
} else {
// We have a match.
ctxtstr.resize(ctxtstr.length() - str32.length());
/** how many chars of the context we need to clear */
auto charsToDelete = matchedContext - str32.length(); /* we don't need to clear the output of the current key */
/** how many context items need to be removed */
size_t contextRemoved = 0;
for (auto c = state->context().rbegin(); charsToDelete > 0 && c != state->context().rend(); c++, contextRemoved++) {
/** last char of context */
km_core_usv lastCtx = ctxtstr.back();
uint8_t type = c->type;
assert(type == KM_CORE_BT_CHAR || type == KM_CORE_BT_MARKER);
if (type == KM_CORE_BT_CHAR) {
// single char, drop it
charsToDelete--;
assert(c->character == lastCtx);
ctxtstr.pop_back();
state->actions().push_backspace(KM_CORE_BT_CHAR, lastCtx); // Cause prior char to be removed
} else if (type == KM_CORE_BT_MARKER) {
// it's a marker, 'worth' 3 uchars
assert(charsToDelete >= 3);
assert(lastCtx == c->marker); // end of list
charsToDelete -= 3;
// pop off the three-part sentinel string
ctxtstr.pop_back();
ctxtstr.pop_back();
ctxtstr.pop_back();
// push a special backspace to delete the marker
state->actions().push_backspace(KM_CORE_BT_MARKER, c->marker);
}
}
// now, pop the right number of context items
for (size_t i = 0; i < contextRemoved; i++) {
// we don't pop during the above loop because the iterator gets confused
state->context().pop_back();
}
// Now, add in the updated text. This will convert UC_SENTINEL, etc back to marker actions.
emit_text(state, outputString);
// If we needed it further. we could update ctxtstr here:
// ctxtstr.append(outputString);
// ... but it is no longer needed at this point.
} // end of transform match
} // end of processing transforms
process_key_string(state, key_str);
} // end of processing a 'normal' vk
} // end of switch
// end of normal processing: commit and exit
@ -330,6 +259,103 @@ ldml_processor::process_event(
return KM_CORE_STATUS_OK;
}
void
ldml_processor::process_key_string(km_core_state *state, const std::u16string &key_str) const {
// We know that key_str is not empty per the caller.
assert(!key_str.empty());
// we convert the keys str to UTF-32 here instead of using the emit_text() overload
// so that we don't have to reconvert it inside the transform code.
std::u32string key_str32 = kmx::u16string_to_u32string(key_str);
assert(ldml::normalize_nfd(key_str32)); // TODO-LDML: else fail?
// extract context string, in NFC
std::u32string old_ctxtstr_nfc;
(void)context_to_string(state, old_ctxtstr_nfc, false);
assert(ldml::normalize_nfc(old_ctxtstr_nfc)); // TODO-LDML: else fail?
// context string in NFD
std::u32string ctxtstr;
(void)context_to_string(state, ctxtstr, true); // with markers
// add the newly added key output to ctxtstr
ctxtstr.append(key_str32);
assert(ldml::normalize_nfd(ctxtstr)); // TODO-LDML: else fail?
/** transform output string */
std::u32string outputString;
/** how many chars of the ctxtstr to replace */
size_t matchedContext = 0; // zero if no transforms
// begin modifications to the string
if(transforms) {
matchedContext = transforms->apply(ctxtstr, outputString);
} else {
// no transforms, no output
}
// drop last 'matchedContext':
ctxtstr.resize(ctxtstr.length() - matchedContext);
ctxtstr.append(outputString); // TODO-LDML: should be able to do a normalization-safe append here.
assert(ldml::normalize_nfd(ctxtstr)); // TODO-LDML: else fail?
// Ok. We've done all the happy manipulations.
/** NFC and no markers */
std::u32string ctxtstr_cleanedup = ctxtstr;
// TODO-LDML: remove markers!
assert(ldml::normalize_nfc(ctxtstr_cleanedup)); // TODO-LDML: else fail?
// find common prefix
auto ctxt_prefix = mismatch(old_ctxtstr_nfc.begin(), old_ctxtstr_nfc.end(), ctxtstr_cleanedup.begin(), ctxtstr_cleanedup.end());
/** the part of the old str that changed */
std::u32string old_ctxtstr_changed(ctxt_prefix.first,old_ctxtstr_nfc.end());
std::u32string new_ctxtstr_changed(ctxt_prefix.second,ctxtstr_cleanedup.end());
// drop the old suffix. Note: this mutates old_ctxtstr_changed.
remove_text(state, old_ctxtstr_changed, old_ctxtstr_changed.length());
assert(old_ctxtstr_changed.length() == 0);
emit_text(state, new_ctxtstr_changed);
}
void
ldml_processor::remove_text(km_core_state *state, std::u32string &str, size_t length) {
/** how many context items need to be removed */
size_t contextRemoved = 0;
for (auto c = state->context().rbegin(); length > 0 && c != state->context().rend(); c++, contextRemoved++) {
/** last char of context */
km_core_usv lastCtx = str.back();
uint8_t type = c->type;
assert(type == KM_CORE_BT_CHAR || type == KM_CORE_BT_MARKER);
if (type == KM_CORE_BT_CHAR) {
// single char, drop it
length--;
assert(c->character == lastCtx);
str.pop_back();
state->actions().push_backspace(KM_CORE_BT_CHAR, c->character); // Cause prior char to be removed
} else if (type == KM_CORE_BT_MARKER) {
// it's a marker, 'worth' 3 uchars
assert(length >= 3);
assert(lastCtx == c->marker); // end of list
length -= 3;
// pop off the three-part sentinel string (in reverse order of course)
assert(str.back() == c->marker); // marker #
str.pop_back();
assert(str.back() == LDML_MARKER_CODE);
str.pop_back();
assert(str.back() == LDML_UC_SENTINEL);
str.pop_back();
// push a special backspace to delete the marker
state->actions().push_backspace(KM_CORE_BT_MARKER, c->marker);
}
}
// now, pop the right number of context items
for (size_t i = 0; i < contextRemoved; i++) {
// we don't pop during the above loop because the iterator gets confused
state->context().pop_back();
}
}
km_core_attr const & ldml_processor::attributes() const {
return engine_attrs;
}
@ -389,24 +415,26 @@ ldml_processor::emit_text(km_core_state *state, km_core_usv ch) {
void
ldml_processor::emit_marker(km_core_state *state, KMX_DWORD marker_no) {
assert(km::kbp::kmx::is_valid_marker(marker_no));
assert(km::core::kmx::is_valid_marker(marker_no));
state->actions().push_marker(marker_no);
state->context().push_marker(marker_no);
}
size_t
ldml_processor::context_to_string(km_core_state *state, std::u32string &str) {
ldml_processor::context_to_string(km_core_state *state, std::u32string &str, bool include_markers) {
str.clear();
auto &cp = state->context();
size_t ctxlen = 0; // TODO-LDML: is this needed?
size_t ctxlen = 0; // TODO-LDML: not used by callers?
uint8_t last_type = KM_CORE_BT_UNKNOWN;
for (auto c = cp.rbegin(); c != cp.rend(); c++, ctxlen++) {
last_type = c->type;
if (last_type == KM_CORE_BT_CHAR) {
str.insert(0, 1, c->character);
} else if (last_type == KM_CORE_BT_MARKER) {
assert(km::kbp::kmx::is_valid_marker(c->marker));
prepend_marker(str, c->marker);
assert(km::core::kmx::is_valid_marker(c->marker));
if (include_markers) {
prepend_marker(str, c->marker);
}
} else {
break;
}
@ -414,6 +442,5 @@ ldml_processor::context_to_string(km_core_state *state, std::u32string &str) {
return ctxlen; // consumed the entire context buffer.
}
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -20,7 +20,7 @@
#include "ldml_transforms.hpp"
namespace km {
namespace kbp {
namespace core {
#define KM_CORE_LMDL_PROCESSOR_VERSION u"1.0"
@ -93,6 +93,15 @@ namespace kbp {
static void emit_text(km_core_state *state, km_core_usv ch);
/** emit a marker */
static void emit_marker(km_core_state *state, KMX_DWORD marker);
/**
* Delete text from the state.
* @param str string with text to remove, from the end
* @param length number of chars from the end of str to drop
*/
static void remove_text(km_core_state *state, std::u32string &str, size_t length);
/** process a typed key */
void process_key_string(km_core_state *state, const std::u16string &key_str) const;
/**
* add the string+marker portion of the context to the beginning of str.
@ -100,7 +109,7 @@ namespace kbp {
* Convert markers into the UC_SENTINEL format.
* @return the number of context items consumed
*/
static size_t context_to_string(km_core_state *state, std::u32string &str);
static size_t context_to_string(km_core_state *state, std::u32string &str, bool include_markers = true);
/** prepend the marker string in UC_SENTINEL format to the str */
inline static void prepend_marker(std::u32string &str, KMX_DWORD marker);
@ -112,5 +121,5 @@ namespace kbp {
str.insert(0, triple, 3);
}
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -12,11 +12,11 @@
#include "kmx/kmx_xstring.h"
#ifndef assert
#define assert(x) // TODO-LDML
#define assert(x) ((void)0)
#endif
namespace km {
namespace kbp {
namespace core {
namespace ldml {
/**
@ -403,25 +403,29 @@ transform_entry::transform_entry(const transform_entry &other)
transform_entry::transform_entry(const std::u32string &from, const std::u32string &to)
: fFrom(from), fTo(to), fFromPattern(nullptr), fMapFromStrId(), fMapToStrId(), fMapFromList(), fMapToList() {
assert(!fFrom.empty()); // TODO-LDML: should not happen?
assert(!fFrom.empty());
init();
}
// TODO-LDML: How do we return errors from here?
transform_entry::transform_entry(
const std::u32string &from,
const std::u32string &to,
KMX_DWORD mapFrom,
KMX_DWORD mapTo,
const kmx::kmx_plus &kplus)
const kmx::kmx_plus &kplus,
bool &valid)
: fFrom(from), fTo(to), fFromPattern(nullptr), fMapFromStrId(mapFrom), fMapToStrId(mapTo) {
if (!valid)
return; // exit early
assert(!fFrom.empty()); // TODO-LDML: should not happen?
assert((fMapFromStrId == 0) == (fMapToStrId == 0)); // we have both or we have neither.
assert(kplus.strs != nullptr);
assert(kplus.vars != nullptr);
assert(kplus.elem != nullptr);
init();
if(!init()) {
valid = false;
}
// setup mapFrom
if (fMapFromStrId != 0) {
@ -456,18 +460,23 @@ transform_entry::transform_entry(
}
}
void
bool
transform_entry::init() {
if (!fFrom.empty()) {
// TODO-LDML: if we have mapFrom, may need to do other processing.
const std::u16string patstr = km::kbp::kmx::u32string_to_u16string(fFrom);
UErrorCode status = U_ZERO_ERROR;
/* const */ icu::UnicodeString patustr = icu::UnicodeString(patstr.data(), (int32_t)patstr.length());
// add '$' to match to end
patustr.append(u'$');
fFromPattern.reset(icu::RegexPattern::compile(patustr, 0, status));
assert(U_SUCCESS(status)); // TODO-LDML: may be best to propagate status up ^^
if (fFrom.empty()) {
return false;
}
// TODO-LDML: if we have mapFrom, may need to do other processing.
const std::u16string patstr = km::core::kmx::u32string_to_u16string(fFrom);
UErrorCode status = U_ZERO_ERROR;
/* const */ icu::UnicodeString patustr_raw = icu::UnicodeString(patstr.data(), (int32_t)patstr.length());
// add '$' to match to end
patustr_raw.append(u'$');
icu::UnicodeString patustr;
const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(status);
// NFD normalize on pattern creation
nfd->normalize(patustr_raw, patustr, status);
fFromPattern.reset(icu::RegexPattern::compile(patustr, 0, status));
return (UASSERT_SUCCESS(status));
}
size_t
@ -476,11 +485,11 @@ transform_entry::apply(const std::u32string &input, std::u32string &output) cons
// TODO-LDML: Really? can't go from u32 to UnicodeString?
// TODO-LDML: Also, we could cache the u16 string at the transformGroup level or higher.
UErrorCode status = U_ZERO_ERROR;
const std::u16string matchstr = km::kbp::kmx::u32string_to_u16string(input);
const std::u16string matchstr = km::core::kmx::u32string_to_u16string(input);
icu::UnicodeString matchustr = icu::UnicodeString(matchstr.data(), (int32_t)matchstr.length());
// TODO-LDML: create a new Matcher every time. These could be cached and reset.
std::unique_ptr<icu::RegexMatcher> matcher(fFromPattern->matcher(matchustr, status));
assert(U_SUCCESS(status));
UASSERT_SUCCESS(status);
if (!matcher->find(status)) { // i.e. matches somewhere, in this case at end of str
return 0; // no match
@ -490,7 +499,7 @@ transform_entry::apply(const std::u32string &input, std::u32string &output) cons
// TODO-LDML: if we had an underlying UText this would be simpler.
int32_t matchStart = matcher->start(status);
int32_t matchEnd = matcher->end(status);
assert(U_SUCCESS(status));
UASSERT_SUCCESS(status);
// extract..
const icu::UnicodeString substr = matchustr.tempSubStringBetween(matchStart, matchEnd);
// preflight to UTF-32 to get length
@ -509,7 +518,7 @@ transform_entry::apply(const std::u32string &input, std::u32string &output) cons
// Normal case: not a map.
// This replace will apply $1, $2 etc.
// Convert the fTo into u16 TODO-LDML (we could cache this?)
const std::u16string rstr = km::kbp::kmx::u32string_to_u16string(fTo);
const std::u16string rstr = km::core::kmx::u32string_to_u16string(fTo);
rustr = icu::UnicodeString(rstr.data(), (int32_t)rstr.length());
} else {
// Set map case: mapping from/to
@ -517,7 +526,7 @@ transform_entry::apply(const std::u32string &input, std::u32string &output) cons
// we actually need the group(1) string here.
// this is only the content in parenthesis ()
icu::UnicodeString group1 = matcher->group(1, status);
assert(U_SUCCESS(status)); // TODO-LDML: could be a malformed from pattern
UASSERT_SUCCESS(status); // TODO-LDML: could be a malformed from pattern
// now, how long is group1 in UTF-32, hmm?
UErrorCode preflightStatus = U_ZERO_ERROR; // throwaway status
auto group1Len = group1.toUTF32(nullptr, 0, preflightStatus);
@ -525,7 +534,7 @@ transform_entry::apply(const std::u32string &input, std::u32string &output) cons
assert(s != nullptr); // TODO-LDML: OOM
// convert
substr.toUTF32((UChar32 *)s, group1Len + 1, status);
assert(U_SUCCESS(status));
UASSERT_SUCCESS(status);
std::u32string match32(s, group1Len); // taken from just group1
// clean up buffer
delete [] s;
@ -539,18 +548,27 @@ transform_entry::apply(const std::u32string &input, std::u32string &output) cons
// 2. get the target string, convert to utf-16
// we use the same matchIndex that was just found
const std::u16string rstr = km::kbp::kmx::u32string_to_u16string(fMapToList.at(matchIndex));
const std::u16string rstr = km::core::kmx::u32string_to_u16string(fMapToList.at(matchIndex));
// 3. update the UnicodeString for replacement
rustr = icu::UnicodeString(rstr.data(), (int32_t)rstr.length());
// and we return to the regular code flow.
}
const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(status);
icu::UnicodeString rustr2;
nfd->normalize(rustr, rustr2, status);
UASSERT_SUCCESS(status);
// here we replace the match output.
icu::UnicodeString entireOutput = matcher->replaceFirst(rustr, status);
assert(U_SUCCESS(status)); // TODO-LDML: could fail here due to bad input (syntax err)
icu::UnicodeString entireOutput = matcher->replaceFirst(rustr2, status);
UASSERT_SUCCESS(status); // TODO-LDML: could fail here due to bad input (syntax err)
// entireOutput includes all of 'input', but modified. Need to substring it.
icu::UnicodeString outu = entireOutput.tempSubString(matchStart);
icu::UnicodeString outu_raw = entireOutput.tempSubString(matchStart);
// normalize the replaced string
icu::UnicodeString outu;
nfd->normalize(outu_raw, outu, status);
UASSERT_SUCCESS(status);
// Special case if there's no output, save some allocs
if (outu.length() == 0) {
@ -565,7 +583,7 @@ transform_entry::apply(const std::u32string &input, std::u32string &output) cons
assert(s != nullptr);
// convert
outu.toUTF32((UChar32 *)s, out32len + 1, status);
assert(U_SUCCESS(status));
UASSERT_SUCCESS(status);
output.assign(s, out32len);
// now, build a u32string
std::u32string out32(s, out32len);
@ -752,37 +770,36 @@ transforms::apply(std::u32string &str) {
transforms *
transforms::load(
const kmx::kmx_plus &kplus,
const kbp::kmx::COMP_KMXPLUS_TRAN *tran,
const kbp::kmx::COMP_KMXPLUS_TRAN_Helper &tranHelper) {
const core::kmx::COMP_KMXPLUS_TRAN *tran,
const core::kmx::COMP_KMXPLUS_TRAN_Helper &tranHelper) {
bool valid = true;
if (tran == nullptr) {
DebugLog("for tran: tran is null");
assert(false);
return nullptr;
}
if (!tranHelper.valid()) {
valid = false;
} else if (!tranHelper.valid()) {
DebugLog("for tran: tranHelper is invalid");
assert(false);
return nullptr;
}
if (nullptr == kplus.elem) {
valid = false;
} else if (nullptr == kplus.elem) {
DebugLog("for tran: kplus.elem == nullptr");
assert(false);
return nullptr;
}
if (nullptr == kplus.strs) {
valid = false;
} else if (nullptr == kplus.strs) {
DebugLog("for tran: kplus.strs == nullptr"); // need a string table to get strings
assert(false);
return nullptr;
}
if (nullptr == kplus.vars) {
valid = false;
} else if (nullptr == kplus.vars) {
DebugLog("for tran: kplus.vars == nullptr"); // need a vars table to get maps
assert(false);
valid = false;
}
assert(valid);
if (!valid) {
return nullptr;
}
// with that out of the way, let's set it up
transforms *transforms = new ldml::transforms();
std::unique_ptr<transforms> transforms;
transforms.reset(new ldml::transforms());
for (KMX_DWORD groupNumber = 0; groupNumber < tran->groupCount; groupNumber++) {
const kmx::COMP_KMXPLUS_TRAN_GROUP *group = tranHelper.getGroup(groupNumber);
@ -798,7 +815,15 @@ transforms::load(
const std::u32string toStr = kmx::u16string_to_u32string(kplus.strs->get(element->to));
KMX_DWORD mapFrom = element->mapFrom; // copy, because of alignment
KMX_DWORD mapTo = element->mapTo; // copy, because of alignment
newGroup.emplace_back(fromStr, toStr, mapFrom, mapTo, kplus); // creating a transform_entry
assert(!fromStr.empty());
if (fromStr.empty()) {
valid = false;
}
newGroup.emplace_back(fromStr, toStr, mapFrom, mapTo, kplus, valid); // creating a transform_entry
assert(valid);
if(!valid) {
return nullptr;
}
}
transforms->addGroup(newGroup);
} else if (group->type == LDML_TRAN_GROUP_TYPE_REORDER) {
@ -828,9 +853,63 @@ transforms::load(
return nullptr;
}
}
return transforms;
assert(valid);
if (!valid) {
return nullptr;
} else {
return transforms.release();
}
}
// string manipulation
bool normalize_nfd(std::u32string &str) {
std::u16string rstr = km::core::kmx::u32string_to_u16string(str);
if(!normalize_nfd(rstr)) {
return false;
} else {
str = km::core::kmx::u16string_to_u32string(rstr);
return true;
}
}
/** internal function to normalize with a specified mode */
static bool normalize(const icu::Normalizer2 *n, std::u16string &str, UErrorCode &status) {
UASSERT_SUCCESS(status);
assert(n != nullptr);
icu::UnicodeString dest;
icu::UnicodeString src = icu::UnicodeString(str.data(), (int32_t)str.length());
n->normalize(src, dest, status);
if (UASSERT_SUCCESS(status)) {
str.assign(dest.getBuffer(), dest.length());
}
return U_SUCCESS(status);
}
bool normalize_nfd(std::u16string &str) {
UErrorCode status = U_ZERO_ERROR;
const icu::Normalizer2 *nfd = icu::Normalizer2::getNFDInstance(status);
UASSERT_SUCCESS(status);
return normalize(nfd, str, status);
}
bool normalize_nfc(std::u32string &str) {
std::u16string rstr = km::core::kmx::u32string_to_u16string(str);
if(!normalize_nfc(rstr)) {
return false;
} else {
str = km::core::kmx::u16string_to_u32string(rstr);
return true;
}
}
bool normalize_nfc(std::u16string &str) {
UErrorCode status = U_ZERO_ERROR;
const icu::Normalizer2 *nfc = icu::Normalizer2::getNFCInstance(status);
UASSERT_SUCCESS(status);
return normalize(nfc, str, status);
}
} // namespace ldml
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -13,6 +13,7 @@
#include <string>
#include <unordered_map>
#include <utility>
#include "debuglog.h"
#if !defined(HAVE_ICU4C)
#error icu4c is required for this code
@ -25,12 +26,25 @@
#include "unicode/unistr.h"
#include "unicode/regex.h"
#include "unicode/utext.h"
#include "unicode/normalizer2.h"
namespace km {
namespace kbp {
namespace core {
namespace ldml {
using km::kbp::kmx::SimpleUSet;
/** @returns true on success */
inline bool uassert_success(const char *file, int line, const char *function, UErrorCode status) {
if (U_FAILURE(status)) {
DebugLog2(file, line, function, "U_FAILURE(%s)", u_errorName(status));
return false;
} else {
return true;
}
}
#define UASSERT_SUCCESS(status) assert(U_SUCCESS(status)), uassert_success(__FILE__, __LINE__, __FUNCTION__, status)
using km::core::kmx::SimpleUSet;
/**
* Type of a group
@ -94,7 +108,8 @@ public:
const std::u32string &to,
KMX_DWORD mapFrom,
KMX_DWORD mapTo,
const kmx::kmx_plus &kplus);
const kmx::kmx_plus &kplus,
bool &valid);
/**
* If matching, apply the match to the output string
@ -113,8 +128,8 @@ private:
const KMX_DWORD fMapToStrId;
std::deque<std::u32string> fMapFromList;
std::deque<std::u32string> fMapToList;
/** Internal function to setup pattern string */
void init();
/** Internal function to setup pattern string @returns true on success */
bool init();
/** @returns the index of the item in the fMapFromList list, or -1 */
int32_t findIndexFrom(const std::u32string &match) const;
public:
@ -266,10 +281,21 @@ public:
/** load from a kmx_plus data section, either tran or bksp */
static transforms *
load(const kmx::kmx_plus &kplus,
const kbp::kmx::COMP_KMXPLUS_TRAN *tran,
const kbp::kmx::COMP_KMXPLUS_TRAN_Helper &tranHelper);
const core::kmx::COMP_KMXPLUS_TRAN *tran,
const core::kmx::COMP_KMXPLUS_TRAN_Helper &tranHelper);
};
// string routines
/** Normalize a u32string inplace to NFD. @return false on failure */
bool normalize_nfd(std::u32string &str);
/** Normalize a u16string inplace to NFD. @return false on failure */
bool normalize_nfd(std::u16string &str);
/** Normalize a u32string inplace to NFC. @return false on failure */
bool normalize_nfc(std::u32string &str);
/** Normalize a u16string inplace to NFC. @return false on failure */
bool normalize_nfc(std::u16string &str);
} // namespace ldml
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -9,7 +9,7 @@
#include "kmx_file.h"
namespace km {
namespace kbp {
namespace core {
namespace ldml {
vkeys::vkeys() : vkey_to_string() {
@ -78,5 +78,5 @@ vkeys::lookup(const vkey_id& id) const {
}
} // namespace ldml
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -16,7 +16,7 @@
#include <keyman/keyman_core_api.h>
namespace km {
namespace kbp {
namespace core {
namespace ldml {
/**
@ -55,5 +55,5 @@ private:
};
} // namespace ldml
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -1,5 +1,5 @@
# Copyright: © 2018-2022 SIL International.
# Description: Cross platform build script to compile libkmnkbp.
# Description: Cross platform build script to compile libkeymancore.
# Create Date: 2 Oct 2018
# Authors: Tim Eves (TSE)
#
@ -7,7 +7,7 @@
version_res = []
# TODO: shouldn't this be defined only for dylib and not static lib?
defns += ['-DKMN_KBP_EXPORTING']
defns += ['-DKM_CORE_LIBRARY_EXPORTING']
if cpp_compiler.get_id() == 'msvc'
defns += ['-DUNICODE']
@ -18,7 +18,7 @@ endif
if cpp_compiler.get_id() == 'emscripten'
# TODO: why do we need this defn here?
defns += ['-DKMN_KBP']
defns += ['-DKM_CORE_LIBRARY']
endif
# ICU4C is used for repertoire tests and core implementation
@ -42,10 +42,12 @@ endif
kmx_files = files(
'action.cpp',
'option.cpp',
'keyboard.cpp',
'state.cpp',
'debuglog.cpp',
'km_core_action_api.cpp',
'km_core_context_api.cpp',
'km_core_keyboard_api.cpp',
'km_core_options_api.cpp',
@ -74,6 +76,7 @@ kmx_files = files(
)
api_files = files(
'km_core_action_api.cpp',
'km_core_context_api.cpp',
'km_core_keyboard_api.cpp',
'km_core_options_api.cpp',
@ -83,6 +86,7 @@ api_files = files(
)
core_files = files(
'action.cpp',
'option.cpp',
'keyboard.cpp',
'state.cpp',
@ -94,7 +98,7 @@ mock_files = files(
'mock/mock_processor.cpp',
)
lib = library('kmnkbp0',
lib = library('keymancore',
api_files,
core_files,
kmx_files,
@ -111,11 +115,11 @@ lib = library('kmnkbp0',
headerdirs = [ '.', 'keyman' ] # subdirectories of ${prefix}/include to add to header path
kmnkbp = declare_dependency(link_with: lib, include_directories: inc, dependencies: [icu_uc, icu_i18n])
keymancore = declare_dependency(link_with: lib, include_directories: inc, dependencies: [icu_uc, icu_i18n])
pkg = import('pkgconfig')
pkg.generate(
name: 'keyman_kmn_processor',
name: 'keyman_core',
version: meson.project_version(),
description: 'Keyman processor for KMN keyboards.',
subdirs: headerdirs,

View file

@ -70,9 +70,9 @@ namespace
}
namespace km {
namespace kbp
namespace core
{
mock_processor::mock_processor(kbp::path const & path)
mock_processor::mock_processor(core::path const & path)
: abstract_processor(
keyboard_attributes(path.stem(), u"3.145", path.parent(), {
option{KM_CORE_OPT_KEYBOARD, u"__test_point", u"not tiggered"},
@ -228,5 +228,5 @@ namespace km {
km_core_status mock_processor::validate() const { return KM_CORE_STATUS_OK; }
km_core_status null_processor::validate() const { return KM_CORE_STATUS_INVALID_ARGUMENT; }
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -16,14 +16,14 @@
#include "option.hpp"
namespace km {
namespace kbp
namespace core
{
class mock_processor : public abstract_processor
{
std::unordered_map<std::u16string, std::u16string> _options;
public:
mock_processor(km::kbp::path const &);
mock_processor(km::core::path const &);
// ~mock_processor() override;
km_core_status
@ -76,5 +76,5 @@ namespace kbp
km_core_status validate() const override;
};
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -12,7 +12,7 @@
#include "processor.hpp"
using namespace km::kbp;
using namespace km::core;
namespace
{
@ -44,7 +44,7 @@ option::option(km_core_option_scope s, char16_t const *k, char16_t const *v)
}
// TODO: Relocate this and fix it
json & km::kbp::operator << (json &j, abstract_processor const &)
json & km::core::operator << (json &j, abstract_processor const &)
{
j << json::object;
// auto n = 0;

View file

@ -18,7 +18,7 @@
class json;
namespace km {
namespace kbp
namespace core
{
struct option : public km_core_option_item
{
@ -89,5 +89,5 @@ namespace kbp
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -19,7 +19,7 @@
// Forward declarations
namespace km {
namespace kbp
namespace core
{
class path
{
@ -27,14 +27,14 @@ namespace kbp
using char_type = std::remove_const_t<
std::remove_pointer_t<km_core_path_name>>;
using string_type = std::basic_string<char_type>;
static constexpr char_type const parent_separator = _KM_KBP_PATH_SEPARATOR,
suffix_separator = _KM_KBP_EXT_SEPARATOR;
static constexpr char_type const parent_separator = _KM_CORE_PATH_SEPARATOR,
suffix_separator = _KM_CORE_EXT_SEPARATOR;
private:
string_type _path;
void normalise() {
#if '/' != _KM_KBP_PATH_SEPARATOR
std::replace(_path.begin(), _path.end(), char_type('/'), _KM_KBP_PATH_SEPARATOR);
#if '/' != _KM_CORE_PATH_SEPARATOR
std::replace(_path.begin(), _path.end(), char_type('/'), _KM_CORE_PATH_SEPARATOR);
#endif
}
@ -157,5 +157,5 @@ namespace kbp
return os;
}
} // namespace kbp
} // namespace core
} // namespace km

View file

@ -16,7 +16,7 @@
#include "keyboard.hpp"
namespace km {
namespace kbp
namespace core
{
class abstract_processor
{
@ -75,7 +75,7 @@ namespace kbp
* updateing the state as required.
*
* @param state An opaque pointer to a state object
* @return km_core_status `KM_CORE_STATUS_OK`: On success. Else KB_KBP_ error code
* @return km_core_status `KM_CORE_STATUS_OK`: On success. Else KB_CORE_ error code
*/
virtual km_core_status
process_queued_actions(
@ -127,7 +127,7 @@ namespace kbp
json & operator << (json &j, abstract_processor const &opts);
} // namespace kbp
} // namespace core
} // namespace km
struct km_core_keyboard : public km::kbp::abstract_processor {};
struct km_core_keyboard : public km::core::abstract_processor {};

View file

@ -9,7 +9,7 @@
#include "processor.hpp"
#include <keyman/keyman_core_api_consts.h>
using namespace km::kbp;
using namespace km::core;
void actions::push_persist(option const &opt) {
assert(empty() || back().type != KM_CORE_IT_END);
@ -35,7 +35,7 @@ void actions::push_capslock(bool turnOn) {
}
state::state(km::kbp::abstract_processor & ap, km_core_option_item const *env)
state::state(km::core::abstract_processor & ap, km_core_option_item const *env)
: _processor(ap)
{
for (; env && env->key != nullptr; env++) {

View file

@ -18,7 +18,7 @@
#include "debug.hpp"
namespace km {
namespace kbp
namespace core
{
//Forward declarations
class abstract_processor;
@ -37,7 +37,7 @@ public:
actions(Args&&... args);
void push_character(km_core_usv usv);
void push_marker(uintptr_t marker);
void push_marker(uint32_t marker);
void push_alert();
void push_backspace(km_core_backspace_type expected_type, uintptr_t expected_value = 0);
void push_persist(option const &);
@ -68,7 +68,7 @@ void actions::push_character(km_core_usv usv) {
inline
void actions::push_marker(uintptr_t marker) {
void actions::push_marker(uint32_t marker) {
assert(empty() || (!empty() && back().type != KM_CORE_IT_END));
emplace_back(km_core_action_item {KM_CORE_IT_MARKER, {0,}, {marker}});
}
@ -122,30 +122,30 @@ void actions::clear() {
class state
{
protected:
kbp::context _ctxt;
kbp::abstract_processor & _processor;
kbp::actions _actions;
kbp::debug_items _debug_items;
core::context _ctxt;
core::abstract_processor & _processor;
core::actions _actions;
core::debug_items _debug_items;
km_core_keyboard_imx_platform _imx_callback;
void *_imx_object;
public:
state(kbp::abstract_processor & kb, km_core_option_item const *env);
state(core::abstract_processor & kb, km_core_option_item const *env);
state(state const &) = default;
state(state const &&) = delete;
kbp::context & context() noexcept { return _ctxt; }
kbp::context const & context() const noexcept { return _ctxt; }
core::context & context() noexcept { return _ctxt; }
core::context const & context() const noexcept { return _ctxt; }
kbp::abstract_processor const & processor() const noexcept { return _processor; }
kbp::abstract_processor & processor() noexcept { return _processor; }
core::abstract_processor const & processor() const noexcept { return _processor; }
core::abstract_processor & processor() noexcept { return _processor; }
kbp::actions & actions() noexcept { return _actions; }
kbp::actions const & actions() const noexcept { return _actions; }
core::actions & actions() noexcept { return _actions; }
core::actions const & actions() const noexcept { return _actions; }
kbp::debug_items & debug_items() noexcept { return _debug_items; }
kbp::debug_items const & debug_items() const noexcept { return _debug_items; }
core::debug_items & debug_items() noexcept { return _debug_items; }
core::debug_items const & debug_items() const noexcept { return _debug_items; }
void imx_register_callback(km_core_keyboard_imx_platform imx_callback, void *callback_object);
@ -154,12 +154,12 @@ public:
void imx_callback(uint32_t imx_id);
};
} // namespace kbp
} // namespace core
} // namespace km
struct km_core_state : public km::kbp::state
struct km_core_state : public km::core::state
{
template<typename... Args>
km_core_state(Args&&... args) : km::kbp::state(std::forward<Args>(args)...)
km_core_state(Args&&... args) : km::core::state(std::forward<Args>(args)...)
{}
};

View file

@ -23,7 +23,7 @@
VALUE "InternalName", "Keyman Core"
VALUE "LegalCopyright", "© SIL International"
VALUE "LegalTrademarks", ""
VALUE "OriginalFilename", "KMNKBP0-0.DLL"
VALUE "OriginalFilename", "KEYMANCORE1.DLL"
VALUE "ProductName", "Keyman Core"
VALUE "ProductVersion", KM_CORE_VERSION_STRING
VALUE "Comments", ""

View file

@ -35,7 +35,7 @@ uconfig.set('U_ENABLE_DYLOAD', 0) # no DLL
uconfig.set('U_CHECK_DYLOAD', 0) # no DLL
uconfig.set('UCONFIG_NO_FILE_IO', 1)
uconfig.set('UCONFIG_NO_LEGACY_CONVERSION', 1) # turn off file based codepage conversion
uconfig.set('UCONFIG_NO_NORMALIZATION', 1) # TODO-LDML: may want this
uconfig.set('UCONFIG_NO_NORMALIZATION', 0)
uconfig.set('UCONFIG_NO_BREAK_ITERATION', 1) # TODO-LDML: may want this
uconfig.set('UCONFIG_NO_IDNA', 1)
uconfig.set('UCONFIG_NO_COLLATION', 1)

View file

@ -107,7 +107,7 @@ KmxTestSource::is_token(const std::string token, std::string &line) {
int
KmxTestSource::load_source(
const km::kbp::path &path,
const km::core::path &path,
std::string &keys,
std::u16string &expected,
std::u16string &context,
@ -210,15 +210,15 @@ key_event
KmxTestSource::char_to_event(char ch) {
assert(ch >= 32);
return {
km::kbp::kmx::s_char_to_vkey[(int)ch - 32].vk,
(uint16_t)(km::kbp::kmx::s_char_to_vkey[(int)ch - 32].shifted ? KM_CORE_MODIFIER_SHIFT : 0)};
km::core::kmx::s_char_to_vkey[(int)ch - 32].vk,
(uint16_t)(km::core::kmx::s_char_to_vkey[(int)ch - 32].shifted ? KM_CORE_MODIFIER_SHIFT : 0)};
}
uint16_t
KmxTestSource::get_modifier(std::string const m) {
for (int i = 0; km::kbp::kmx::s_modifier_names[i].name; i++) {
if (m == km::kbp::kmx::s_modifier_names[i].name) {
return km::kbp::kmx::s_modifier_names[i].modifier;
for (int i = 0; km::core::kmx::s_modifier_names[i].name; i++) {
if (m == km::core::kmx::s_modifier_names[i].name) {
return km::core::kmx::s_modifier_names[i].modifier;
}
}
return 0;
@ -227,7 +227,7 @@ KmxTestSource::get_modifier(std::string const m) {
km_core_virtual_key
KmxTestSource::get_vk(std::string const &vk) {
for (int i = 1; i < 256; i++) {
if (vk == km::kbp::kmx::s_key_names[i]) {
if (vk == km::core::kmx::s_key_names[i]) {
return i;
}
}

View file

@ -25,7 +25,7 @@ public:
KmxTestSource();
int load_source(
const km::kbp::path &path,
const km::core::path &path,
std::string &keys,
std::u16string &expected,
std::u16string &context,

View file

@ -13,7 +13,7 @@ coretest_files = files(
'kmx_test_source.cpp',
)
kmx_test_source_lib = static_library('kmnkbp-tests',
kmx_test_source_lib = static_library('keymancore-tests',
coretest_files,
cpp_args: defns + warns + flags,
include_directories: [inc, libsrc],

View file

@ -0,0 +1,445 @@
/*
Copyright: © 2018 SIL International.
Description: Tests for the context API family of functions.
Create Date: 23 Oct 2023
Authors: Marc Durdin
History: 23 Oct 2023 - MCD - Initial implementation.
*/
#include <string>
#include <keyman/keyman_core_api.h>
#include "path.hpp"
#include "action.hpp"
#include <test_assert.h>
#include "../emscripten_filesystem.h"
const km_core_action_item alert_action_item();
const km_core_action_item bksp_action_item(uint8_t type, uintptr_t value);
const km_core_action_item caps_action_item(uint8_t capsLock);
const km_core_action_item char_action_item(km_core_usv chr);
const km_core_action_item emit_keystroke_action_item();
const km_core_action_item persist_opt_action_item(km_core_option_item const *option);
const km_core_action_item end_action_item();
const km_core_action_item invalidate_context_action_item();
const km_core_action_item marker_action_item(uint32_t marker);
//-------------------------------------------------------------------------------------
void test_two_backspaces() {
const km_core_action_item action_items[] = {
char_action_item('D'),
bksp_action_item(KM_CORE_BT_CHAR, 'D'),
bksp_action_item(KM_CORE_BT_CHAR, 'E'),
end_action_item()
};
km_core_actions *actions = km::core::action_item_list_to_actions_object(action_items);
assert(actions->code_points_to_delete == 1);
assert(std::u32string(actions->output) == U"");
assert(actions->persist_options != nullptr);
assert(actions->persist_options[0].key == nullptr);
assert(actions->persist_options[0].value == nullptr);
assert(actions->persist_options[0].scope == KM_CORE_OPT_UNKNOWN);
assert(actions->do_alert == false);
assert(actions->emit_keystroke == false);
assert(actions->new_caps_lock_state == -1);
try_status(km_core_actions_dispose(actions));
}
//-------------------------------------------------------------------------------------
void test_marker_text_interleaved() {
const km_core_action_item action_items[] = {
char_action_item('A'),
marker_action_item(1),
char_action_item('B'),
marker_action_item(2),
char_action_item('C'),
bksp_action_item(KM_CORE_BT_CHAR, 'C'),
bksp_action_item(KM_CORE_BT_MARKER, 2),
char_action_item('D'),
end_action_item()
};
km_core_actions *actions = km::core::action_item_list_to_actions_object(action_items);
assert(actions->code_points_to_delete == 0);
assert(std::u32string(actions->output) == U"ABD");
assert(actions->persist_options != nullptr);
assert(actions->persist_options[0].key == nullptr);
assert(actions->persist_options[0].value == nullptr);
assert(actions->persist_options[0].scope == KM_CORE_OPT_UNKNOWN);
assert(actions->do_alert == false);
assert(actions->emit_keystroke == false);
assert(actions->new_caps_lock_state == -1);
try_status(km_core_actions_dispose(actions));
}
//-------------------------------------------------------------------------------------
void test_alert() {
const km_core_action_item action_items[] = {
alert_action_item(),
end_action_item()
};
km_core_actions *actions = km::core::action_item_list_to_actions_object(action_items);
assert(actions->code_points_to_delete == 0);
assert(std::u32string(actions->output) == U"");
assert(actions->persist_options != nullptr);
assert(actions->persist_options[0].key == nullptr);
assert(actions->persist_options[0].value == nullptr);
assert(actions->persist_options[0].scope == KM_CORE_OPT_UNKNOWN);
assert(actions->do_alert == KM_CORE_TRUE);
assert(actions->emit_keystroke == KM_CORE_FALSE);
assert(actions->new_caps_lock_state == KM_CORE_CAPS_UNCHANGED);
try_status(km_core_actions_dispose(actions));
}
//-------------------------------------------------------------------------------------
void test_emit_keystroke() {
const km_core_action_item action_items[] = {
emit_keystroke_action_item(),
end_action_item()
};
km_core_actions *actions = km::core::action_item_list_to_actions_object(action_items);
assert(actions->code_points_to_delete == 0);
assert(std::u32string(actions->output) == U"");
assert(actions->persist_options != nullptr);
assert(actions->persist_options[0].key == nullptr);
assert(actions->persist_options[0].value == nullptr);
assert(actions->persist_options[0].scope == KM_CORE_OPT_UNKNOWN);
assert(actions->do_alert == KM_CORE_FALSE);
assert(actions->emit_keystroke == KM_CORE_TRUE);
assert(actions->new_caps_lock_state == KM_CORE_CAPS_UNCHANGED);
try_status(km_core_actions_dispose(actions));
}
//-------------------------------------------------------------------------------------
void test_invalidate_context() {
// note, this generates a no-op
const km_core_action_item action_items[] = {
invalidate_context_action_item(),
end_action_item()
};
km_core_actions *actions = km::core::action_item_list_to_actions_object(action_items);
assert(actions->code_points_to_delete == 0);
assert(std::u32string(actions->output) == U"");
assert(actions->persist_options != nullptr);
assert(actions->persist_options[0].key == nullptr);
assert(actions->persist_options[0].value == nullptr);
assert(actions->persist_options[0].scope == KM_CORE_OPT_UNKNOWN);
assert(actions->do_alert == KM_CORE_FALSE);
assert(actions->emit_keystroke == KM_CORE_FALSE);
assert(actions->new_caps_lock_state == KM_CORE_CAPS_UNCHANGED);
try_status(km_core_actions_dispose(actions));
}
//-------------------------------------------------------------------------------------
void test_persist_opt() {
const km_core_option_item option = {
u"key",
u"value",
KM_CORE_OPT_KEYBOARD
};
const km_core_action_item action_items[] = {
persist_opt_action_item(&option),
end_action_item()
};
km_core_actions *actions = km::core::action_item_list_to_actions_object(action_items);
assert(actions->code_points_to_delete == 0);
assert(std::u32string(actions->output) == U"");
assert(actions->persist_options != nullptr);
assert(std::u16string(actions->persist_options[0].key) == u"key");
assert(std::u16string(actions->persist_options[0].value) == u"value");
assert(actions->persist_options[0].scope == KM_CORE_OPT_KEYBOARD);
// verify that data is copied
assert(actions->persist_options[0].key != option.key);
assert(actions->persist_options[0].value != option.value);
// verify that we have a KM_CORE_OPTIONS_END term
assert(actions->persist_options[1].key == nullptr);
assert(actions->persist_options[1].value == nullptr);
assert(actions->persist_options[1].scope == KM_CORE_OPT_UNKNOWN);
assert(actions->do_alert == KM_CORE_FALSE);
assert(actions->emit_keystroke == KM_CORE_FALSE);
assert(actions->new_caps_lock_state == KM_CORE_CAPS_UNCHANGED);
try_status(km_core_actions_dispose(actions));
}
//-------------------------------------------------------------------------------------
// Context tests
//-------------------------------------------------------------------------------------
km_core_option_item test_env_opts[] =
{
KM_CORE_OPTIONS_END
};
km_core_keyboard * test_kb = nullptr;
km_core_state * test_state = nullptr;
km_core_context_item * citems = nullptr;
std::string arg_path;
void teardown() {
if(citems) {
km_core_context_items_dispose(citems);
citems = nullptr;
}
if(test_state) {
km_core_state_dispose(test_state);
test_state = nullptr;
}
if(test_kb) {
km_core_keyboard_dispose(test_kb);
test_kb = nullptr;
}
}
void setup(const char *keyboard, const km_core_cp* context) {
teardown();
km::core::path path = km::core::path::join(arg_path, keyboard);
try_status(km_core_keyboard_load(path.native().c_str(), &test_kb));
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
try_status(km_core_context_items_from_utf16(context, &citems));
try_status(km_core_context_set(km_core_state_context(test_state), citems));
}
bool is_identical_context(km_core_cp const *cached_context) {
size_t buf_size;
try_status(km_core_context_get(km_core_state_context(test_state), &citems));
try_status(km_core_context_items_to_utf16(citems, nullptr, &buf_size));
km_core_cp* new_cached_context = new km_core_cp[buf_size];
try_status(km_core_context_items_to_utf16(citems, new_cached_context, &buf_size));
bool result = std::u16string(cached_context) == new_cached_context;
delete[] new_cached_context;
return result;
}
void test_context_set_if_needed_identical_context() {
km_core_cp const *application_context = u"This is a test";
km_core_cp const *cached_context = u"This is a test";
setup("k_000___null_keyboard.kmx", cached_context);
assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UNCHANGED);
assert(is_identical_context(cached_context));
teardown();
}
void test_context_set_if_needed_different_context() {
km_core_cp const *application_context = u"This is a test";
km_core_cp const *cached_context = u"This isn't a test";
setup("k_000___null_keyboard.kmx", cached_context);
assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UPDATED);
assert(!is_identical_context(cached_context));
assert(is_identical_context(application_context));
teardown();
}
void test_context_set_if_needed_app_context_is_longer() {
km_core_cp const *application_context = u"Longer This is a test";
km_core_cp const *cached_context = u"This is a test";
setup("k_000___null_keyboard.kmx", cached_context);
assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UNCHANGED);
// Should be true -- longer, but what exists is identical to cached
assert(is_identical_context(cached_context));
teardown();
}
void test_context_set_if_needed_app_context_is_shorter() {
km_core_cp const *application_context = u"is a test";
km_core_cp const *cached_context = u"This is a test";
setup("k_000___null_keyboard.kmx", cached_context);
assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UPDATED);
// Should be false -- app ctxt is shorter, so doesn't matter that what we have
// matches
assert(!is_identical_context(cached_context));
assert(is_identical_context(application_context));
teardown();
}
void test_context_set_if_needed_cached_context_has_markers() {
km_core_cp const *application_context = u"123";
km_core_cp const *cached_context = u"123";
setup("k_000___null_keyboard.kmx", cached_context);
km_core_context_item const citems[] = {
{ KM_CORE_CT_MARKER, {0}, { 5 } },
{ KM_CORE_CT_CHAR, {0}, { '1' } },
{ KM_CORE_CT_MARKER, {0}, { 1 } },
{ KM_CORE_CT_CHAR, {0}, { '2' } },
{ KM_CORE_CT_MARKER, {0}, { 2 } },
{ KM_CORE_CT_CHAR, {0}, { '3' } },
{ KM_CORE_CT_MARKER, {0}, { 3 } },
{ KM_CORE_CT_MARKER, {0}, { 4 } },
KM_CORE_CONTEXT_ITEM_END
};
try_status(km_core_context_set(km_core_state_context(test_state), citems));
assert(km_core_state_context_set_if_needed(test_state, application_context) == KM_CORE_CONTEXT_STATUS_UNCHANGED);
km_core_context_item* citems_new;
try_status(km_core_context_get(km_core_state_context(test_state), &citems_new));
for(int i = 0; citems[i].type || citems_new[i].type; i++) {
assert(citems_new[i].type == citems[i].type);
if(citems[i].type == KM_CORE_CT_CHAR) {
assert(citems_new[i].character == citems[i].character);
} else {
assert(citems_new[i].marker == citems[i].marker);
}
}
teardown();
}
void test_context_set_if_needed() {
test_context_set_if_needed_identical_context();
test_context_set_if_needed_different_context();
test_context_set_if_needed_app_context_is_longer();
test_context_set_if_needed_app_context_is_shorter();
test_context_set_if_needed_cached_context_has_markers();
}
void test_context_clear() {
km_core_cp const *cached_context = u"This is a test";
setup("k_000___null_keyboard.kmx", cached_context);
try_status(km_core_state_context_clear(test_state));
assert(!is_identical_context(cached_context));
assert(is_identical_context(u""));
teardown();
}
//-------------------------------------------------------------------------------------
// Launcher
//-------------------------------------------------------------------------------------
constexpr const auto help_str = "\
action_api [--color] <SOURCE_PATH>\n\
\n\
--color Force color output\n\
SOURCE_PATH Path where debug_api.cpp is found; kmx files are\n\
located relative to this path.\n";
int error_args() {
std::cerr << "debug_api: Invalid arguments." << std::endl;
std::cout << help_str;
return 1;
}
int main(int argc, char *argv []) {
if(argc < 2) {
return error_args();
}
auto arg_color = std::string(argv[1]) == "--color";
if(arg_color && argc < 3) {
return error_args();
}
console_color::enabled = console_color::isaterminal() || arg_color;
#ifdef __EMSCRIPTEN__
arg_path = get_wasm_file_path(argv[arg_color ? 2 : 1]);
#else
arg_path = argv[arg_color ? 2 : 1];
#endif
// actions
test_two_backspaces();
test_marker_text_interleaved();
test_alert();
test_emit_keystroke();
test_invalidate_context();
// context -- todo move to another file
test_context_set_if_needed();
test_context_clear();
}
//-------------------------------------------------------------------------------------
// Helper functions
//-------------------------------------------------------------------------------------
const km_core_action_item alert_action_item() {
km_core_action_item res = {0};
res.type = KM_CORE_IT_ALERT;
return res;
}
const km_core_action_item bksp_action_item(uint8_t type, uintptr_t value) {
km_core_action_item res = {0};
res.type = KM_CORE_IT_BACK;
res.backspace.expected_type = type;
res.backspace.expected_value = value;
return res;
}
const km_core_action_item caps_action_item(uint8_t capsLock) {
km_core_action_item res = {0};
res.type = KM_CORE_IT_CAPSLOCK;
res.capsLock = capsLock;
return res;
}
const km_core_action_item char_action_item(km_core_usv chr) {
km_core_action_item res = {0};
res.type = KM_CORE_IT_CHAR;
res.character = chr;
return res;
}
const km_core_action_item emit_keystroke_action_item() {
km_core_action_item res = {0};
res.type = KM_CORE_IT_EMIT_KEYSTROKE;
return res;
}
const km_core_action_item persist_opt_action_item(km_core_option_item const *option) {
km_core_action_item res = {0};
res.type = KM_CORE_IT_PERSIST_OPT;
res.option = option;
return res;
}
const km_core_action_item end_action_item() {
km_core_action_item res = {0};
res.type = KM_CORE_IT_END;
return res;
}
const km_core_action_item invalidate_context_action_item() {
km_core_action_item res = {0};
res.type = KM_CORE_IT_INVALIDATE_CONTEXT;
return res;
}
const km_core_action_item marker_action_item(uint32_t marker) {
km_core_action_item res = {0};
res.type = KM_CORE_IT_MARKER;
res.character = marker;
return res;
}

View file

@ -21,7 +21,7 @@
#include <test_assert.h>
#include "../emscripten_filesystem.h"
using namespace km::kbp::kmx;
using namespace km::core::kmx;
km_core_option_item test_env_opts[] =
{
@ -51,7 +51,7 @@ void teardown() {
void setup(const char *keyboard) {
teardown();
km::kbp::path path = km::kbp::path::join(arg_path, keyboard);
km::core::path path = km::core::path::join(arg_path, keyboard);
try_status(km_core_keyboard_load(path.native().c_str(), &test_kb));
try_status(km_core_state_create(test_kb, test_env_opts, &test_state));
@ -402,8 +402,11 @@ void test_save_option() {
km_core_state_debug_item{KM_CORE_DEBUG_END, 0, {}, {u"", nullptr, nullptr, {}, 1}},
}));
km_core_action_item action = {KM_CORE_IT_PERSIST_OPT, {0,}, };
action.option = &opt;
assert(action_items(test_state, {
{KM_CORE_IT_PERSIST_OPT, {0,}, {uintptr_t(&opt)}},
action,
{KM_CORE_IT_END}
}));
}

View file

@ -6,7 +6,7 @@
#include <test_assert.h>
using namespace km::kbp::kmx;
using namespace km::core::kmx;
namespace {

View file

@ -13,7 +13,7 @@
namespace
{
km::kbp::path const test_kb_path = "/a/dummy/keyboard.mock";
km::core::path const test_kb_path = "/a/dummy/keyboard.mock";
}
#define try_status(expr) \

Some files were not shown because too many files have changed in this diff Show more